繁体   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