【问题标题】:"Not In" in Entity Framework实体框架中的“不在”
【发布时间】:2015-11-12 09:39:29
【问题描述】:

我有以下实体

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

并有一个列表List<Person> badGuys

我想要做的是从除badGuys列表中的所有人之外的所有人中选择

我的代码

context.Persons
    .where(p => !badGuys.Contain(p))
    .ToList()

但我得到一个错误

这里只支持原始类型或枚举类型 上下文。

如何解决这个问题?

【问题讨论】:

    标签: c# entity-framework linq


    【解决方案1】:

    您可以创建一个包含坏人 id 的数组并过滤掉那些 id(它们是原始类型,所以它应该可以工作):

    var badGuyIds = badGuys.Select(x => x.PersonId).ToArray();
    
    context.Persons
        .Where(p => !badGuyIds.Contain(p.PersonId))
        .ToList();
    

    【讨论】:

    • 您可以通过像这样删除.ToArray(); 来提高效率:var badGuyIds = badGuys.Select(x => x.PersonId); 从而防止在badGuyId 上进行不必要的查询,因为Persons 上的第二个查询无论如何都会这样做...
    【解决方案2】:

    您可以使用带有 distinct(!=) 的子句 ALL

    var badBoys= from P in context.Persons
             where badGuys.All(a => a.PersonId!= P.PersonId)
             select P;
    

    【讨论】:

      【解决方案3】:

      您可以实现自己的方法来制作必要的表达式树,如下所示:

          public static IQueryable<TEntity> WhereNotIn<TEntity, TValue>(
              this IQueryable<TEntity> queryable,
              Expression<Func<TEntity, TValue>> valueSelector,
              IEnumerable<TValue> values)
              where TEntity : class
          {
              if (queryable == null)
                  throw new ArgumentNullException("queryable");
      
              if (valueSelector == null)
                  throw new ArgumentNullException("valueSelector");
      
              if (values == null)
                  throw new ArgumentNullException("values");
      
              if (!values.Any())
                  return queryable.Where(e => true);
      
              var parameterExpression = valueSelector.Parameters.Single();
      
              var equals = from value in values
                           select Expression.NotEqual(valueSelector.Body, Expression.Constant(value, typeof (TValue)));
      
              var body = equals.Aggregate(Expression.And);
      
              return queryable.Where(Expression.Lambda<Func<TEntity, bool>>(body, parameterExpression));
          }
      }
      

      现在你可以调用这个扩展方法了

      var badGuys = new int[] { 100, 200, 300 };
      context.Persons.WhereNotIn(p => p.PersionId, badGuys);
      

      这个方法和下面的一样:

      context.Persons.Where(p => p.PersonId != badGuys[0]
                              && p.PersonId != badGuys[1]
                              . . .
                              && p.PersonId != badGuys[N]);
      

      对于badGuys 可枚举对象的每个元素。

      该方法的另一个优点是它的通用性,因为您可以为任何实体的任何属性调用它,例如 f.e. context.Persons.WhereNotIn(p =&gt; p.LastName, new[] { "Smith", "Brown", "Jones" }).

      【讨论】:

      • 这是一个很好的解决方案,但在性能方面并不是一个好的解决方案。我宁愿使用 ANY 语句来提高性能。另外,EF 本身就支持它,为什么不采用这种方法呢?
      • @hbulens 如果 EF 在建议的上下文中不能处理 Contains,我不确定它是否可以处理 Any。但这是可能的。至于性能,我认为这两个查询在数据库级别会有相似的执行计划。
      • @MarkShevchenko 这很漂亮,但因此无论如何我都必须使用p.PersonId 进行过滤,您的解决方案和@W0lf 解决方案有什么区别?
      • @MohamedBadr 如我所见,没有更多的区别。
      • 那么使用您的方法生成的 SQL 就像 WHERE (2 &lt;&gt; [Extent1].[PersonId]) AND (1 &lt;&gt; [Extent1].[PersonId]) 而从 @W0lf 答案生成的代码就像 WHERE NOT ([Extent1].[PersonId] IN (2, 1)) 哪个查询更有效?
      【解决方案4】:

      LINQ to ENTITIES:不包含方法:

      方法应该是.Contains() 而不是.Contain。这需要一个原始类型枚举,因此只需枚举 badGuys 集合的关键字段,并在查询上下文时在 Where 子句中调用 .Contains()

      //get the list of bad guys
      List<Person> badGuys = ...
      //get simple primitive type enumeration
      var badGuysIDs = badGuys.Select(t => t.PersonId).ToList();
      
      using(Context c = new Context())
      {
         var badPersons = c.Persons.Where(t => !badGuysIDs.Contains(t.PersonId)).ToList();
      }
      

      LINQ TO OBJECTS EXCEPT() 方法:

      另一个选择,如果你已经拥有你的List&lt;Person&gt; 的坏人是在你的收藏中使用.Except() 方法。这必须在您将集合枚举为对象后完成。

      //get the list of bad guys
      List<Person> badGuys = ...
      
      using(Context c = new Context())
      {
         //enumerate Persons, then exclude badPersons
         var goodPersons = c.Persons.ToList().Except(badPersons);
      }
      

      LINQ to ENTITIES EXCEPT() 方法:

      如果您的 Persons 表中有大量行,上述方法将不理想,因为它会枚举整个集合。如果您有大量行,您将希望在枚举之前在数据库端执行排除。为此,您只需告诉数据库如何比较两个对象的相等性,如This excellent SO answer

      public class PersonComparer: IEqualityComparer<Persons> 
      {
           public bool Equals(Person x, Person y) {return x.Id == y.Id;}
           public int GetHashCode(Person person) {return person.PersonId.GetHashCode();}
      }
      
      
      using(Context c = new Context())
      {
          //get the list of bad guys
          List<Person> badGuys = ...
      
         var goodPersons = c.Persons.Except(badPersons, PersonComparer()).ToList();
      }
      

      【讨论】:

        猜你喜欢
        • 2014-04-01
        • 2015-06-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多