Reputation: 5744
I'm using EntityFramework Core, and am trying to eager-load navigation properties that only exist on some derived type (all within a single query). Probably best to demonstrate with a simple example.
Assume you have some data structure like
class Transaction
{
public Product product { get; set; }
public DateTime date { get; set; }
}
abstract class Product
{
public string Name { get; set; }
}
class PhysicalProduct : Product
{
public Photo photo { get; set; }
}
class Service : Product
{
public Person provider { get; set; }
}
And some DbContext
class MyContext : DbContext
{
public DbSet<Transaction> Transactions;
}
How can I query MyContext.Transactions to return all transactions, and include (eager load) Transaction.product.photo (in case product is PhysicalProduct), and Transaction.product.provider (in case product is Service) ? As mentioned, trying to achieve this with only one query.
I've tried the following:
// This is conceptually what I want to achieve.
// Not very surprisingly, this will throw an InvalidCastException
Transactions
.Include(x => ((PhysicalProduct)x.product).photo)
.Include(x => ((Service)x.product).provider)
.ToList();
// Based on http://stackoverflow.com/questions/7635152/entity-framework-eager-loading-of-subclass-related-objects
// Projection into an anonymous type, then transform back.
// doesn't work though, throws an InvalidOperationException, e.g.
// The property "photo" on entity type "Product" could not be found. Ensure that the property exists and has been included in the model.
// i.e. even though I wrapped this in a condition (x.product is PhysicalProduct), seems like EntityFramework still tries to execute or parse the statement thereafter even if the condition is not true.
var query = Transactions.Select(x => new
{
_transaction = x,
_physicalProductPhoto = (x.product is PhysicalProduct) ? ((PhysicalProduct)x.product).photo : null;
_serviceProvider = (x.product is Service) ? ((Service)x.product).provider : null;
})
.ToList() // Execute query. Exception will be thrown at this step.
.Select(x =>
{
var result = x._transaction;
if (x.product is PhysicalProduct)
((PhysicalProduct)x.product).photo = x._physicalProductPhoto;
else if(x.product is Service)
((Service)x.product).provider = x._serviceProvider;
return result;
})
.ToList();
Can anyone think of a way to achieve this? Thanks!
Upvotes: 3
Views: 2335
Reputation: 1
I am to believe from the linked documentation that by using "ThenInclude". You are able to accomplish this. Sorry but I did not try it myself, so I can not verify that it works.
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.Include(blog => blog.Owner)
.ThenInclude(owner => owner.Photo)
.ToList();
https://docs.efproject.net/en/latest/querying/related-data.html
Upvotes: -1
Reputation: 205759
Yesterday I was fighting similar issue in EF6 - EF Eager fetching derived class. Currently EF Core is not better in that regard - in fact it's worse, because from the 3 EF6 workarounds only the #2 works here.
The workaround is:
It definitely can't be done with a single query. You need to execute the master query and materialize the result in memory. Then for each derived navigation type, collect the PKs and execute a query filtered by these keys. At the end, due to EF navigation property fixup you'll end up with all the navigation properties loaded.
var transactions = db.Transactions.Include(e => e.product).ToList();
var productIds = transactions.Where(e => e.product is PhysicalProduct)
.Select(e => e.product.Id).Distinct();
db.BaseProducts.OfType<PhysicalProduct>().Include(e => e.photo)
.Where(e => productIds.Contains(e.Id)).Load();
var serviceIds = transactions.Where(e => e.product is Service)
.Select(e => e.product.Id).Distinct();
db.BaseProducts.OfType<Service>().Include(e => e.provider)
.Where(e => serviceIds.Contains(e.Id)).Load();
Upvotes: 3
Reputation: 7533
This isn't yet supported in EF Core. See https://github.com/aspnet/EntityFramework/issues/3910 for the issue tracking it.
I believe the only workaround is to execute multiple queries and let the EF context do the fixup for you.
Upvotes: 3