2016-09-21 94 views
2

我正在使用EntityFramework核心,並試圖加載僅存在於某種派生類型(全部在單個查詢中)的導航屬性。 可能最好用一個簡單的例子來演示。EntityFramework核心:渴望加載派生類型的導航屬性

假設你有一個像

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。照片(如果產品是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

昨天我在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(); 
+0

謝謝伊萬!不幸的是,這可能會使EF的大部分IAsyncEnumerable優勢無效;加上它讓我頭痛,因爲我無法執行並將密鑰存入內存,這似乎與EFCore和GUID-Keys一起生成錯誤的SQL,所以需要像在您的示例中一樣將IID保留爲IQueryable(productIds ) - 儘管如此,多層次案例的一個障礙,例如包括本身屬於派生類型的屬性的派生類型的引用。無論如何,希望我能弄明白這一點;感謝您的意見,這非常有幫助! – Bogey

2

EF Core尚未支持此功能。跟蹤它的問題請參閱https://github.com/aspnet/EntityFramework/issues/3910

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

+0

感謝亞瑟。在EFCore中,這樣一個關鍵性的功能並沒有實現。解決方法是導致一些問題(回覆伊萬的帖子中描述)不幸,但似乎這是唯一的方式去,然後 – Bogey

-1

我相信通過使用「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

+0

謝謝。我相信。然後插入是用於子引用(如你的例子),但不是爲了鑄造類型。即如果Blogs.Posts如果鍵入Post,並且DerivedPost是派生的Post,則不允許包含任何特定於DerivedPost的屬性 – Bogey