[英]Entity framework inheritance TPH adding discriminator on every query
我有一个实体框架模型,该模型具有一个名为“ Call”的基类(抽象)和两个分别带有区分符“ Survo”和“ Voicemail”的子类,分别称为“ SurvoCall”和“ VoicemailCall”。
问题是,每当我对这些呼叫进行查询时,例如计算我们昨天收到的呼叫数量(无论是Survo还是Voicemail),实体框架都会在查询中添加以下“ where”子句:
...('Survo','Voicemail')中的鉴别符....
在我看来,这似乎没有必要,而且会损害性能,而没有任何好处。 因为如果我通过调用查询(不使用OfType <>),则是在告诉我要进行每个调用,不需要检查鉴别符,但是实体框架仍然添加了“输入”条件。
有没有办法解决? 因为它影响了我们的某些查询...
谢谢!
您可以随时在此处查看Entity Framework的来源以找到答案: https : //entityframework.codeplex.com/
我对此进行了一些调试和研究,由于以下原因,您似乎无法摆脱[Dicriminator] IN('X1','X2',....,'Xn')至少对于实体框架6,视图生成和实现。
在为该特定表(为您呼叫)实际生成SELECT语句之前,它会评估该表具有的所有列以及可以从Call派生的所有可能的类型。 在您的情况下,有2种类型-SurvoCall和VoicemailCall。 为了获得这两个实体的数据,它需要2个不同的查询,因为在此特定表中它们很可能具有不同的列集,因此从技术上讲,它会在此处生成2个SELECTS:
SELECT Id, PhoneNumber, Duration, etc.., SurvoResult FROM [Call] where [Discriminator] = 'Survo'
和
SELECT Id, PhoneNumber, Duration, etc.., VoicemailDuration FROM [Call] where [Discriminator] = 'Voicemail'
。
经过一些内部优化后,它将“ 2个” SQL查询“合并”为您拥有的一个,并将具体的类型选择委托给实现器:
SELECT Id, PhoneNumber, Duration, etc.., SurvoResult, VoicemailDuration FROM [Call] where [Discriminator] IN ('Survo', 'Voicemail')
。
它实际上看起来像是Entity Framework内部架构方法,用于在已区分的表上建立查询-为每个区分器建立1个查询,然后进行合并,结果得到此IN
子句。 这背后的原因是什么? 可能您需要深入研究Entity Framework源代码,或者询问正在开发这部分代码的人员。
从概念上讲,此解决方案是有道理的-它限制了您的查询遍历Entity Framework知道的数据,并且知道如何创建具体类型的数据,因此,如果Discriminator列值不正确-可以的-应用程序不会损坏。 但是我不认为这是主要原因。
需要注意的另一件有趣的事情-您可能具有实体的多层结构。 这是给你的一个例子:
[Table("ProductOrService")]
public abstract class ProductOrService
{
public int Id { get; set; }
public DateTime AddedToCartAt { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
[Table("ProductOrService")]
public class Level1Product : ProductOrService
{
public string Owner { get; set; }
}
[Table("ProductOrService")]
public class Level1Service : ProductOrService
{
public string Activator { get; set; }
}
[Table("ProductOrService")]
public abstract class Level2ProductOrService : ProductOrService
{
public string SomeProp { get; set; }
}
[Table("ProductOrService")]
public class Level2Product : Level2ProductOrService
{
public DateTime IssuedAt { get; set; }
}
[Table("ProductOrService")]
public class Level2Service : Level2ProductOrService
{
public DateTime ExpiresAt { get; set; }
}
在这个例子中,我们有两个层次的层次结构:
ProductOrService (abstract)
->Level1Product
->Level1Service
->Level2ProductOrService (abstract)
->->Level2Product
->->Level2Service
您可能可以使用dbSets访问任何层次结构:
public class TestDbContext : DbContext
{
public IDbSet<ProductOrService> ProductsOrServices { get; set; }
public IDbSet<Level2ProductOrService> Level2ProductsOrServices { get; set; }
public IDbSet<Level1Product> Level1Products { get; set; }
public IDbSet<Level2Service> Level2Services { get; set; }
}
之后,调用这些dbSet会得到非常有趣的结果:
var level1Root = context.ProductsOrServices
.Where(x=>x.Price > 0);
//generates query with all possible discriminators:
{SELECT
...
FROM [dbo].[ProductOrService] AS [Extent1]
WHERE ([Extent1].[Discriminator] IN (N'Level1Product',N'Level2Product',N'Level2Service',N'Level1Service')) AND ([Extent1].[Price] > cast(0 as decimal(18)))}
var level2Root = context.Level2ProductsOrServices
.Where(x => x.Price > 0);
//generates query with discriminators twice! first for root hierarchy, then for second level hierarchy!
{SELECT
...
FROM [dbo].[ProductOrService] AS [Extent1]
WHERE ([Extent1].[Discriminator] IN (N'Level1Product',N'Level2Product',N'Level2Service',N'Level1Service')) AND ([Extent1].[Discriminator] IN (N'Level2Product',N'Level2Service')) AND ([Extent1].[Price] > cast(0 as decimal(18)))}
var level1Products = context.Level1Products
.Where(x => x.Price > 0);
//generates correct query with 1 discriminator
{SELECT
'0X0X' AS [C1],
...
FROM [dbo].[ProductOrService] AS [Extent1]
WHERE ([Extent1].[Discriminator] = N'Level1Product') AND ([Extent1].[Price] > cast(0 as decimal(18)))}
var level2Services = context.Level2Services
.Where(x => x.Price > 0);
//generates correct query with 1 discriminator
{SELECT
'0X0X0X' AS [C1],
...
FROM [dbo].[ProductOrService] AS [Extent1]
WHERE ([Extent1].[Discriminator] = N'Level2Service') AND ([Extent1].[Price] > cast(0 as decimal(18)))}
如您所见,对于已区分对象表的基本类型的查询会使用IN
子句为查询对象所使用的每个层次结构级别(当前+所有基本类型)建立次优查询(如果查询具有派生类型)。
从用例来看,除非您加载的是带有区分符的对象的非常大的图,否则这些区分符的查询时间并没有真正遇到任何问题,但是在这些情况下,您实际上无法影响查询的构建过程。 当您的实体不是聚合根时,总体上使用“每个层次的表”会给您带来很多痛苦。 避免这种情况的可能方法是摆脱数据库处理的继承-创建一个具有所需所有列的实体,并在C#端由您自己处理。 实际上,这种方法也有缺点,并使代码更脏。 您可能可以在实际域模型和基于实体框架的持久性模型之间添加一些层,请查看以下文章: http : //www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model /
可悲的是,似乎没有一种将实体框架与继承一起使用的好方法。 但这是开源的,因此您可以尝试将其分叉并产生一个不错的解决方案:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.