gpt4 book ai didi

c# - 带有 TPH 和枚举的 Entity Framework 中的多个 CASE WHEN

转载 作者:可可西里 更新时间:2023-11-01 07:59:01 25 4
gpt4 key购买 nike

在 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 WHEN

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 as 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

一个常见的解决方案是为整数值创建一个单独的属性并忽略枚举属性。这行得通,但为了同一目的而拥有 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/Large 查询

我已经完成了一些 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 试图找出这种行为,似乎正在发生的是 Entity Framework 正在将属性转换为“方面”(我的术语)。例如,如果翻译后的字符串包含“0X”或“0X0X”,则类将具有“PropertyA”。 PropertyB 可能会翻译成“R2D2”,PropertyC 可能会翻译成“C3P0”。这样,如果一个类名被翻译成“R2D2C3P0”。它知道它同时拥有 PropertyB 和 PropertyC。它必须考虑一些隐藏的派生类型和所有父类(super class)型。现在,如果 Entity Framework 可以更加确定您的类层次结构(通过密封类),它可以简化这里的逻辑。根据我的经验,EF 生成的字符串构建逻辑可能比您在此处显示的逻辑更复杂。这就是为什么使类抽象/密封 EF 可以更聪明地解决这个问题并减少您的查询。

另一个性能提示

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

“绝望”绩效指标

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

更新:

在 RX_DID_RX 的评论之后进行更多研究后发现,如果不使用动态代理生成,则只能密封/制作 poco 的摘要。 (延迟加载和更改跟踪)。在我的特定应用中,我们没有使用它,所以它对我们来说效果很好,但我必须撤消我之前的建议。

有关 EF6 特定链接的更多详细信息 http://www.entityframeworktutorial.net/Types-of-Entities.aspx

不过,添加索引并在 linq 查询中使用转换仍然有帮助。

关于c# - 带有 TPH 和枚举的 Entity Framework 中的多个 CASE WHEN,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33900031/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com