I'm trying to figure out this why this entity code is not working. When I call the following code,
await _context.ApplicationUsers
.Include(u => u.SentFriendRequests)
.Include(u => u.ReceievedFriendRequests)
.Include(u => u.Friends)
.FirstOrDefaultAsync()
I get a message
Lambda expression used inside Include is not valid
when I include Friends. Another weird issue if I don't include the friends property, it finds the list of friends, but each item only has RequestedBy object and all RequestedTo users are null. It only finds the RequestedToId.
Here's the code:
public class ApplicationUser : IdentityUser
public ApplicationUser()
{
SentFriendRequests = new List<Friend>();
ReceievedFriendRequests = new List<Friend>();
}
public string Name { get; set; }
public ICollection<Friend> SentFriendRequests { get; set; }
public ICollection<Friend> ReceievedFriendRequests { get; set; }
[NotMapped]
public ICollection<Friend> Friends
{
get
{
var friends = SentFriendRequests.Where(x => x.Approved).ToList();
friends.AddRange(ReceievedFriendRequests.Where(x => x.Approved));
return friends;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<FriendRequest>()
.HasKey(t => new { t.RequestedById, t.RequestedToId });
modelBuilder.Entity<Friend>()
.HasOne(a => a.RequestedBy)
.WithMany(b => b.SentFriendRequests)
.HasForeignKey(c => c.RequestedById);
modelBuilder.Entity<Friend>()
.HasOne(a => a.RequestedTo)
.WithMany(b => b.ReceievedFriendRequests)
.HasForeignKey(c => c.RequestedToId);
}
public class Friend
{
public int RequestedById { get; set; }
public int RequestedToId { get; set; }
public ApplicationUser RequestedBy { get; set; }
public ApplicationUser RequestedTo { get; set; }
public FriendRequestFlag FriendRequestFlag { get; set; }
[NotMapped]
public bool Approved => FriendRequestFlag == FriendRequestFlag.Approved;
}
public enum FriendRequestFlag
{
New,
Approved,
Rejected,
Blocked,
Spam
};
So the first issue is that your Friends collection is NotMapped, therefore, you can't add it to an Include Statement since EFCore doesn't know/care about the property (Which is what you wanted by adding NotMapped).
Your second problem is that when you do an "Include", it's only one level deep. If you have navigational properties on that object, you need to keep going. For example :
await _context.ApplicationUsers
.Include(u => u.SentFriendRequests)
.ThenInclude(u => u.RequestedTo)//This is in the context of SendFriendRequests
.Include(u => u.ReceievedFriendRequests)
.ThenInclude(u => u.RequestedTo)//This is in the context of ReceivedFriendRequests
.FirstOrDefaultAsync()
The problem you are going to find is that, you will have to pre-define how deep you want your connections to go (Since you have what looks to be a recursive type model).
It's probably better to instead create a DTO and select out exactly what you need so then you don't do an overselect.
Related
I am trying to fetch data from two tables that has one to many relationship in Entity Framework Core 3. The models that represent the tables are shown below. One Person can have many PersonNotes. I need to query the PersonNotes table by passing the PersonId. I am currently getting an error saying Person does not contain the definition of Contains. How do I formulate this query.
class Person
{
[Key]
public int Id { get; set; }
public List<PersonNote> PersonNotes { get; set; }
}
class PersonNote
{
[Key]
public int Id { get; set; }
public int PersonId { get; set; }
}
class StackOverflow : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<PersonNote> PersonNotes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.PersonNotes)
.WithOne()
.HasForeignKey(p => p.PersonId);
}
}
Query
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var PersonNotes1 = PersonNotes.Where(p => Person.Contains(p.Id));
return PersonNotes1;
}
To get PersonNotes by one PersonID:
IQueryable<PersonNote> GetPersonNotesSingleId(int ID) =>
context.PersonNotes.Where(p => p.PersonId == ID);
To get PersonNotes by multiple PersonIDs:
IQueryable<PersonNote> GetPersonNotesMultipleId(IEnumerable<int> IDs) =>
context.PersonNotes.Where(p => IDs.Contains(p.PersonId));
The last query will generate IN T-SQL clause:
var notes = GetPersonNotesMultipleId(new[] { 1, 2, 3 });
I am using ef core 3.0 code-first database. I have a table, Status, and I need to create a relationship to itself to list the possible "next status" List<Status> SubsequentStatuses. This is of course to systematically control the workflow of the object.
Using this at face value, it creates a one-to-many relationship and a new StatusId column in the table; however, I need to be able to set a status to be a "SubsequentStatus" to more than one Status.
For example, if there are 4 statuses:
New
In Work
Complete
Cancelled
I want to have the following
New
Subsequent Statuses
In Work
Cancelled
In Work
Subsequent Statuses
Complete
Cancelled
Complete
None
Cancelled
None
Notice that "Cancelled" is related to both "New" and "In Work"
Here are the classes and config that I have at this point:
public class EstimateStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<EstimateStatusRel> SubsequentStatuses { get; set; }
}
public class EstimateStatusRel
{
public int EstimateStatusId { get; set; }
public EstimateStatus EstimateStatus { get; set; }
public int SubsequentStatusId { get; set; }
public EstimateStatus SubsequentStatus { get; set; }
}
public class SapphireContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EstimateStatusRel>().HasKey(x => new { x.EstimateStatusId, x.SubsequentStatusId });
modelBuilder.Entity<StatusRel>()
.HasOne(pt => pt.Status)
.WithMany(p => p.SubsequentStatuses)
.HasForeignKey(pt => pt.StatusId);
}
}
The issue this is creating, is that when Entity Framework is building the migration, it errors out about the multiple cascading delete action, but when I add the NoAction modifier to the modelBuilder fluent API, it still does not clear the error
It ended up being because I didn't specify an OnDelete action
This is my final config:
modelBuilder.Entity<EstimateStatusRel>()
.HasOne(pt => pt.Status)
.WithMany(p => p.SubsequentStatus)
.HasForeignKey(pt => pt.EstimateStatusId)
.OnDelete(DeleteBehavior.NoAction);
For self-reference in one-to-many relationships, you could try the below code:
public class EstimateStatus
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public EstimateStatus ParentStatuses { get; set; }
public virtual ICollection<EstimateStatus> SubsequentStatuses { get; set; }
}
public class TestDbContext:DbContext
{
public TestDbContext (DbContextOptions<TestDbContext> options):base(options)
{ }
public DbSet<EstimateStatus> EstimateStatuse { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EstimateStatus>()
.HasMany(e => e.SubsequentStatuses)
.WithOne(s => s.ParentStatuses)
.HasForeignKey(e => e.ParentId);
}
}
I have this class
public class Client
{
public int Id { get; set; }
public ICollection<Recipe> Recipes { get; set; }
public ICollection<Recipe> Favorites { get; set; }
}
that has 2 1 to many relationship with the recipe class
public class Recipe
{
public int Id { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
}
How can I describe these two relationships? do I need an extra class (favorite)?
Thanks for the help.
EDIT:
I should've been more clear. Client.Recipes is the recipes the client actually owns:
modelBuilder.Entity<Client>()
.HasMany(c => c.Recipes)
.WithRequired(r => r.Client)
.HasForeignKey(r => r.ClientId);
the problem with Client.Favorites is that it doesn't own them thus Recipe.ClientId is invalid for this particular relationship. I need a relational table for this, ut do I need to express it in a class or can it be expressed in Fluent Api? If yes, how?
Sorry if I wasn't explicit at first.
You should be able to define them as a standard 1 to N relationship. Should be something similar to this
class MyContext : DbContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Recipe> Recipes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Client>()
.HasOne(p => p.Clients )
.WithMany(b => b.Recipes ) .HasForeignKey(x => x.ClientId );
modelBuilder.Entity<Client>()
.HasOne(p => p.Clients )
.WithMany(b => b.Favorites ) .HasForeignKey(x => x.ClientId );
}
}
I am trying to learn asp.net core and I am trying to implement a reddit like website.
what I am trying to do is when a subreddit is called, I want to load both admins and the members of the that particular subreddit and pass them to viewmodel.
to achieve this i have made two tables
public class MemberSubReddit
{
public long SubRedditId { get; set; }
public virtual SubReddit SubReddit { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
// the names differ since I was trying to check if the problem is from naming conventions
public class AdminSubReddit
{
public long SubRedditId { get; set; }
public virtual SubReddit SubReddit { get; set; }
public string AspNetUsersId { get; set; }
public virtual ApplicationUser AspNetUsers { get; set; }
}
now when I execute this code below:
var sub = _subRedditRepository.GetSingle(e => e.Name == name, s => s.Posts, s => s.Admins, s => s.Members);
the SubRedditId and SubReddit object loads in both admins and members, but as for User object only the UserId(AspNetUserId) loads and the ApplicationUser do not load.
also this is my modelbuilder code (based on asp.net core website in entityframework core, many to many connections are not handled automatically and you have to do it manually):
modelBuilder.Entity<AdminSubReddit>()
.HasKey(t => new { t.SubRedditId, t.AspNetUsersId });
modelBuilder.Entity<MemberSubReddit>()
.HasKey(t => new { t.SubRedditId, t.UserId });
modelBuilder.Entity<MemberSubReddit>()
.HasOne(us => us.ApplicationUser)
.WithMany(ms => ms.MemberIn)
.HasForeignKey(us => us.UserId);
modelBuilder.Entity<MemberSubReddit>()
.HasOne(ms => ms.SubReddit)
.WithMany(ms => ms.Members)
.HasForeignKey(ms => ms.SubRedditId);
modelBuilder.Entity<AdminSubReddit>()
.HasOne(us => us.AspNetUsers)
.WithMany(ms => ms.AdminIn)
.HasForeignKey(us => us.AspNetUsersId);
modelBuilder.Entity<AdminSubReddit>()
.HasOne(ms => ms.SubReddit)
.WithMany(ms => ms.Admins)
.HasForeignKey(ms => ms.SubRedditId);
so please tell me what I am doing wrong :D
also since I am new to this if you can see any structural problem with the code I would be happy if you point them out.
EDIT
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
This is the method which is getting called in the controller
I am having difficulty getting EF7 to populate the objects referenced in a many to many join. I have followed the docs at https://docs.efproject.net/en/latest/modeling/relationships.html, but the object is still null. From what I can tell you don't have to do anything specific to get EF to populate them. I copied the sample code from the docs page as follows:
public class MyContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public string Title { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
I had to add a constructor to get it to run. I also added a Title field to the Tag class so it has more than just a key. Once I populated some data in the tables, I run the following code to retrieve from the database:
var results = _context.Posts.Include(s => s.PostTags).ToList();
When I examine the results in the debugger, the Tag objects are null even though the keys to obtain them are present. Notice that the Post objects are populated. It is always the second column of the two column key that is not joined:
This is the SQL generated by EF7:
SELECT [s].[PostId], [s].[Content], [s].[Title]
FROM [Posts] AS [s]
ORDER BY [s].[PostId]
SELECT [p].[PostId], [p].[TagId]
FROM [PostTag] AS [p]
WHERE EXISTS (
SELECT 1
FROM [Posts] AS [s]
WHERE [p].[PostId] = [s].[PostId])
ORDER BY [p].[PostId]
It doesn't appear to be fetching the Tag object at all. What am I missing here?
For completeness, I have included the sample data:
Thanks to #SOfanatic for pointing me in the right direction. I am not sure why EF doesn't automatically load the second reference class, but it doesn't. The following code will retrieve the Tag object (as well as the Post object even though we don't explicitly load it).
var results = _context.Posts.Include(s => s.PostTags).ThenInclude(t => t.Tag).ToList();
You are doing a many-to-many with what is known as Payload class. The PostTag class is technically not needed for EF to create a many-to-many relationship.
You are going to have to do something like this:
var results = _context.Posts.Include(s => s.PostTags.Select(pt => pt.Tag).ToList();
Right now your linq is loading the related entities, which are just the id's so it's of no use.
If your PostTags class is not going to have any other fields/properties you should look into creating a many-to-many without the payload