EntityFramework核心:渴望加载派生类型的导航属性

时间:2016-09-21 08:27:48

标签: c# entity-framework linq-to-sql entity-framework-core

我正在使用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.Transactions以返回所有交易,并包括(急切加载)Transaction.product.photo(如果产品是PhysicalProduct)和Transaction.product.provider(如果产品是服务)? 如上所述,尝试仅使用一个查询来实现此目的。

我尝试过以下方法:

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

有谁能想到实现这个目标的方法? 谢谢!

3 个答案:

答案 0 :(得分:3)

昨天我在EF6中遇到类似的问题 - EF Eager fetching derived class。目前EF Core在这方面并不是更好 - 事实上它更糟糕,因为从3 EF6的变通办法中只有#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();

答案 1 :(得分:2)

EF Core尚不支持此功能。有关跟踪它的问题,请参阅https://github.com/aspnet/EntityFramework/issues/3910

我相信唯一的解决方法是执行多个查询并让EF上下文为您完成修复。

答案 2 :(得分:-1)

我要通过使用&#34; ThenInclude&#34;来链接文档。你能够做到这一点。对不起,但我没有亲自尝试,所以我无法验证它是否有效。

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