Linq to Entitiesを使用した「Contains()」回避策?


86

Silverlight ADO.Net Data ServicesクライアントAPI(したがって、Linq To Entities)を使用して、where句のIDのリストを使用するクエリを作成しようとしています。誰かがサポートされていないContainsの回避策を知っていますか?

私はこのようなことをしたい:

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)を使用していたことは知っています。
ダレルノートン2010

7
@Darrellあなたのコメントをスキップしたので、私はちょうど30分を無駄にしました。コメントを点滅させて画面全体に表示させたいと思います。
Chris Dwyer 2011年

回答:


97

更新: EF≥4はContains直接サポートするAnyため(Checkout )、回避策は必要ありません。

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
チェックコレクションが空の場合に結果が返されないことを意味する場合は、上記のスニペットで-replaceif (!collection.Any()) //action;アクションを、最高のパフォーマンスを得るために要求されたタイプの空のクエリを返すだけで置き換える-または単にこの行を削除します。
シミーワイツハンドラー2010

1
WhereIn(query、selector、collection);を返します。return WhereIn(query、selector、(IEnumerable <TValue>)collection);に置き換える必要があります。不要な再帰を回避するため。
アントワーヌオーブリー2011年

1
コードにバグがあると思います。指定された値のリストが空の場合、正しい動作は結果を返さないことです。つまり、クエリ内のオブジェクトがコレクションに存在しません。ただし、コードは正反対のことを行います。すべての値が返されますが、いずれも返されません。「if(!collection.Any())return query.Where(e => false)」が必要だと思います
ShadowChaser 2012

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
「それ」についてもっと情報がありますか?「it」プレフィックスはMSDNサンプルに表示されますが、「it」が必要な時期と理由についての説明はどこにもありません。
ロバートクレイプール

1
Entity Frameworkの動的クエリで使用されます。geekswithblogs.net / thanigai / archive / 2009/04/29 / をご覧ください。ThanigainathanSiranjeeviがそこで説明しています。
シミーワイツハンドラー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についてはよくわかりませんが、オブジェクトへのlinqでは、これらのクエリに常にany()を使用します。

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

5
Anyは、シーケンスタイプのオブジェクトを取りません。パラメータがない(この場合、「これは空かどうか」)か、述語を取ります。
Jon Skeet

AndreasN +1のおかげで):私はこの答えを見つけたことがひどくうれしい
SDReyes

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を使用している場合は、実際にはデータベースに移動する必要があることに注意してください基本的に、「where in」句をチェックするために多くのSQL「または」条件を実行するためです(SQLプロファイラーで実行して確認してください)。

これは、複数のBuildContainsExpression()を使用してIQueryableを改良している場合、期待どおりに最後に実行される1つのSQLステートメントに変換されないことを意味します。

私たちの回避策は、複数のLINQ結合を使用して、1つのSQL呼び出しに保持することでした。


0

選択した回答に加えて。

に置き換えExpression.OrExpression.OrElseNhibernateで使用し、Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'例外を修正します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.