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!
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();
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.