Entity Framework Core many-to-many navigation issues - c#

Entity Framework Core has yet to implement many-to-many relationships, as tracked in GitHub issue #1368; however, when I follow the navigation examples in that issue or similar answers here at Stack Overflow, my enumeration fails to yield results.
I have a many-to-many relationship between Photos and Tags.
After implementing the join table, examples show I should be able to:
var tags = photo.PhotoTags.Select(p => p.Tag);
While that yields no results, I am able to to load via:
var tags = _context.Photos
.Where(p => p.Id == 1)
.SelectMany(p => p.PhotoTags)
.Select(j => j.Tag)
.ToList();
Relevant code:
public class Photo
{
public int Id { get; set; }
public virtual ICollection<PhotoTag> PhotoTags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public virtual ICollection<PhotoTag> PhotoTags { get; set; }
}
public class PhotoTag
{
public int PhotoId { get; set; }
public Photo Photo { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<PhotoTag>().HasKey(x => new { x.PhotoId, x.TagId });
}
What am I missing from other examples?

In fact this is not a specific for many-to-many relationship, but in general to the lack of lazy loading support in EF Core. So in order to have Tag property populated, it has to be eager (or explicitly) loaded. All this is (sort of) explained in the Loading Related Data section of the EF Core documentation. If you take a look at Including multiple levels section, you'll see the following explanation
You can drill down thru relationships to include multiple levels of related data using the ThenInclude method. The following example loads all blogs, their related posts, and the author of each post.
and example for loading the Post.Author which is pretty much the same as yours:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
So to make this working
var tags = photo.PhotoTags.Select(p => p.Tag);
the photo variable should have been be retrieved using something like this:
var photo = _context.Photos
.Include(e => e.PhotoTags)
.ThenInclude(e => e.Tag)
.FirstOrDefault(e => e.Id == 1);

Related

How to Use Include More Than Once Ef Core? [duplicate]

Suppose I have a entity class with one to one relationship as below:
public class Transaction
{
public int TransactionID { get; set; }
public Double Amount { get; set; }
public int TransactionDetailID { get; set; }
public virtual TransactionDetail TransactionDetailFk { get; set; }
}
public class TransactionDetail
{
public int TransactionDetailID { get; set; }
public DateTime PostedDate { get; set; }
public int TransactionTypeID { get; set; }
public int TransactionCategoryID { get; set; }
public int PaymentMethodID { get; set; }
public int PaymentPayorID { get; set; }
public virtual TransactionType TransactionTypeFk { get; set; }
public virtual TransactionCategory TransactionCategoryFk { get; set; }
public virtual PaymentMethod PaymentMethodFk { get; set; }
public virtual PaymentPayor PaymentPayorFk { get; set; }
public virtual Transaction TransactionFk { get; set; }
}
Now I would like to get a Transaction Object based on TransactionID, in addition I would like to get all my related object from Transaction to TransactionDetail
to (TransactionType/TransactionCategory/PaymentMethod/PaymentPayor), which is a two-level data mapping and my function would be like:
public async Task<Transaction> GetSingleFullTransactionByIDAsync(int transactionID)
=> await GetSingleOrDefaultAsync(
predicate: tr => (tr.TransactionID == transactionID),
include: (obj => (
obj
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.PaymentPayorFk)
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.PaymentMethodFk)
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.TransactionTypeFk)
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.TransactionCategoryFk)
))
);
I feel my code is not that clean and tidy, since for each related entity of Transaction Detail, I am actually including multiple instance of transaction detail...I would like to do something like below which only include one instance of Transaction Detail but entity framework does not allow me to do that:
public async Task<Transaction> GetSingleFullTransactionByIDAsync(int transactionID)
=> await GetSingleOrDefaultAsync(
predicate: tr => (tr.TransactionID == transactionID),
include: (obj => (
obj
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.PaymentPayorFk)
.ThenInclude(td => td.PaymentMethodFk)
.ThenInclude(td => td.TransactionTypeFk)
.ThenInclude(td => td.TransactionCategoryFk)
))
);
So what would be the efficient way for doing that using EF core? Note here I am using repository pattern so I can't use sql-to-linq expression, but only have to use "Include"/"ThenInclude" operation...
I feel my code is not that clean and tidy, since for each related entity of Transaction Detail, I am actually including multiple instance of transaction detail...
This is exactly the intended ("by design")way of including multiple related entities in EF Core. It is explained (with example) in the Loading Related Data - Including multiple levels section of the EF Core documentation:
You may want to include multiple related entities for one of the entities that is being included. For example, when querying Blogs, you include Posts and then want to include both the Author and Tags of the Posts. To do this, you need to specify each include path starting at the root. For example, Blog -> Posts -> Author and Blog -> Posts -> Tags. This does not mean you will get redundant joins, in most cases EF will consolidate the joins when generating SQL.
Note the last paragraph. To recap, every Include / ThenInclude chain represents an entity path to be loaded. Each entity contained in the include paths is included just once.

Get Object with deep level of related entity

Suppose I have a entity class with one to one relationship as below:
public class Transaction
{
public int TransactionID { get; set; }
public Double Amount { get; set; }
public int TransactionDetailID { get; set; }
public virtual TransactionDetail TransactionDetailFk { get; set; }
}
public class TransactionDetail
{
public int TransactionDetailID { get; set; }
public DateTime PostedDate { get; set; }
public int TransactionTypeID { get; set; }
public int TransactionCategoryID { get; set; }
public int PaymentMethodID { get; set; }
public int PaymentPayorID { get; set; }
public virtual TransactionType TransactionTypeFk { get; set; }
public virtual TransactionCategory TransactionCategoryFk { get; set; }
public virtual PaymentMethod PaymentMethodFk { get; set; }
public virtual PaymentPayor PaymentPayorFk { get; set; }
public virtual Transaction TransactionFk { get; set; }
}
Now I would like to get a Transaction Object based on TransactionID, in addition I would like to get all my related object from Transaction to TransactionDetail
to (TransactionType/TransactionCategory/PaymentMethod/PaymentPayor), which is a two-level data mapping and my function would be like:
public async Task<Transaction> GetSingleFullTransactionByIDAsync(int transactionID)
=> await GetSingleOrDefaultAsync(
predicate: tr => (tr.TransactionID == transactionID),
include: (obj => (
obj
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.PaymentPayorFk)
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.PaymentMethodFk)
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.TransactionTypeFk)
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.TransactionCategoryFk)
))
);
I feel my code is not that clean and tidy, since for each related entity of Transaction Detail, I am actually including multiple instance of transaction detail...I would like to do something like below which only include one instance of Transaction Detail but entity framework does not allow me to do that:
public async Task<Transaction> GetSingleFullTransactionByIDAsync(int transactionID)
=> await GetSingleOrDefaultAsync(
predicate: tr => (tr.TransactionID == transactionID),
include: (obj => (
obj
.Include(entity => entity.TransactionDetailFk)
.ThenInclude(td => td.PaymentPayorFk)
.ThenInclude(td => td.PaymentMethodFk)
.ThenInclude(td => td.TransactionTypeFk)
.ThenInclude(td => td.TransactionCategoryFk)
))
);
So what would be the efficient way for doing that using EF core? Note here I am using repository pattern so I can't use sql-to-linq expression, but only have to use "Include"/"ThenInclude" operation...
I feel my code is not that clean and tidy, since for each related entity of Transaction Detail, I am actually including multiple instance of transaction detail...
This is exactly the intended ("by design")way of including multiple related entities in EF Core. It is explained (with example) in the Loading Related Data - Including multiple levels section of the EF Core documentation:
You may want to include multiple related entities for one of the entities that is being included. For example, when querying Blogs, you include Posts and then want to include both the Author and Tags of the Posts. To do this, you need to specify each include path starting at the root. For example, Blog -> Posts -> Author and Blog -> Posts -> Tags. This does not mean you will get redundant joins, in most cases EF will consolidate the joins when generating SQL.
Note the last paragraph. To recap, every Include / ThenInclude chain represents an entity path to be loaded. Each entity contained in the include paths is included just once.

Using Include vs ThenInclude

I have been experimenting a little with Entity Framework, and after facing the error below, I tried using ThenInclude to resolve it.
The expression '[x].ModelA.ModelB' passed to the Include operator could not be bound
But now it seems I lack some understanding of why it did solve the problem
What's the difference between this:
.Include(x => x.ModelA.ModelB)
And this:
.Include(x => x.ModelA).ThenInclude(x => x.ModelB)
"Include" works well with list of object, but if you need to get multi-level data, then "ThenInclude" is the best fit. Let me explain it with an example. Say we have two entities, Company and Client:
public class Company
{
public string Name { get; set; }
public string Location { get; set; }
public List<Client> Clients {get;set;}
}
public class Client
{
public string Name { get; set; }
public string Domains { get; set; }
public List<string> CountriesOfOperation { get; set; }
}
Now if you want just companies and the entire client list of that company, you can just use "Include":
using (var context = new YourContext())
{
var customers = context.Companies
.Include(c => c.Clients)
.ToList();
}
But if you want a Company with "CountriesOfOperation" as related data, you can use "ThenInclude" after including Clients like below:
using (var context = new MyContext())
{
var customers = context.Companies
.Include(i => i.Clients)
.ThenInclude(a => a.CountriesOfOperation)
.ToList();
}
The difference is that Include will reference the table you are originally querying on regardless of where it is placed in the chain, while ThenInclude will reference the last table included. This means that you would not be able to include anything from your second table if you only used Include.

How to include only selected properties on related entities

I can include only related entities.
using (var context = new BloggingContext())
{
// Load all blogs, all related posts
var blogs1 = context.Blogs
.Include(b => b.Posts)
.ToList();
}
However, I don't need entire BlogPost entity. I'm interested only in particular properties, e.g:
using (var context = new BloggingContext())
{
// Load all blogs, all and titles of related posts
var blogs2 = context.Blogs
.Include(b => b.Posts.Select(p => p.Title) //throws runtime exeption
.ToList();
foreach(var blogPost in blogs2.SelectMany(b => b.Posts))
{
Console.Writeline(blogPost.Blog.Id); //I need the object graph
Console.WriteLine(blogPost.Title); //writes title
Console.WriteLine(blogPost.Content); //writes null
}
}
You either use Include which loads the entire entity, or you project what you need to a .Select:
var blogs2 = context.Blogs
.Select(x => new
{
BlogName = x.BlogName, //whatever
PostTitles = x.Post.Select(y => y.Title).ToArray()
})
.ToList();
Or, you could do something like this:
var blogs2 = context.Blogs
.Select(x => new
{
Blog = x,
PostTitles = x.Post.Select(y => y.Title).ToArray()
})
.ToList();
A Select is always better when you don't need the entire child, as it prevents querying unneeded data.
In fact what you want is: split an entity in a common, representational part and a special part that you don't always want to pull from the database. This is not an uncommon requirement. Think of products and images, files and their content, or employees with public and private data.
Entity framework core supports two ways to achieve this: owned type and table splitting.
Owned type
An owned type is a type that's wrapped in another type. It can only be accessed through its owner. This is what it looks like:
public class Post
{
public int ID { get; set; }
public Blog Blog { get; set; }
public string Title { get; set; }
public PostContent Content { get; set; }
}
public class PostContent
{
public string Content { get; set; }
}
And the owned-type mapping:
modelBuilder.Entity<Post>().OwnsOne(e => e.Content);
Where Blog is
public class Blog
{
public Blog()
{
Posts = new HashSet<Post>();
}
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
}
However, as per the docs:
When querying the owner the owned types will be included by default.
Which means that a statement like...
var posts = context.Posts.ToList();
...will always get you posts and their contents. Therefore, owned type is probably not the right approach for you. I still mentioned it, because I found out that when Posts are Included...
var blogs = context.Blogs.Include(b => b.Posts).ToList();
...the owned types, PostContents, are not included (DISCLAIMER: I'm not sure if this is a bug or a feature...). In this case, when the owned types should be included a ThenInclude is required:
var blogs = context.Blogs.Include(b => b.Posts)
.ThenInclude(p => p.Content).ToList();
So if Posts will always be queried through Blogs, owned type may be appropriate.
I don't think this applies here, but it does when children having owned types have an identifying relationship with their parents (classical example: Order-OrderLine).
Table splitting
With table splitting a database table is split up into two or more entities. Or, from the objects side: two or more entities are mapped to one table. The model is almost identical. The only difference is that PostContent now has a required primary key property (ID, of course having the same value as Post.ID):
public class Post
{
public int ID { get; set; }
public Blog Blog { get; set; }
public string Title { get; set; }
public PostContent Content { get; set; }
}
public class PostContent
{
public int ID { get; set; }
public string Content { get; set; }
}
And the table-splitting mapping:
modelBuilder.Entity<Post>()
.HasOne(e => e.Content).WithOne()
// or .WithOne(c => c.Post) if there is a back reference
.HasForeignKey<PostContent>(e => e.ID);
modelBuilder.Entity<Post>().ToTable("Posts");
modelBuilder.Entity<PostContent>().ToTable("Posts");
Now Posts will always be queried without their contents by default. PostContent should always be Include()-ed explicitly.
Also, PostContent can now be queried without its owner Post:
var postContents = context.Set<PostContent>().ToList();
I think this is exactly what you're looking for.
Of course you can do without these mappings if you'll always use projections when you want to fetch posts without contents.
You can try this :
using (var context = new BloggingContext())
{
var blogProps = context.Blogs
.SelectMany(b =>
b.Posts.Select(p =>
new { Blog = b, PostTitle = p.Title }
)
)
.ToList();
}
EDIT
If you want to stick to your data model, you could try something like this :
using (var context = new BloggingContext())
{
var blogProps = context.Blogs
.Select(b =>
new Blog
{
Name = b.Name,
Posts = new List<Post>(b.Posts.Select(p =>
new Post
{
Title = p.Title
})
}
)
.ToList();
}
I think there's a much easier way to do this. Projection is nice and all, but what if you want all the columns from your parent entity and most of them from the child? When those types have a lot of properties, using projection means you have a lot of lines of code to write just to select everything you want except the few that you don't. Well, since using projection means your entities won't be tracked, it's much easier to use .AsNoTracking() and then just empty out the things you don't want.
var foos = await _context.DbSet<Foo>()
.AsQueryable()
.Where(x => x.Id == id)
.Include(x => x.Bars)
.AsNoTracking()
.ToListAsync();
foreach (var foo in foos)
{
foreach (Bar bar in foo.Bars)
{
bar.Baz = null;
}
}

EF Code First working with many-to-many relationships

I am using Visual Studio 2010, C# 4.0 and Entity Framework 5.0. I have been using database first development for many years but am trying to move to code first and am running into problems. Reading and searching does not seem to address the problems
I have simplified my problem as follows - I have two classes - Assessors and Documents.
public class Assessor
{
public int AssessorID { get; set; }
public virtual List<Document> Documents { get; set; }
}
public class Document
{
public int DocumentID { get; set; }
public string DocumentLocation { get; set; }
public string DocumentName { get; set; }
public virtual List<Assessor> Assessors { get; set; }
}
with the context
public class DocumentAssignment : DbContext
{
public DbSet<Assessor> Assessors { get; set; }
public DbSet<Document> Documents { get; set; }
}
An assessor can have many documents and a document can have many assessors (a classic many-to-many relationship).
I am using convention to create the relationship but have also used the fluent API. I have seeded the document table.
My two questions:
ONE - I want to assign documents to assessors - what is the best way to save this to the database?
TWO I have the following method to retrieve documents assigned to an assessor:
public static IEnumerable<MaternalDocument> GetAssignedDocumentList(int UserID, string ConnectionString)
{
using (DocumentAssignment dbContext = new DocumentAssignment(ConnectionString))
{
return returnValue = dbContext.MaternalAssessments
.Where(m => m.AssessorID == UserID)
.Include(m => m.MaternalDocuments)
.Select(m => m.MaternalDocuments)
.ToList();
}
}
but I cannot get this to compile because of mapping issues. What am I doing wrong?
You have to tell the DbContext about how the many-to-many relationship is set up, by overriding OnModelCreating in DocumentAssignment. Replace AssessorDocuments in this code with your relation table name.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Assessor>()
.HasMany(a => a.Documents)
.WithMany(d => d.Assessors)
.Map(m =>
{
m.MapLeftKey("AssessorID");
m.MapRightKey("DocumentID");
m.ToTable("AssessorDocuments");
});
base.OnModelCreating(modelBuilder);
}
To assign a Document to an Assessor (assuming a Document exists with DocumentID of 1 and an Assessor exists with an AssessorID of 1):
using (var context = new DocumentAssignment())
{
var assessor = context.Assessors.Find(1);
var document = context.Documents.Find(1);
assessor.Documents.Add(document);
context.SaveChanges();
}
Your GetAssignedDocumentList method would look something like this:
public static IEnumerable<Document> GetAssignedDocumentList(int UserID)
{
using (var context = new DocumentAssignment())
{
return context.Documents.Where(d => d.Assessors.Any(a => a.AssessorID == UserID));
}
}

Categories