簡體   English   中英

具有TPH和枚舉的實體框架中的多個CASE WHEN

[英]Multiple CASE WHEN in Entity Framework with TPH and enumeration

在EF 6.1.3上使用TPH時,我有一個非常奇怪的行為。 這是一個重現的基本示例:

public class BaseType
{
    public int Id { get; set; }
}
public class TypeA : BaseType
{
    public string PropA { get; set; }
}
public class TypeB : BaseType
{
    public decimal PropB { get; set; }
    public OneEnum PropEnum { get; set; }
}
public class TypeC : TypeB
{
    public int PropC { get; set; }
}

public enum OneEnum
{
    Foo,
    Bar
}

public partial class EnumTestContext : DbContext
{
    public EnumTestContext()
    {
        this.Database.Log = s => { Debug.WriteLine(s); };
    }
    public DbSet<BaseType> BaseTypes { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<EnumTestContext>());
        using (var context = new EnumTestContext())
        {
            context.BaseTypes.Add(new TypeA() { Id = 1, PropA = "propA" });
            context.BaseTypes.Add(new TypeB() { Id = 2, PropB = 4.5M, /*PropEnum = OneEnum.Bar*/ });
            context.BaseTypes.Add(new TypeC() { Id = 3, PropB = 4.5M, /*PropEnum = OneEnum.Foo,*/ PropC = 123 });
            context.SaveChanges();

            var onetype = context.BaseTypes.Where(b => b.Id == 1).FirstOrDefault();

            Console.WriteLine("typeof {0} with {1}", onetype.GetType().Name, onetype.Id);
        }

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }
}

這段代碼可以完美地工作,但是生成的查詢極其奇怪和復雜,尤其是當出現CASE時

SELECT 
    [Limit1].[C1] AS [C1], 
    [Limit1].[Id] AS [Id], 
    [Limit1].[C2] AS [C2], 
    [Limit1].[C3] AS [C3], 
    [Limit1].[C4] AS [C4], 
    [Limit1].[C5] AS [C5]
    FROM ( SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        CASE WHEN ([Extent1].[Discriminator] = N'BaseType') THEN '0X' WHEN ([Extent1].[Discriminator] = N'TypeA') THEN '0X0X' WHEN ([Extent1].[Discriminator] = N'TypeB') THEN '0X1X' ELSE '0X1X0X' END AS [C1], 
        CASE WHEN ([Extent1].[Discriminator] = N'BaseType') THEN CAST(NULL AS varchar(1)) WHEN ([Extent1].[Discriminator] = N'TypeA') THEN [Extent1].[PropA] WHEN ([Extent1].[Discriminator] = N'TypeB') THEN CAST(NULL AS varchar(1)) END AS [C2], 
        CASE WHEN ([Extent1].[Discriminator] = N'BaseType') THEN CAST(NULL AS decimal(18,2)) WHEN ([Extent1].[Discriminator] = N'TypeA') THEN CAST(NULL AS decimal(18,2)) WHEN ([Extent1].[Discriminator] = N'TypeB') THEN [Extent1].[PropB] ELSE [Extent1].[PropB] END AS [C3], 
        CASE WHEN ([Extent1].[Discriminator] = N'BaseType') THEN CAST(NULL AS int) WHEN ([Extent1].[Discriminator] = N'TypeA') THEN CAST(NULL AS int) WHEN ([Extent1].[Discriminator] = N'TypeB') THEN [Extent1].[PropEnum] ELSE [Extent1].[PropEnum] END AS [C4], 
        CASE WHEN ([Extent1].[Discriminator] = N'BaseType') THEN CAST(NULL AS int) WHEN ([Extent1].[Discriminator] = N'TypeA') THEN CAST(NULL AS int) WHEN ([Extent1].[Discriminator] = N'TypeB') THEN CAST(NULL AS int) ELSE [Extent1].[PropC] END AS [C5]
        FROM [dbo].[BaseTypes] AS [Extent1]
        WHERE ([Extent1].[Discriminator] IN (N'TypeA',N'TypeB',N'TypeC',N'BaseType')) AND (1 = [Extent1].[Id])
    )  AS [Limit1]

除了需要多個且無用的THEN CAST(NULL為X)的代價之外,在我的項目中查詢很大(> 50 KB),因為我有很多派生類,包含很多屬性。 如您所料,我的DBA團隊不高興看到這種對我們數據庫的查詢。

如果我刪除TypeB上的枚舉屬性,則請求將更加干凈。 如果只有兩個層次結構級別,也就是class TypeC : BaseType (在示例中為3,因為class TypeC : TypeB ),這是相同的。

是否有任何設置或模型配置或解決方法來避免這種奇怪的行為?

更新資料

如果我刪除TypeB.PropEnum,這是生成的查詢

SELECT TOP (1) 
    [Extent1].[Discriminator] AS [Discriminator], 
    [Extent1].[Id] AS [Id], 
    [Extent1].[PropA] AS [PropA], 
    [Extent1].[PropB] AS [PropB], 
    [Extent1].[PropC] AS [PropC]
    FROM [dbo].[BaseTypes] AS [Extent1]
    WHERE ([Extent1].[Discriminator] IN (N'TypeA',N'TypeB',N'TypeC',N'BaseType')) AND (1 = [Extent1].[Id])

更新2

一種常見的解決方案是創建一個單獨的整數值屬性,並忽略enum屬性。 這可以工作,但是具有2個用於相同目的的屬性非常令人困惑。

public class TypeB : BaseType
{
    public decimal PropB { get; set; }

    public int PropEnumValue { get; set; }

    [NotMapped]
    public OneEnum PropEnum
    {
        get { return (OneEnum)PropEnumValue; }
        set { PropEnumValue = (int)value; }
    }
}

更新3

我在codeplex上發現了一個錯誤: https ://entityframework.codeplex.com/workitem/2117。 它似乎沒有解決。

關於使用EF /大型查詢

我已經完成了EF6和半大型層次結構的一些工作。 您應該考慮幾件事。 首先,您的DBA團隊為什么對這種查詢不滿意。 當然,這些不是他們將要編寫的查詢,但是假設管理層不希望您花時間從頭開始編寫每個查詢,那么他們將不得不忍受您使用ORM框架並且ORM框架可能導致查詢更大。

現在,如果他們有特定的性能問題,則應該解決這些問題。

你可以做什么

現在您該怎么做才能清理查詢。

1)使所有可能抽象的類抽象。

2)將所有其他類別密封。

3)在您的linq查詢中,盡可能將類型強制轉換為具體類型(使用OfType())。 這甚至可能比.Select(x => x as SomethingHere)更好。 如果您有一個特別令人討厭的查詢,則可能需要做一些試驗才能使linq的查詢最佳。

解釋我通過實驗發現的東西

正如您在查詢中注意到的那樣,它正在檢查鑒別符。 如果查詢變得更復雜(我希望那50k查詢就是其中之一),您會發現它為字符串連接添加了代碼,以檢查每種可能的組合。 您會看到在

THEN '0X' WHEN ([Extent1].[Discriminator] = N'TypeA') THEN '0X0X' 

部分。 我做了一些POC試圖弄清楚這種行為,似乎正在發生的事情是實體框架將屬性轉換為“方面”(我的說法)。 例如,如果翻譯后的字符串包含“ 0X”或“ 0X0X”,則類將具有“ PropertyA”。 PropertyB可能會轉換為“ R2D2”,PropertyC可能轉換為“ C3P0”。 如果將類名轉換為“ R2D2C3P0”,則采用這種方式。 它知道它同時具有PropertyB和PropertyC。 它必須考慮一些隱藏的派生類型和所有超類型。 現在,如果enity框架可以更加確定您的類層次結構(通過將類密封),則可以在這里簡化邏輯。 以我的經驗,EF生成的字符串構建邏輯可能比您在此處顯示的還要復雜。 這就是為什么使類成為抽象/密封的EF可以對此更精明並減少查詢的原因。

另一個性能提示

現在,還要確保在“鑒別符”列上具有正確的索引。 (您可以從實體框架內的DbMigration腳本中執行此操作)。

“絕望的”績效指標

現在,如果其他所有方法都失敗,則將您的鑒別符設為int。 這會損害數據庫的可讀性/查詢很多,但有助於提高性能。 (甚至可以讓所有類自動發出包含類名稱的屬性,以便使數據庫內部的類型保持一定的可讀性)。

更新:

經過更多研究后,RX_DID_RX的注釋表明,如果不使用動態代理生成,則只能密封/制作poco的摘要。 (延遲加載和更改跟蹤)。 在我的特定應用程序中,我們沒有使用它,因此它對我們來說效果很好,但是我必須恢復我以前的建議。

有關更多詳細信息, 請參見 EF6特定鏈接http://www.entityframeworktutorial.net/Types-of-Entities.aspx

不過,添加索引以及在linq查詢中進行強制轉換仍然可以提供幫助。

來自Batavia的關於查詢的答案:“現在,如果它們有特定的性能問題,您應該解決這些問題”,不要浪費時間在其他查詢上。 而且,不要浪費時間來理解EF為什么生成查詢(如果您使用Include跟蹤LINQ查詢,您會對生成的查詢印象深刻)。
您需要解決的其他查詢是與EF提供程序不兼容的查詢(例如有時使用EF生成的帶有CROSS JOIN的查詢)。

關於SQL語句中的性能(在DML中,您還可以在stackoverflow上找到其他幾個問題):
-如果需要,可以使用存儲過程;
-EF中缺少一項功能。 您無法使用EF中定義的映射來運行SQL查詢並將其映射到類。 您可以在此處找到一個實現實體框架代碼優先-為SqlQuery配置映射 (實際上,它可能需要一些修復才能與TPH配合使用)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM