'Contains()' workaround using Linq to Entities?

前端 未结 10 1963
清酒与你
清酒与你 2020-11-22 14:01

I\'m trying to create a query which uses a list of ids in the where clause, using the Silverlight ADO.Net Data Services client api (and therefore Linq To Entities). Does any

相关标签:
10条回答
  • 2020-11-22 14:31

    Update: EF ≥ 4 supports Contains directly (Checkout Any), so you don't need any workaround.

    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);
    }
    

    USAGE:

    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"
          );
      }
    }
    
    0 讨论(0)
  • 2020-11-22 14:35

    You can fall back on hand coding some e-sql (note the keyword "it"):

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

    Here is the code that I used to generate some e-sql from a collection, YMMV:

    string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
    return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");
    
    0 讨论(0)
  • 2020-11-22 14:38

    In addition to selected answer.

    Replace Expression.Or with Expression.OrElse to use with Nhibernate and fix Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression' exception.

    0 讨论(0)
  • 2020-11-22 14:39

    Thanks very much. WhereIn extension method was enough for me. I profiled it and generated the same SQL command to the DataBase as e-sql.

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

    Generated this:

    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 讨论(0)
  • 2020-11-22 14:43

    From 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);
    } 
    

    and the query becomes:

    var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));
    
    0 讨论(0)
  • 2020-11-22 14:47

    Sorry new user, I would have commented on the actual answer, but it seems I can't do that yet?

    Anyway, in regards to the answer with sample code for BuildContainsExpression(), be aware that if you use that method on database Entities (i.e. not in-memory objects) and you are using IQueryable, that it actually has to go off to the database since it basically does a lot of SQL "or" conditions to check the "where in" clause (run it with SQL Profiler to see).

    This can mean, if you are refining an IQueryable with multiple BuildContainsExpression(), it won't turn it in to one SQL statement that gets run at the end as you expect.

    The workaround for us was to use multiple LINQ joins to keep it to one SQL call.

    0 讨论(0)
提交回复
热议问题