简体   繁体   English

具有TPH和枚举的实体框架中的多个CASE WHEN

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

I have a very strange behavior when using TPH on EF 6.1.3. 在EF 6.1.3上使用TPH时,我有一个非常奇怪的行为。 Here is a basic example to reproduce : 这是一个重现的基本示例:

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();
    }
}

This code works perfectly, but the generated query is extrememly weird and complex, especialy there are a lot of CASE WHEN 这段代码可以完美地工作,但是生成的查询极其奇怪和复杂,尤其是当出现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]

Except the cost of multiple and useless THEN CAST(NULL as X) , the query is large (> 50 KBs) in my project because I have a lot of derived classes, containing a lot a properties. 除了需要多个且无用的THEN CAST(NULL为X)的代价之外,在我的项目中查询很大(> 50 KB),因为我有很多派生类,包含很多属性。 As you can expect, my DBA team is not happy to see this kind of queries to our databases. 如您所料,我的DBA团队不高兴看到这种对我们数据库的查询。

If I remove the enumeration property on TypeB, the request is much more cleaner. 如果我删除TypeB上的枚举属性,则请求将更加干净。 Same thing if I have only two hierarchy levels, aka class TypeC : BaseType (compared to 3 in the example because class TypeC : TypeB ). 如果只有两个层次结构级别,也就是class TypeC : BaseType (在示例中为3,因为class TypeC : TypeB ),这是相同的。

Is there any settings or model configuration or workaround to avoid this strange behavior ? 是否有任何设置或模型配置或解决方法来避免这种奇怪的行为?

Update 更新资料

Here is the generated query if I remove TypeB.PropEnum 如果我删除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])

Update 2 更新2

A common solution is to create a separate property the integer value and ignore the enum property. 一种常见的解决方案是创建一个单独的整数值属性,并忽略enum属性。 This works, but it's quite confusing to have 2 properties for the same purpose. 这可以工作,但是具有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; }
    }
}

Update 3 更新3

I've found a bug on codeplex : https://entityframework.codeplex.com/workitem/2117 . 我在codeplex上发现了一个错误: https ://entityframework.codeplex.com/workitem/2117。 It doesn't seems to be solved. 它似乎没有解决。

About using EF/Large queries 关于使用EF /大型查询

I've done some work with EF6 and semi-large hierarchies. 我已经完成了EF6和半大型层次结构的一些工作。 There are a few things you should consider. 您应该考虑几件事。 First of all why isn't your DBA team not happy with these kind of queries. 首先,您的DBA团队为什么对这种查询不满意。 Of course these arn't the queries they would write but assuming management doesn't want you to spend the time to write every single query from scratch they'll have to live with the fact that you use an ORM framework and that ORM framework might cause queries that are a bit larger. 当然,这些不是他们将要编写的查询,但是假设管理层不希望您花时间从头开始编写每个查询,那么他们将不得不忍受您使用ORM框架并且ORM框架可能导致查询更大。

Now if they have specific performance concerns you SHOULD address those. 现在,如果他们有特定的性能问题,则应该解决这些问题。

What you can do 你可以做什么

now what can you do to clean up your queries. 现在您该怎么做才能清理查询。

1) Make all classes that could be abstract abstract. 1)使所有可能抽象的类抽象。

2) Make all other classes sealed. 2)将所有其他类别密封。

3) In your linq queries cast to concrete types where possible(using OfType() ). 3)在您的linq查询中,尽可能将类型强制转换为具体类型(使用OfType())。 This might even work better than an .Select(x => x as SomethingHere). 这甚至可能比.Select(x => x as SomethingHere)更好。 If you have a particular nasty query it might take some experimentation what tunes your query from linq best. 如果您有一个特别令人讨厌的查询,则可能需要做一些试验才能使linq的查询最佳。

explanation what i've found through experimentation 解释我通过实验发现的东西

As you notice with your queries it's checking the discriminator. 正如您在查询中注意到的那样,它正在检查鉴别符。 If you queries get a bit more complex (and i expect those 50k queries to be one of those) you'll see that it adds in code for string concatenation to check every possible combination. 如果查询变得更复杂(我希望那50k查询就是其中之一),您会发现它为字符串连接添加了代码,以检查每种可能的组合。 you see that happening a bit in the 您会看到在

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

part. 部分。 I've did some POCs trying to figure out this behaviour and what seems to be happening is that entity framework is translating properties to 'aspects' (my term). 我做了一些POC试图弄清楚这种行为,似乎正在发生的事情是实体框架将属性转换为“方面”(我的说法)。 For example an class will have a "PropertyA" if the translated string contains either '0X' or '0X0X'. 例如,如果翻译后的字符串包含“ 0X”或“ 0X0X”,则类将具有“ PropertyA”。 PropertyB it might translate to "R2D2" and PropertyC to "C3P0". PropertyB可能会转换为“ R2D2”,PropertyC可能转换为“ C3P0”。 that way if a classname is translated to "R2D2C3P0". 如果将类名转换为“ R2D2C3P0”,则采用这种方式。 it knows it has both PropertyB & PropertyC. 它知道它同时具有PropertyB和PropertyC。 It has to take in account some hidden derived types and all supertypes. 它必须考虑一些隐藏的派生类型和所有超类型。 Now if enity framework can be more sure about your class hierarchy (by making classes sealed) it can simplify the logic here. 现在,如果enity框架可以更加确定您的类层次结构(通过将类密封),则可以在这里简化逻辑。 And in my experience the string building logic EF generates can be even more complex than then ones you're showing here. 以我的经验,EF生成的字符串构建逻辑可能比您在此处显示的还要复杂。 That is why making classes abstract/sealed EF can be smarter about this and reduce your queries. 这就是为什么使类成为抽象/密封的EF可以对此更精明并减少查询的原因。

Another performance tip 另一个性能提示

Now also make sure you have proper indexes on the discriminator column. 现在,还要确保在“鉴别符”列上具有正确的索引。 (You could do this from your DbMigration script inside entity framework). (您可以从实体框架内的DbMigration脚本中执行此操作)。

'Desparate' performance measure “绝望的”绩效指标

Now if all else fails make your discriminator an int. 现在,如果其他所有方法都失败,则将您的鉴别符设为int。 This will hurt the readability of your database/queries a LOT, but it helps performance. 这会损害数据库的可读性/查询很多,但有助于提高性能。 (and you could even have all your classes automatically emit a property that contains the class name so you keep some readability of types inside your database). (甚至可以让所有类自动发出包含类名称的属性,以便使数据库内部的类型保持一定的可读性)。

UPDATE: 更新:

after some more research after the comment from RX_DID_RX it turns out you can only seal/make poco's abstract if you don't use dynamic proxy generation. 经过更多研究后,RX_DID_RX的注释表明,如果不使用动态代理生成,则只能密封/制作poco的摘要。 (lazy loading & change tracking). (延迟加载和更改跟踪)。 In my particular app we didn't use this so it worked well for us but i have to revert my earlier recommendation. 在我的特定应用程序中,我们没有使用它,因此它对我们来说效果很好,但是我必须恢复我以前的建议。

For more detail an EF6 specific link http://www.entityframeworktutorial.net/Types-of-Entities.aspx 有关更多详细信息, 请参见 EF6特定链接http://www.entityframeworktutorial.net/Types-of-Entities.aspx

adding indexes, and playing with casting in linq queries can still help though. 不过,添加索引以及在linq查询中进行强制转换仍然可以提供帮助。

From Batavia answer about queries: "Now if they have specific performance concerns you SHOULD address those" and don't waste time with other queries. 来自Batavia的关于查询的答案:“现在,如果它们有特定的性能问题,您应该解决这些问题”,不要浪费时间在其他查询上。 And also, don't waste time to understand why EF generates a query (if you trace LINQ queries with Include you will be negatively impressed about the generated queries). 而且,不要浪费时间来理解EF为什么生成查询(如果您使用Include跟踪LINQ查询,您会对生成的查询印象深刻)。
Other queries you need to address is query that are not compatible with your EF Provider (like queries with CROSS JOINs that sometimes EF generate). 您需要解决的其他查询是与EF提供程序不兼容的查询(例如有时使用EF生成的带有CROSS JOIN的查询)。

About performance in SQL statements (in DML you can find several other questions also on stackoverflow): 关于SQL语句中的性能(在DML中,您还可以在stackoverflow上找到其他几个问题):
- if you want you can use stored procedures; -如果需要,可以使用存储过程;
- there is a missing feature in EF. -EF中缺少一项功能。 You can't run a SQL query and map it to a class using the mapping defined in EF. 您无法使用EF中定义的映射来运行SQL查询并将其映射到类。 You can find an implementation here Entity framework Code First - configure mapping for SqlQuery (actually it could need some fix to work with TPH). 您可以在此处找到一个实现实体框架代码优先-为SqlQuery配置映射 (实际上,它可能需要一些修复才能与TPH配合使用)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM