Perché EF genera query SQL con controlli null inutili?

Mi sono imbattuto in un problema con EF che creava query terribili durante la ricerca su un campo di stringa. Ha prodotto una query nello stile di programmatori pigri per comprendere il controllo nullo che forza la scansione dell’intero indice.

considera le seguenti domande.

  1. Query 1

    var p1 = "x"; var r1 = ctx.Set().FirstOrDefault( subject => p1.Equals(subject.StringField)); 
  2. Query 2

     const string p2 = "x"; var r2 = ctx.Set().FirstOrDefault( subject => p2.Equals(subject.StringField)); 

Query 1 produce

 WHERE (('x' = [Extent2].[StringField]) OR (('x' IS NULL) AND ([Extent2].[StringField] IS NULL))) 

ed esegue in 4 secondi

Query 2 produce

 WHERE (N'x' = [Extent2].[StringField]) 

ed esegue in 2 millisecondi

Qualcuno sa di un lavoro in giro? (no il parametro non può essere un const in quanto è immesso dall’input dell’utente ma non può essere nullo).

NB Quando vengono profilati, entrambe le query vengono preparate con sp_executesql da EF; in quanto, se sono stati appena eseguiti, Query Optimiser annullerebbe il controllo OR ‘x’ IS NULL.

per @Martin

Imposta UseDatabaseNullSemantics = true;

Ottiene o imposta un valore che indica se la semantica null del database viene esibita quando si confrontano due operandi, entrambi potenzialmente annullabili. Il valore predefinito è falso. Ad esempio (operando1 == operando2) sarà tradotto come: (operando1 = operando2) se UseDatabaseNullSemantics è true, rispettivamente (((operando1 = operando2) AND (NOT (operando1 È NULLO OP operando2 È NULL))) (OR operativo1 NULL) E (operando 2 È NULL))) se UseDatabaseNullSemantics è falso.

https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbcontextconfiguration.usedatabasenullsemantics(v=vs.113).aspx

 public class MyContext : DbContext { public MyContext() { this.Configuration.UseDatabaseNullSemantics = true; } } 

Puoi anche impostare questa impostazione sull’istanza dbContext dall’esterno come nell’esempio di codice seguente, dal mio punto di vista (vedi il commento @GertArnold), questo apporach sarà migliore, perché non cambierà il comportamento o la configurazione del database predefinito):

 myDbContext.Configuration.UseDatabaseNullSemantics = true; 

È ansible risolvere questo problema aggiungendo [Required] sulla proprietà StringField

 public class Test { [Key] public int Id { get; set; } [Required] public string Bar{ get; set; } public string Foo { get; set; } } string p1 = "x"; var query1 = new Context().Tests.Where(F => p1.Equals(F.Bar)); var query2 = new Context().Tests.Where(F => p1.Equals(F.Foo)); 

questa è query1

{SELEZIONA [Extent1]. [Id] AS [Id], [Extent1]. [Bar] AS [Bar], [Extent1]. [Foo] AS [Foo] FROM [dbo]. [Test] AS [Extent1] WHERE @ p__linq__0 = [Extent1]. [Bar]}

e questo è query2

{SELEZIONA [Extent1]. [Id] AS [Id], [Extent1]. [Bar] AS [Bar], [Extent1]. [Foo] AS [Foo] FROM [dbo]. [Test] AS [Extent1] WHERE (@ p__linq__0 = [Extent1]. [Foo]) OR ((@ p__linq__0 È NULL) AND ([Extent1]. [Bar2] IS NULL))}}

Un mio collega ha appena trovato una soluzione davvero bella. Da quando ho già scoperto che l’uso delle costanti produce l’SQL corretto. Ci siamo domandati se potessimo scambiare le variabili nell’espressione con le costanti; e come risulta è ansible. Credo che questo metodo sia meno invasivo rispetto alla modifica delle impostazioni nulle sul contesto DB.

 public class Foo_test : EntityContextIntegrationSpec { private static string _foo = null; private static DataConnection _result; private Because _of = () => _result = EntityContext.Set().Where(StringMatch(x => x.StringField)); private static Expression> StringMatch(Expression> prop) { var body = Expression.Equal(prop.Body, Expression.Constant(_foo)); return Expression.Lambda>(body, prop.Parameters[0]); } [Test] public void Test() => _result.ShouldNotBeNull(); }