2016-03-08 36 views
1

我有一個實體框架模型,它有一個名爲「Call」的基類(抽象)和兩個名爲「SurvoCall」和「VoicemailCall」的子類,帶有鑑別器「Survo」和「Voicemail」分別。實體框架繼承TPH在每個查詢添加鑑別器

問題是,每當我對呼叫進行查詢時,例如計算我們昨天得到的呼叫數量(無論他們是Survo還是Voicemail),實體框架都會向查詢中添加以下「where 「條款:

...辨別器(‘Survo’,‘語音信箱’)....

對我來說,它看起來像不必要的傷害和性能,而無需任何好處。因爲如果我通過調用查詢(不使用OfType <>),我告訴我需要每次調用,不需要檢查鑑別器,但是實體框架仍會添加「入」條件。

有沒有辦法解決這個問題?因爲它影響我們的一些查詢...

謝謝!

+0

客場各地:不使用TPH。提高查詢速度;添加一個索引。 –

回答

2

你總是可以看看源實體框架在這裏找到答案:https://entityframework.codeplex.com/

我做了一些調試和研究,就這個問題和它看來你是無法擺脫的這個[Dicriminator ] IN('X1','X2',...,'Xn')完全由於視圖生成和實現的細節而定,至少對於實體框架6來說是這樣。

在爲此生成一個SELECT語句特定表(Call for you),它會評估此表具有的所有列以及可從Call派生的所有可能類型。在你的情況下,有2種類型 - SurvoCall和VoicemailCall。要獲得它需要2個不同的查詢,這兩個實體的數據,因爲他們極有可能在這個特定表的不同列集合他們,所以在技術上它產生2選擇在這裏:

SELECT Id, PhoneNumber, Duration, etc.., SurvoResult FROM [Call] where [Discriminator] = 'Survo'

SELECT Id, PhoneNumber, Duration, etc.., VoicemailDuration FROM [Call] where [Discriminator] = 'Voicemail'

一些內部優化它「concantenates」這2個SQL查詢到一個你有和具體類型選擇委託給materializer後:

SELECT Id, PhoneNumber, Duration, etc.., SurvoResult, VoicemailDuration FROM [Call] where [Discriminator] IN ('Survo', 'Voicemail')

它真的看起來像實體框架內部架構方式來建立歧視表查詢 - 建立1查詢每個鑑別器,團結,結果你得到這個IN子句。這背後的原因是什麼?可能您需要深入Entity Framework源代碼內部,或詢問正在開發這部分代碼的人員。

從概念上講,這個解決方案很有意義 - 它限制了查詢通過Entity Framework知道的數據,並且知道如何創建它的具體類型,所以如果Discriminator列值不正確 - 它可能是 - 什麼也得不到破壞你的應用程序。但我不認爲這是主要原因。

另一個有趣的事情要注意 - 你可能有多層次的實體。下面是一個例子:

[Table("ProductOrService")] 
public abstract class ProductOrService 
{ 
    public int Id { get; set; } 
    public DateTime AddedToCartAt { get; set; } 
    public string Name { get; set; } 
    public decimal Price { get; set; } 
} 

[Table("ProductOrService")] 
public class Level1Product : ProductOrService 
{ 
    public string Owner { get; set; } 
} 

[Table("ProductOrService")] 
public class Level1Service : ProductOrService 
{ 
    public string Activator { get; set; } 
} 

[Table("ProductOrService")] 
public abstract class Level2ProductOrService : ProductOrService 
{ 
    public string SomeProp { get; set; } 
} 

[Table("ProductOrService")] 
public class Level2Product : Level2ProductOrService 
{ 
    public DateTime IssuedAt { get; set; } 
} 

[Table("ProductOrService")] 
public class Level2Service : Level2ProductOrService 
{ 
    public DateTime ExpiresAt { get; set; } 
} 

在這個例子中,我們有2級層次:

ProductOrService (abstract) 
->Level1Product 
->Level1Service 
->Level2ProductOrService (abstract) 
->->Level2Product 
->->Level2Service 

,並且可以使用dbSets可能有機會獲得層次結構的任何級別:

public class TestDbContext : DbContext 
    { 
     public IDbSet<ProductOrService> ProductsOrServices { get; set; } 
     public IDbSet<Level2ProductOrService> Level2ProductsOrServices { get; set; } 
     public IDbSet<Level1Product> Level1Products { get; set; } 
     public IDbSet<Level2Service> Level2Services { get; set; } 
    } 

之後調用這些dbSets給人蠻有趣的結果:

var level1Root = context.ProductsOrServices 
    .Where(x=>x.Price > 0); 
//generates query with all possible discriminators: 
{SELECT 
    ... 
    FROM [dbo].[ProductOrService] AS [Extent1] 
    WHERE ([Extent1].[Discriminator] IN (N'Level1Product',N'Level2Product',N'Level2Service',N'Level1Service')) AND ([Extent1].[Price] > cast(0 as decimal(18)))} 

var level2Root = context.Level2ProductsOrServices 
    .Where(x => x.Price > 0); 

//generates query with discriminators twice! first for root hierarchy, then for second level hierarchy! 
{SELECT 
    ... 
    FROM [dbo].[ProductOrService] AS [Extent1] 
    WHERE ([Extent1].[Discriminator] IN (N'Level1Product',N'Level2Product',N'Level2Service',N'Level1Service')) AND ([Extent1].[Discriminator] IN (N'Level2Product',N'Level2Service')) AND ([Extent1].[Price] > cast(0 as decimal(18)))} 


var level1Products = context.Level1Products 
    .Where(x => x.Price > 0); 
//generates correct query with 1 discriminator 
{SELECT 
    '0X0X' AS [C1], 
    ... 
    FROM [dbo].[ProductOrService] AS [Extent1] 
    WHERE ([Extent1].[Discriminator] = N'Level1Product') AND ([Extent1].[Price] > cast(0 as decimal(18)))} 

var level2Services = context.Level2Services 
    .Where(x => x.Price > 0); 
//generates correct query with 1 discriminator 
{SELECT 
    '0X0X0X' AS [C1], 
    ... 
    FROM [dbo].[ProductOrService] AS [Extent1] 
    WHERE ([Extent1].[Discriminator] = N'Level2Service') AND ([Extent1].[Price] > cast(0 as decimal(18)))} 

正如您所看到的,查詢歧視表的基本類型會爲查詢對象的每個層級(當前+所有基本類型)使用IN子句構建次優查詢(如果它具有派生類型)。

從用例中我並沒有真正面對這些鑑別器的查詢時間的任何問題,除非你正在加載一個真正的大對象圖,其中內置深層的鑑別器,但在這些情況下,你不能真正影響查詢建設。當您的實體不是聚合根目錄時,總體使用Table Per Hierarchy會給您帶來很大的麻煩。避免它的可能方法是擺脫數據庫處理的繼承 - 創建一個實體,它具有您需要的所有列,並在C#端自己處理它。這種方法確實也有缺點,並且使代碼更加骯髒。您可以在您的真實域模型和基於實體框架的持久性模型之間添加一些圖層,請參閱以下文章:http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model/

遺憾的是,似乎沒有將繼承與實體框架一起使用的優秀且高性能的解決方案。但它是開源的,所以你可以嘗試分叉,併產生一個很好的解決方案:)