.net d'entités - Comment puis-je demander des valeurs nulles dans le cadre d'entité?





nommées reconnaissance (13)


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é, les cas spéciaux LINQ to Entities x == null (c'est-à-dire une comparaison d'égalité avec la constante null) 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 casser le code qui dépend déjà du comportement existant.
  2. La nouvelle traduction peut affecter la performance des requêtes existantes même si un paramètre nul est rarement utilisé.

Dans tous les cas, le fait de travailler dessus dépendra beaucoup de la priorité relative que nos clients lui attribueront. Si vous vous souciez de ce problème, je vous encourage à voter pour ce dernier sur notre nouveau site de suggestion de fonctionnalités: https://data.uservoice.com .

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

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

et obtenir un IS NULL généré.

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

Modifier 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 something = @p; @p = NULL . Il semble que EF traduise correctement l'expression constante mais si une variable est impliquée, elle la traite comme une comparaison normale. Cela a du sens en réalité. Je vais fermer cette question




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 déconseillé dans toute future version 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 il est celui qui peut être mis en œuvre très rapidement et fonctionne pour toutes les requêtes.







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 traduise correctement l'expression constante mais si une variable est impliquée, elle la traite comme une comparaison normale. Cela a du sens en réalité.




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




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;

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




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 (ouch!):

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

C'est un insecte méchant qui m'a mordu plusieurs fois. Si ce bug vous a également affecté, consultez le rapport de bogue sur UserVoice et informez Microsoft que ce bogue vous a également affecté.

Edit: Ce bug est corrigé dans EF 4.5 ! Merci à tous pour avoir dérangé ce bug!

Pour la rétrocompatibilité, il s'agira d'une option d'activation - vous devez activer manuellement un paramètre pour que l' entry == value fonctionne. Aucun mot encore sur ce que ce paramètre est. Restez à l'écoute!

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

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

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




Si vous préférez utiliser la syntaxe de la 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;



Soulignant que toutes les suggestions d'Entity Framework <6.0 génèrent un SQL maladroit. Voir le deuxième exemple de correction "propre".

Solution ridicule

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

return DataModel.Foos.FirstOrDefault(o =>
    o.ProductID == item.ProductID
    // ridiculous < EF 4.5 nullable comparison workaround http://.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 dans 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 outrancière

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

// outrageous < EF 4.5 nullable comparison workaround http://.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()
    ;

aboutit à 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)



pour faire face aux comparaisons Null, utilisez Object.Equals() au lieu de ==

vérifier cette reference




Si c'est un type nullable, peut-être essayer d'utiliser la propriété HasValue?

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

N'ayez pas d'EF à tester ici ... juste une suggestion =)




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

Utiliser ça




foreach (Foos foo in Enum.GetValues(typeof(Foos)))
{
    ...
}




.net entity-framework ado.net