使用Linq to Entities的“ Contains()”解决方法?


86

我正在尝试创建一个查询,该查询使用Silverlight ADO.Net数据服务客户端api(并因此使用Linq To Entities)在where子句中使用ID列表。有人知道不支持“包含”的解决方法吗?

我想做这样的事情:

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        where txnIds.Contains(t.OpenTransactionId)
        select t;

试过这个:

var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;

但是得到了“不支持方法'Any'”。


35
注意:Entity Framework 4(在.NET 4中)具有一个“包含”方法,以防万一有人偶然正在阅读不了解它的内容。我知道OP正在使用EF1(.NET 3.5)。
DarrellNorton 2010年

7
@Darrell我浪费了一个半小时,因为我跳过了您的评论。希望我可以让您的评论眨眼并在屏幕上显示字幕。
克里斯·德怀

Answers:


97

更新: EF≥4Contains直接支持(Checkout Any),因此您不需要任何解决方法。

public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    IEnumerable<TValue> collection
  )
{
  if (selector == null) throw new ArgumentNullException("selector");
  if (collection == null) throw new ArgumentNullException("collection");
  if (!collection.Any()) 
    return query.Where(t => false);

  ParameterExpression p = selector.Parameters.Single();

  IEnumerable<Expression> equals = collection.Select(value =>
     (Expression)Expression.Equal(selector.Body,
          Expression.Constant(value, typeof(TValue))));

  Expression body = equals.Aggregate((accumulate, equal) =>
      Expression.Or(accumulate, equal));

  return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}

//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    params TValue[] collection
  )
{
  return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}

用法:

public static void Main()
{
  using (MyObjectContext context = new MyObjectContext())
  {
    //Using method 1 - collection provided as collection
    var contacts1 =
      context.Contacts.WhereIn(c => c.Name, GetContactNames());

    //Using method 2 - collection provided statically
    var contacts2 = context.Contacts.WhereIn(c => c.Name,
      "Contact1",
      "Contact2",
      "Contact3",
      "Contact4"
      );
  }
}

6
警告; 当arg是大集合(我的是8500个int列表)时,堆栈溢出。您可能会认为通过这样的列表很疯狂,但是我认为这仍然暴露了这种方法的缺陷。
dudeNumber4 2010年

2
如果我错了,请纠正我。但这意味着当传递的集合(过滤器)为空集时,它将基本上导致所有数据,因为它只是返回了查询参数。我期望它可以过滤所有值,有没有办法做到这一点?
午睡

1
如果您的意思是当检查集合为空时,它应该不返回任何结果,则将上述代码段中的-replace操作替换为if (!collection.Any()) //action;仅返回一个请求类型的空查询以获得最佳性能-或仅删除此行。
Shimmy Weitzhandler's

1
返回WhereIn(查询,选择器,集合); 应该由return WhereIn(query,selector,(IEnumerable <TValue>)collection)代替;以避免不必要的递归。
Antoine Aubry,

1
我相信代码中有错误。如果提供的值列表为空,则正确的行为应该是不返回任何结果-即/集合中不存在查询中的对象。但是,代码的作用恰恰相反-返回所有值,而不返回任何值。我相信您希望“ if(!collection.Any())返回query.Where(e => false)”
ShadowChaser

18

您可以依靠一些e-sql进行编码(注意关键字“ it”):

return CurrentDataSource.Product.Where("it.ID IN {4,5,6}"); 

这是我用来从集合YMMV生成一些e-sql的代码:

string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");

1
您是否有关于“它”的更多信息?MSDN示例中显示了“ it”前缀,但是我在哪里找不到关于何时/为什么需要“ it”的解释。
罗伯特·克莱普尔

1
在Entity Framework动态查询中使用时,请看一下geekswithblogs.net/thanigai/archive/2009/04/29/…,Thanigainathan Siranjeevi在那里进行了解释。
Shimmy Weitzhandler,2010年

13

MSDN

static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
    Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
    if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
    if (null == values) { throw new ArgumentNullException("values"); }
    ParameterExpression p = valueSelector.Parameters.Single();

    // p => valueSelector(p) == values[0] || valueSelector(p) == ...
    if (!values.Any())
    {
        return e => false;
    }

    var equals = values.Select(
             value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));

    var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(body, p);
} 

查询变为:

var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));

3
如果要执行“不包含”,只需在BuildContainsExpression方法中进行以下编辑:-Expression.Equal变为Expression.NotEqual-Expression.Or成为Expression.And
Merritt

2

我不确定Silverligth,但对于对象,我总是对这些查询使用any()。

var q = from t in svc.OpenTranaction
        where txnIds.Any(t.OpenTransactionId)
        select t;

5
任何对象都不会采用序列类型的对象-它要么没有参数(在这种情况下只是“是否为空”),要么带有谓词。
乔恩·斯基特

我很高兴找到这个答案:) +1感谢AndreasN
SDReyes 2010年

1

为了完成记录,这是我最后使用的代码(为清楚起见,省略了错误检查)...

// How the function is called
var q = (from t in svc.OpenTransaction.Expand("Currency,LineItem")
         select t)
         .Where(BuildContainsExpression<OpenTransaction, long>(tt => tt.OpenTransactionId, txnIds));



 // The function to build the contains expression
   static System.Linq.Expressions.Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
                System.Linq.Expressions.Expression<Func<TElement, TValue>> valueSelector, 
                IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            System.Linq.Expressions.ParameterExpression p = valueSelector.Parameters.Single();

            // p => valueSelector(p) == values[0] || valueSelector(p) == ...
            if (!values.Any())
            {
                return e => false;
            }

            var equals = values.Select(value => (System.Linq.Expressions.Expression)System.Linq.Expressions.Expression.Equal(valueSelector.Body, System.Linq.Expressions.Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<System.Linq.Expressions.Expression>((accumulate, equal) => System.Linq.Expressions.Expression.Or(accumulate, equal));
            return System.Linq.Expressions.Expression.Lambda<Func<TElement, bool>>(body, p);
        }


0

非常感谢。WhereIn扩展方法对我来说足够了。我对其进行了概要分析,并将与e-sql相同的SQL命令生成到数据库。

public Estado[] GetSomeOtherMore(int[] values)
{
    var result = _context.Estados.WhereIn(args => args.Id, values) ;
    return result.ToArray();
}

产生了这个:

SELECT 
[Extent1].[intIdFRLEstado] AS [intIdFRLEstado], 
[Extent1].[varDescripcion] AS [varDescripcion]
FROM [dbo].[PVN_FRLEstados] AS [Extent1]
WHERE (2 = [Extent1].[intIdFRLEstado]) OR (4 = [Extent1].[intIdFRLEstado]) OR (8 = [Extent1].[intIdFRLEstado])


0

抱歉,新用户,我会在实际答案中发表评论,但看来我还不能这样做?

无论如何,关于BuildContainsExpression()的示例代码的答案,请注意,如果您在数据库实体(即非内存对象)上使用该方法并且使用的是IQueryable,则实际上它必须转到数据库因为它基本上会执行很多SQL“或”条件来检查“ where in”子句(请使用SQL Profiler运行它以进行查看)。

这意味着,如果您正在使用多个BuildContainsExpression()完​​善IQueryable,它将无法将其转换为一条SQL语句,该语句最终将按您的期望运行。

我们的解决方法是使用多个LINQ联接将其保留为一个SQL调用。


0

除了选择答案。

更换Expression.OrExpression.OrElse与NHibernate和修复使用Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'异常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.