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

谁能想到一个方法来实现这一点?谢谢!

EntityFramework Core:主动加载派生类型的导航属性

昨天我在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