Cyb*_*axs 14 .net c# 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; }
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();
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码完美无缺,但生成的查询非常奇怪而复杂,特别是有很多情况下的情况.
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]
Run Code Online (Sandbox Code Playgroud)
除了多个无用的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])
Run Code Online (Sandbox Code Playgroud)
更新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; }
}
}
Run Code Online (Sandbox Code Playgroud)
更新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'
Run Code Online (Sandbox Code Playgroud)
部分。我做了一些 POC 试图找出这种行为,似乎正在发生的事情是实体框架正在将属性转换为“方面”(我的术语)。例如,如果翻译的字符串包含“0X”或“0X0X”,则类将具有“PropertyA”。PropertyB 可能会翻译为“R2D2”,PropertyC 可能会翻译为“C3P0”。这样,如果类名被翻译为“R2D2C3P0”。它知道它同时拥有PropertyB 和PropertyC。它必须考虑一些隐藏的派生类型和所有超类型。现在,如果实体框架可以更加确定您的类层次结构(通过使类密封),它可以简化此处的逻辑。根据我的经验,EF 生成的字符串构建逻辑可能比您在此处显示的更为复杂。这就是为什么使类抽象/密封 EF 可以更智能地处理此问题并减少查询。
另一个性能提示
现在还要确保鉴别器列上有正确的索引。(您可以从实体框架内的 DbMigration 脚本执行此操作)。
“绝望”的绩效衡量标准
现在,如果一切都失败了,请让你的鉴别器成为一个 int 。这会严重损害数据库/查询的可读性,但它有助于提高性能。(您甚至可以让所有类自动发出一个包含类名的属性,以便您在数据库中保持类型的一些可读性)。
更新:
经过 RX_DID_RX 的评论后进行了更多研究,结果表明,如果您不使用动态代理生成,则只能密封/制作 poco 的摘要。(延迟加载和更改跟踪)。在我的特定应用程序中,我们没有使用它,因此它对我们来说效果很好,但我必须恢复我之前的建议。
有关 EF6 特定链接的更多详细信息,请访问http://www.entityframeworktutorial.net/Types-of-Entities.aspx
添加索引,以及在 linq 查询中进行转换仍然有帮助。
| 归档时间: |
|
| 查看次数: |
1456 次 |
| 最近记录: |