EntityFramework核心:渴望加載派生類型的導航屬性

c# entity-framework entity-framework-core linq-to-sql

我正在使用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(如果產品是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在這方面並不是更好 - 事實上它更糟糕,因為從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();

熱門答案

EF Core尚不支持此功能。有關跟踪它的問題,請參閱https://github.com/aspnet/EntityFramework/issues/3910

我相信唯一的解決方法是執行多個查詢並讓EF上下文為您執行修復。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因