Comment puis-je rechercher des valeurs nulles dans le cadre d'entité?

109

Je veux exécuter une requête comme celle-ci

   var result = from entry in table
                     where entry.something == null
                     select entry;

et obtenez un IS NULLfichier généré.

Modifié: Après les deux premières réponses, je ressens le besoin de préciser que j'utilise Entity Framework et non Linq to SQL. La méthode object.Equals () ne semble pas fonctionner dans EF.

Edit n ° 2: La requête ci-dessus fonctionne comme prévu. Il génère correctement IS NULL. Mon code de production était cependant

value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

et le SQL généré était something = @p; @p = NULL. Il semble que EF traduit correctement l'expression constante, mais si une variable est impliquée, il la traite comme une comparaison normale. Cela a du sens en fait. Je clore cette question

Adrian Zanescu
la source
17
Je pense que cela n'a pas vraiment de sens ... Le connecteur doit être un peu intelligent et ne pas nous demander de faire son travail: effectuer une traduction correcte en SQL de la requête C # correcte. Cela génère un comportement inattendu.
Julien N
6
Je suis avec Julien, c'est un échec de la part d'EF
Mr Bell
1
Il s'agit d'un échec des normes, et cela ne fait que s'aggraver maintenant que la comparaison avec null entraîne de manière permanente une indéfinie à partir de SQL Server 2016 avec les ANSI NULL activés de manière permanente. Null peut représenter une valeur inconnue, mais "null" lui-même n'est pas une valeur inconnue. La comparaison d'une valeur nulle avec une valeur nulle devrait absolument donner true, mais malheureusement, la norme s'écarte du bon sens ainsi que de la logique booléenne.
Triynko

Réponses:

126

Solution de contournement pour Linq-to-SQL:

var result = from entry in table
             where entry.something.Equals(value)
             select entry;

Solution de contournement pour Linq-to-Entities (aïe!):

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

C'est un vilain bogue qui m'a mordu plusieurs fois. Si ce bogue vous a également affecté, consultez le rapport de bogue sur UserVoice et faites savoir à Microsoft que ce bogue vous a également affecté.


Edit: Ce bogue est en cours de correction dans EF 4.5 ! Merci à tous d'avoir voté pour ce bogue!

Pour la compatibilité ascendante, ce sera opt-in - vous devez activer manuellement un paramètre pour entry == valuefonctionner. Aucun mot pour le moment sur ce paramètre. Restez à l'écoute!


Edit 2: Selon cet article de l'équipe EF, ce problème a été corrigé dans EF6! Woohoo!

Nous avons modifié le comportement par défaut d'EF6 pour compenser la logique à trois valeurs.

Cela signifie que le code existant qui repose sur l'ancien comportement ( null != nullmais uniquement lors de la comparaison à une variable) devra soit être modifié pour ne pas s'appuyer sur ce comportement, soit défini UseCSharpNullComparisonBehaviorsur false pour utiliser l'ancien comportement cassé.

BlueRaja - Danny Pflughoeft
la source
6
J'ai voté pour le rapport de bogue. Espérons qu'ils résolvent ce problème. Je ne peux pas dire que je me souviens vraiment de ce bogue présent dans la version bêta de vs2010 ...
noobish
2
oh allez sur microsoft ... vraiment?!?!? Dans la version 4.1?!?! +1
David
1
Cette solution de contournement Linq-To-SQL ne semble pas fonctionner (essayer avec un Guid?). L'utilisation de la solution de contournement Entities fonctionne dans L2S, mais génère un SQL horrible. J'ai dû faire une instruction if dans le code(var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null);
Michael Stum
5
Object.Equals fonctionne réellement(where Object.Equals(entry.something,value))
Michael Stum
5
@ leen3o (ou quelqu'un d'autre) - Quelqu'un a-t-il encore trouvé où se trouve ce correctif présumé dans EF 4.5 / 5.0? J'utilise 5.0 et il se comporte toujours mal.
Shaul Behr
17

Depuis Entity Framework 5.0, vous pouvez utiliser le code suivant pour résoudre votre problème:

public abstract class YourContext : DbContext
{
  public YourContext()
  {
    (this as IObjectContextAdapter).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;
  }
}

Cela devrait résoudre vos problèmes car Entity Framerwork utilisera la comparaison nulle «C # like».

ITmeze
la source
16

Il existe une solution de contournement légèrement plus simple qui fonctionne avec LINQ to Entities:

var result = from entry in table
         where entry.something == value || (value == null && entry.something == null)
         select entry;

Cela fonctionne parce que, comme AZ l'a remarqué, LINQ to Entities des cas spéciaux x == null (c'est-à-dire une comparaison d'égalité avec la constante nulle) et le traduit en x IS NULL.

Nous envisageons actuellement de changer ce comportement pour introduire automatiquement les comparaisons de compensation si les deux côtés de l'égalité sont nullables. Il y a cependant quelques défis:

  1. Cela pourrait potentiellement interrompre le code qui dépend déjà du comportement existant.
  2. La nouvelle traduction peut affecter les performances des requêtes existantes même lorsqu'un paramètre nul est rarement utilisé.

Dans tous les cas, la question de savoir si nous y parvenons dépendra grandement de la priorité relative que nos clients lui attribueront. Si vous vous souciez du problème, je vous encourage à voter pour celui-ci sur notre nouveau site de suggestions de fonctionnalités: https://data.uservoice.com .

plonger
la source
9

S'il s'agit d'un type Nullable, essayez peut-être d'utiliser la propriété HasValue?

var result = from entry in table
                 where !entry.something.HasValue
                 select entry;

Cependant, je n'ai pas d'EF à tester ici ... juste une suggestion =)

Svish
la source
1
Eh bien ... cela ne fonctionne que si vous recherchez simplement des valeurs nulles, mais que l'utilisation == nullne soit pas touchée par le bogue de toute façon. Le point est de filtrer par la valeur d'une variable, dont la valeur peut être nulle, et que la valeur nulle trouve les enregistrements nuls.
Dave Cousineau
1
Votre réponse m'a sauvé. J'ai oublié d'utiliser un type Nullable sur ma classe de modèle d'entité et je n'ai pas pu le faire (x => x.Column == null)fonctionner. :)
Reuel Ribeiro
Cela donne System.NullReferenceException , puisque l'objet est déjà nul!
TiyebM le
5

pour gérer les comparaisons nulles utiliser Object.Equals()au lieu de==

vérifier cette référence

Oscar Cabrero
la source
Cela fonctionne parfaitement dans Linq-To-Sql et génère également le SQL approprié (certaines autres réponses ici génèrent un SQL horrible ou des résultats erronés).
Michael Stum
Supposons que je veux Compairé avec null, Object.Equals(null), si le Objectlui - même est nul?
TiyebM le
4

Soulignant que toutes les suggestions Entity Framework <6.0 génèrent du SQL gênant. Voir le deuxième exemple pour un correctif "propre".

Solution de contournement ridicule

// comparing against this...
Foo item = ...

return DataModel.Foos.FirstOrDefault(o =>
    o.ProductID == item.ProductID
    // ridiculous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948
    && item.ProductStyleID.HasValue ? o.ProductStyleID == item.ProductStyleID : o.ProductStyleID == null
    && item.MountingID.HasValue ? o.MountingID == item.MountingID : o.MountingID == null
    && item.FrameID.HasValue ? o.FrameID == item.FrameID : o.FrameID == null
    && o.Width == w
    && o.Height == h
    );

résultats en SQL comme:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
       [Extent1].[Name]               AS [Name],
       [Extent1].[DisplayName]        AS [DisplayName],
       [Extent1].[ProductID]          AS [ProductID],
       [Extent1].[ProductStyleID]     AS [ProductStyleID],
       [Extent1].[MountingID]         AS [MountingID],
       [Extent1].[Width]              AS [Width],
       [Extent1].[Height]             AS [Height],
       [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  (CASE
  WHEN (([Extent1].[ProductID] = 1 /* @p__linq__0 */)
        AND (NULL /* @p__linq__1 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[ProductStyleID] = NULL /* @p__linq__2 */) THEN cast(1 as bit)
      WHEN ([Extent1].[ProductStyleID] <> NULL /* @p__linq__2 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[ProductStyleID] IS NULL)
        AND (2 /* @p__linq__3 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[MountingID] = 2 /* @p__linq__4 */) THEN cast(1 as bit)
      WHEN ([Extent1].[MountingID] <> 2 /* @p__linq__4 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[MountingID] IS NULL)
        AND (NULL /* @p__linq__5 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[FrameID] = NULL /* @p__linq__6 */) THEN cast(1 as bit)
      WHEN ([Extent1].[FrameID] <> NULL /* @p__linq__6 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[FrameID] IS NULL)
        AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
        AND ([Extent1].[Height] = 16 /* @p__linq__8 */)) THEN cast(1 as bit)
  WHEN (NOT (([Extent1].[FrameID] IS NULL)
             AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
             AND ([Extent1].[Height] = 16 /* @p__linq__8 */))) THEN cast(0 as bit)
END) = 1

Solution de contournement scandaleuse

Si vous souhaitez générer du SQL plus propre, quelque chose comme:

// outrageous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948
Expression<Func<Foo, bool>> filterProductStyle, filterMounting, filterFrame;
if(item.ProductStyleID.HasValue) filterProductStyle = o => o.ProductStyleID == item.ProductStyleID;
else filterProductStyle = o => o.ProductStyleID == null;

if (item.MountingID.HasValue) filterMounting = o => o.MountingID == item.MountingID;
else filterMounting = o => o.MountingID == null;

if (item.FrameID.HasValue) filterFrame = o => o.FrameID == item.FrameID;
else filterFrame = o => o.FrameID == null;

return DataModel.Foos.Where(o =>
    o.ProductID == item.ProductID
    && o.Width == w
    && o.Height == h
    )
    // continue the outrageous workaround for proper sql
    .Where(filterProductStyle)
    .Where(filterMounting)
    .Where(filterFrame)
    .FirstOrDefault()
    ;

donne ce que vous vouliez en premier lieu:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
           [Extent1].[Name]               AS [Name],
           [Extent1].[DisplayName]        AS [DisplayName],
           [Extent1].[ProductID]          AS [ProductID],
           [Extent1].[ProductStyleID]     AS [ProductStyleID],
           [Extent1].[MountingID]         AS [MountingID],
           [Extent1].[Width]              AS [Width],
           [Extent1].[Height]             AS [Height],
           [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  ([Extent1].[ProductID] = 1 /* @p__linq__0 */)
   AND ([Extent1].[Width] = 16 /* @p__linq__1 */)
   AND ([Extent1].[Height] = 20 /* @p__linq__2 */)
   AND ([Extent1].[ProductStyleID] IS NULL)
   AND ([Extent1].[MountingID] = 2 /* @p__linq__3 */)
   AND ([Extent1].[FrameID] IS NULL)
drzaus
la source
Le code exécuté sur le SQL sera plus propre et plus rapide, mais EF générera et mettra en cache un nouveau plan de requête pour chaque combinaison avant de l'envoyer au serveur SQL, ce qui le rend plus lent que les autres solutions de contournement.
Burak Tamtürk
2
var result = from entry in table
                     where entry.something == null
                     select entry;

La requête ci-dessus fonctionne comme prévu. Il génère correctement IS NULL. Mon code de production était cependant

var value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

et le SQL généré était quelque chose = @p; @p = NULL. Il semble que EF traduit correctement l'expression constante, mais si une variable est impliquée, il la traite comme une comparaison normale. Cela a du sens en fait.

Adrian Zanescu
la source
1

Il semble que Linq2Sql ait également ce "problème". Il semble qu'il y ait une raison valable à ce comportement en raison du fait que les ANSI NULL sont activés ou désactivés, mais il est stupéfiant de savoir pourquoi un simple "== null" fonctionnera en fait comme prévu.

JasonCoder
la source
1

Personnellement, je préfère:

var result = from entry in table    
             where (entry.something??0)==(value??0)                    
              select entry;

plus de

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

car cela empêche la répétition - bien que ce ne soit pas mathématiquement exact, mais cela convient bien à la plupart des cas.

Vincent Courcelle
la source
0

Je ne suis pas en mesure de commenter l'article de divega, mais parmi les différentes solutions présentées ici, la solution de divega produit le meilleur SQL. À la fois en termes de performances et de longueur. Je viens de vérifier avec SQL Server Profiler et en regardant le plan d'exécution (avec "SET STATISTICS PROFILE ON").

Buginator
la source
0

Malheureusement, dans Entity Framework 5 DbContext, le problème n'est toujours pas résolu.

J'ai utilisé cette solution de contournement (fonctionne avec MSSQL 2012 mais le paramètre ANSI NULLS peut être obsolète dans toute version future de MSSQL).

public class Context : DbContext
{

    public Context()
        : base("name=Context")
    {
        this.Database.Connection.StateChange += Connection_StateChange;
    }

    void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
    {
        // Set ANSI_NULLS OFF when any connection is opened. This is needed because of a bug in Entity Framework
        // that is not fixed in EF 5 when using DbContext.
        if (e.CurrentState == System.Data.ConnectionState.Open)
        {
            var connection = (System.Data.Common.DbConnection)sender;
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "SET ANSI_NULLS OFF";
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Il convient de noter que c'est une solution de contournement sale mais qui peut être implémentée très rapidement et qui fonctionne pour toutes les requêtes.

Knaģis
la source
Cela cessera immédiatement de fonctionner une fois que ANSI NULLS est définitivement défini sur ON dans une future version de SQL Server, au cas où l'avertissement ne serait pas clair.
Triynko du
0

Si vous préférez utiliser la syntaxe de méthode (lambda) comme je le fais, vous pouvez faire la même chose comme ceci:

var result = new TableName();

using(var db = new EFObjectContext)
{
    var query = db.TableName;

    query = value1 == null 
        ? query.Where(tbl => tbl.entry1 == null) 
        : query.Where(tbl => tbl.entry1 == value1);

    query = value2 == null 
        ? query.Where(tbl => tbl.entry2 == null) 
        : query.Where(tbl => tbl.entry2 == value2);

    result = query
        .Select(tbl => tbl)
        .FirstOrDefault();

   // Inspect the value of the trace variable below to see the sql generated by EF
   var trace = ((ObjectQuery<REF_EQUIPMENT>) query).ToTraceString();

}

return result;
John Meyer
la source
-1
var result = from entry in table    
             where entry.something == value||entry.something == null                   
              select entry;

Utiliser ça

Andrew
la source
5
C'est TRÈS faux car il sélectionnera toutes les entrées où la valeur correspond ET toutes les entrées où quelque chose est nul, même si vous demandez une valeur.
Michael Stum