C# 在具有TPH和枚举的实体框架中时出现多个案例

C# 在具有TPH和枚举的实体框架中时出现多个案例,c#,.net,entity-framework,C#,.net,Entity Framework,在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; } pub

在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();
    }
}
除了多个和无用的THEN CAST(NULL为X)的代价外,在我的项目中查询很大(>50kbs),因为我有很多派生类,包含很多属性。正如您所料,我的DBA团队不喜欢看到这种对数据库的查询

如果我删除TypeB上的枚举属性,请求会更干净。如果我只有两个层次结构级别,即aka
classtypec:BaseType
(与示例中的3相比,因为
classtypec: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

一种常见的解决方案是创建一个单独的属性,即整数值,并忽略枚举属性。这是可行的,但如果有两个属性用于相同的目的,那就相当混乱了

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上发现了一个bug:。这似乎还没有解决。

关于使用EF/大型查询

我对EF6和半大型层次结构做了一些工作。有几件事你应该考虑。首先,为什么您的DBA团队对此类查询不满意。当然,这些查询并不是他们将要编写的查询,但是假设管理层不希望您花时间从头开始编写每个查询,他们将不得不接受这样一个事实,即您使用的是ORM框架,ORM框架可能会导致更大的查询

现在,如果他们有特定的性能问题,您应该解决这些问题

你能做什么

现在,您可以做些什么来清理您的查询

1) 使所有可以是抽象的类都成为抽象的

2) 将所有其他类密封起来

3) 在linq查询中,尽可能将其转换为具体类型(使用OfType()。这甚至可能比.Select(x=>x作为SomethingHere)更有效。如果您有一个特别讨厌的查询,那么可能需要进行一些实验,以确定从linq优化查询的最佳方式

解释我通过实验发现的内容

当你注意到你的查询时,它正在检查鉴别器。如果查询变得更复杂一些(我希望50k查询就是其中之一),您将看到它添加了用于字符串连接的代码,以检查每个可能的组合。你可以看到这种情况在未来发生了一些

THEN '0X' WHEN ([Extent1].[Discriminator] = N'TypeA') THEN '0X0X' 
部分。 我已经做了一些POC试图弄清楚这种行为,似乎正在发生的是实体框架正在将属性转换为“方面”(我的术语)。例如,如果翻译的字符串包含“0X”或“0X0X”,则类将具有“PropertyA”。PropertyB它可以翻译为“R2D2”,PropertyC可以翻译为“C3P0”。如果一个类名被翻译成“R2D2C3P0”,那么就是这样。它知道它同时拥有PropertyB和PropertyC。它必须考虑一些隐藏的派生类型和所有的超类型。现在,如果enity framework可以更确定您的类层次结构(通过密封类),那么它可以简化这里的逻辑。根据我的经验,EF生成的字符串构建逻辑可能比这里显示的还要复杂。这就是为什么让类抽象/密封EF可以更聪明地处理这一点,并减少您的查询

另一个性能提示

现在还要确保鉴别器列上有正确的索引。(您可以从实体框架内的DbMigration脚本执行此操作)

“消极怠工”绩效衡量标准

现在,如果所有其他方法都失败,请将您的鉴别器设置为int。这将严重影响数据库/查询的可读性,但有助于提高性能。(您甚至可以让所有类自动发出一个包含类名的属性,以便在数据库中保持某些类型的可读性)

更新:

在RX_Dod_RX的评论之后进行了更多的研究,结果表明,如果不使用动态代理生成,您只能密封/制作poco的摘要。(延迟加载和更改跟踪)。在我的特定应用程序中,我们没有使用它,因此它对我们很有效,但我必须回复我先前的建议

有关更多详细信息,请访问EF6特定链接


添加索引和在linq查询中使用casting仍然有帮助。

来自巴达维亚,回答有关查询的问题:“现在,如果他们有特定的性能问题,您应该解决这些问题”,不要浪费时间处理其他查询。另外,不要浪费时间去理解EF生成查询的原因(如果您使用Include跟踪LINQ查询,您将对生成的查询留下负面印象)。
您需要解决的其他查询是与EF提供程序不兼容的查询(如有时EF生成的交叉连接查询)

关于SQL语句中的性能(在DML中,您还可以找到有关stackoverflow的其他几个问题):
-如果需要,可以使用存储过程

-EF中缺少一个功能。不能运行SQL查询并使用EF中定义的映射将其映射到类。您可以在这里找到一个实现(实际上,它可能需要一些修复程序才能使用TPH)

就性能而言,EF不能很好地处理大型类层次结构,即使是在TPH的情况下(至少EF5是这样)。如果您计划创建巨大的类层次结构,我强烈建议您不要使用EF,但是为什么我只在枚举上有这个问题呢?如果我删除了所有枚举,那么即使有大的层次结构,SQL也总是干净的。对不起,我使用的上一个版本(EF 5)不支持枚举。我们无法将枚举存储为int。也许你应该试试类似的东西。尝试将枚举值存储为整数,并查看其影响performance@Cybermaxs你为什么不满足
THEN '0X' WHEN ([Extent1].[Discriminator] = N'TypeA') THEN '0X0X'