EntityFramework Core:主动加载派生类型的导航属性
本文关键字:类型 导航 属性 派生 加载 Core EntityFramework | 更新日期: 2023-09-27 18:08:15
我正在使用EntityFramework Core,并且我试图急于加载仅存在于某些派生类型(所有在单个查询)的导航属性。也许最好用一个简单的例子来说明。
假设您有一些数据结构,如
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; }
}
和一些DbContext
class MyContext : DbContext
{
public DbSet<Transaction> Transactions;
}
如何查询MyContext。transaction返回所有事务,并包括(急切加载)Transaction.product.photo(在product为PhysicalProduct的情况下)和Transaction.product.provider(在product为Service的情况下)?如前所述,尝试仅通过一个查询实现此目标。
我试过以下方法:
// 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();
谁能想到一个方法来实现这一点?谢谢!
昨天我在EF6中遇到了类似的问题- EF Eager取回派生类。目前EF Core在这方面并没有更好——事实上它更糟,因为在EF6的3个解决方案中只有#2在这里有效。
解决方法是:
这绝对不能用一个查询完成。您需要执行主查询并将结果具体化到内存中。然后,对于每个派生的导航类型,收集pk并执行由这些键过滤的查询。最后,由于EF的导航属性修复,你最终会加载所有的导航属性。
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();
这在EF Core中还不支持。请参阅https://github.com/aspnet/EntityFramework/issues/3910查看跟踪它的问题。
我相信唯一的解决方法是执行多个查询,并让EF上下文为您做修复。
我相信从链接的文档,通过使用"ThenInclude"。你可以做到这一点。对不起,我自己没有试过,所以我不能验证它是否有效。
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