Ho un comportamento molto strano quando uso TPH su EF 6.1.3. Ecco un esempio di base per riprodurre:CASO multiplo QUANDO in Entity Framework con TPH ed enumerazione
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();
}
}
Questo codice funziona perfettamente, ma la query generata è extrememly strano e complesso, especialy ci sono un sacco di CASO QUANDO
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]
tranne il costo di multiplo e inutile THEN CAST (NULL as X), la query è grande (> 50 KB) nel mio progetto perché ho un sacco di classi derivate, contenenti molte proprietà. Come puoi immaginare, il mio team DBA non è felice di vedere questo tipo di query sui nostri database.
Se rimuovo la proprietà di enumerazione su TypeB, la richiesta è molto più pulita. Stessa cosa se ho solo due livelli di gerarchia, ovvero class TypeC : BaseType
(rispetto a 3 nell'esempio perché class TypeC : TypeB
).
Esiste qualche impostazione o configurazione del modello o soluzione alternativa per evitare questo strano comportamento?
Aggiorna
Ecco la query generata se rimuovere 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])
Aggiorna 2
Una soluzione comune è quella di creare una struttura separata e il valore intero ignorare la proprietà enum. Funziona, ma è abbastanza confuso avere 2 proprietà per lo stesso scopo.
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
Ho trovato un bug su CodePlex: https://entityframework.codeplex.com/workitem/2117. Non sembra essere risolto.
Per quanto riguarda le prestazioni, EF non funziona molto bene con grandi gerarchie di classi, anche nel caso di TPH (almeno questo era il caso di EF 5). Suggerisco fortemente di non usare EF se si sta pianificando di creare enormi gerarchie di classi –
, ma perché ho questo problema solo con le enumerazioni? Se rimuovo tutte le enumerazioni, l'SQL è sempre pulito anche con grandi gerarchie – Cybermaxs
Spiacente, l'ultima versione che ho usato (EF 5) non supportava le enumerazioni. Siamo rimasti bloccati a memorizzare enum come ints. Forse dovresti provare qualcosa di simile. Prova a memorizzare il tuo valore enum come numero intero e scopri come questo influisce sulle prestazioni –