2015-11-24 13 views
14

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.

+0

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 –

+0

, ma perché ho questo problema solo con le enumerazioni? Se rimuovo tutte le enumerazioni, l'SQL è sempre pulito anche con grandi gerarchie – Cybermaxs

+0

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 –

risposta

2

Informazioni sull'uso EF/query di grandi dimensioni

Ho fatto qualche lavoro con EF6 e semi-grandi gerarchie. Ci sono alcune cose che dovresti prendere in considerazione. Innanzitutto perché il tuo team di DBA non è soddisfatto di questo tipo di domande. Naturalmente queste non sono le query che scriverebbero, ma assumendo che la gestione non voglia spendere il tempo necessario per scrivere ogni singola query da zero, dovranno convivere con il fatto che si utilizza un framework ORM e che il framework ORM potrebbe causare query che sono un po 'più grandi.

Ora se hanno problemi di prestazioni specifici, DOVREBBE rivolgersi a quelli.

Cosa si può fare

ora cosa si può fare per ripulire le vostre domande.

1) Creare tutte le classi che potrebbero essere astratte.

2) Rendere sigillate tutte le altre classi.

3) Nelle query di linq eseguite su tipi concreti ove possibile (utilizzando OfType()). Questo potrebbe anche funzionare meglio di un .Select (x => x come SomethingHere). Se si dispone di una query particolarmente spiacevole, potrebbero essere necessari alcuni esperimenti su cosa ottimizza la query da linq.

spiegazione quello che ho trovato attraverso la sperimentazione

Come si nota con le vostre domande è controllo discriminatore. Se le query diventano un po 'più complesse (e mi aspetto che quelle query da 50k siano una di quelle), vedrete che aggiunge codice per la concatenazione delle stringhe per verificare ogni possibile combinazione. vedete che succede un po 'nello

parte. Ho fatto alcuni POC cercando di capire questo comportamento e quello che sembra accadere è che il framework di entità sta traducendo le proprietà in "aspetti" (il mio termine). Ad esempio, una classe avrà una "ProprietàA" se la stringa tradotta contiene "0X" o "0X0X". PropertyB potrebbe tradurre in "R2D2" e PropertyC in "C3P0". in questo modo se un nome di classe viene tradotto in "R2D2C3P0". sa che ha sia PropertyB & PropertyC. Deve prendere in considerazione alcuni tipi derivati ​​nascosti e tutti i supertipi. Ora se il framework enity può essere più sicuro della tua gerarchia di classi (rendendo le classi sigillate) può semplificare qui la logica. E nella mia esperienza la logica di creazione di stringhe EF genera può essere anche più complessa di quelle che mostri qui. Ecco perché rendere le classi astratte/sigillate EF può essere più intelligente e ridurre le tue domande.

Un altro suggerimento prestazioni

Ora anche fare in modo di avere gli indici corretti sulla colonna discriminatore. (Puoi farlo dal tuo script DbMigration all'interno del framework di entità).

'Desparate' misura di performance

Ora, se tutto il resto fallisce rendere il vostro discriminatore un int. Ciò danneggerà la leggibilità del tuo database/query un sacco, ma aiuta le prestazioni. (e potresti persino avere tutte le tue classi emettere automaticamente una proprietà che contiene il nome della classe in modo da mantenere una certa leggibilità dei tipi all'interno del tuo database).

UPDATE:

dopo qualche ricerca più dopo il commento da RX_DID_RX si scopre si può solo sigillare/rendere poco astratto se non si utilizza la generazione di proxy dinamica. (pigro caricamento & modifica rilevamento). Nella mia app particolare non abbiamo usato questo, quindi ha funzionato bene per noi, ma devo ripristinare la mia precedente raccomandazione.

Per ulteriori dettagli di un collegamento specifico EF6 http://www.entityframeworktutorial.net/Types-of-Entities.aspx

l'aggiunta di indici, e giocando con getto in query LINQ può ancora aiutare però.

+0

Apprezzo molto il commento ... ma non sembra essere focalizzato sul mio problema (enum con TPH) – Cybermaxs

+0

Il problema è che EF6 genera query troppo grandi secondo il tuo DBA. Io mostro un modo in cui EF può semplificare le tue query (con l'osservazione che ritengo che il tuo DBA non dovrebbe lamentarsi delle dimensioni della query a patto che non faccia cadere il server in termini di prestazioni). AFAIK questo dovrebbe aiutare i tuoi problemi con e senza enumerazioni - e fai notare notare che accade la stessa cosa nei livelli di gerarchia 2 vs 3 – Batavia

+0

"Rendi le classi astratte/sigillate" - non conosci molto di EF 6, ma torna in EF 4 era una cattiva idea Vedi: https://msdn.microsoft.com/library/dd468057(v=vs.100).aspx –

0

Da Batavia risposta a domande: "Ora se hanno problemi di prestazioni specifici, DOVREBBE rivolgersi a quelli" e non perdere tempo con altre domande. Inoltre, non perdere tempo a capire perché EF genera una query (se si tracciano query LINQ con Includi, si avrà un impatto negativo sulle query generate).
Altre query che è necessario indirizzare sono query che non sono compatibili con il provider EF (come le query con CROSS JOIN che a volte EF genera).

Informazioni sulle prestazioni nelle istruzioni SQL (in DML è possibile trovare anche altre domande sullo stackoverflow):
- se si desidera è possibile utilizzare stored procedure;
- c'è una caratteristica mancante in EF. Non è possibile eseguire una query SQL e associarla a una classe utilizzando la mappatura definita in EF. È possibile trovare un'implementazione qui Entity framework Code First - configure mapping for SqlQuery (in realtà potrebbe essere necessario qualche correzione per lavorare con TPH).