条件付きLinqクエリ


92

ログビューアに取り組んでいます。使用には、ユーザー、重大度などでフィルターするオプションがあります。SQLの日にはクエリ文字列に追加しますが、Linqでそれを実行したいと考えています。条件付きでwhere句を追加するにはどうすればよいですか?

回答:


156

特定の基準に合格した場合にのみフィルタリングする場合は、次のようにします

var logs = from log in context.Logs
           select log;

if (filterBySeverity)
    logs = logs.Where(p => p.Severity == severity);

if (filterByUser)
    logs = logs.Where(p => p.User == user);

このようにすると、式ツリーを希望どおりにすることができます。このようにして作成されたSQLは、まさに必要なものであり、それ以上のものではありません。


2
こんにちはAND句の代わりにOR句をOR句にするための提案はありますか?
Jon H

1
ええ...それは少し難しいです 私が見た中で最高のものは、仕様パターンを介して、述語を仕様にプルして、specification.Or(someOtherSpecification)を呼び出すことです。基本的に、独自の式ツリーを少し書く必要があります。ここでのサンプルコードと解説:codeinsanity.com/archive/2008/08/13/...
ダレンコップ

私は愚かな質問があります。これらのログがデータベースから取得されている場合、すべてのログを取得してメモリでフィルタリングしますか?もしそうなら、どのように条件をデータベースに渡すことができますか
Ali Umair

それはメモリでそれらをフィルタリングしていません。クエリを構築し、データベース内のすべての条件を送信しています(少なくともほとんどのlinq-to-xプロバイダーの場合)
Darren Kopp

このエラーが発生LINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
Ali Umair

22

リスト/配列に基づいてフィルタリングする必要がある場合は、以下を使用します。

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
    {
        if (Numbers == null)
            Numbers = new List<string>();

        if (Letters == null)
            Letters = new List<string>();

        var q = from d in database.table
                where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                where (Letters.Count == 0 || Letters.Contains(d.Letter))
                select new Data
                {
                    Number = d.Number,
                    Letter = d.Letter,
                };
        return q.ToList();

    }

3
これは断然最良で最も正しい答えです。条件付き|| 最初の部分のみを比較し、最初の部分がtrueの場合は2番目をスキップします。
Serj Sagan 2012

1
この構成には、生成されたSQLクエリの式の「または」部分が含まれます。受け入れられた答えはより効率的なステートメントを生成します。もちろん、データプロバイダーの最適化によって異なります。LINQ-to-SQLの方が最適化が優れている可能性がありますが、LINQ-to-Entitiesはそうではありません。
Suncat2000

20

Darenと同様の回答の使用を終了しましたが、IQueryableインターフェイスを使用しました。

IQueryable<Log> matches = m_Locator.Logs;

// Users filter
if (usersFilter)
    matches = matches.Where(l => l.UserName == comboBoxUsers.Text);

 // Severity filter
 if (severityFilter)
     matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);

 Logs = (from log in matches
         orderby log.EventTime descending
         select log).ToList();

これにより、データベースにアクセスする前にクエリが構築されます。コマンドは、最後の.ToList()まで実行されません。


14

条件付きlinqに関しては、フィルターとパイプのパターンが大好きです。
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

基本的に、IQueryableとパラメーターを受け取るフィルターケースごとに拡張メソッドを作成します。

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
    return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}

8

これを拡張メソッドで解決し、滑らかな式の途中で条件付きでLINQを有効にできるようにしました。これにより、ifステートメントで式を分割する必要がなくなります。

.If() 拡張方法:

public static IQueryable<TSource> If<TSource>(
        this IQueryable<TSource> source,
        bool condition,
        Func<IQueryable<TSource>, IQueryable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

これにより、次のことが可能になります。

return context.Logs
     .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
     .If(filterByUser, q => q.Where(p => p.User == user))
     .ToList();

IEnumerable<T>他のほとんどのLINQ式を処理するバージョンもあります。

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

4

別のオプションは、ここで説明したPredicateBuilderのようなものを使用することです。次のようなコードを記述できます。

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");

var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                  .And (Product.IsSelling());

var query = from p in Data.Products.Where (newKids.Or (classics))
            select p;

これはLinq 2 SQLでのみ機能することに注意してください。EntityFrameworkは、このメソッドが機能するために必要なExpression.Invokeを実装していません。ここでこの問題について質問があります


これは、AutoMapperなどのツールと共にデータ転送オブジェクトとエンティティモデルをマッピングするリポジトリとともに、ビジネスロジックレイヤーを使用するユーザーにとって優れた方法です。述語ビルダーを使用すると、フラット化のためにAutoMapperに送信する前にIQueryableを動的に変更できます(リストをメモリに取り込む)。Entity Frameworkもサポートしていることに注意してください。
chrisjsherm 2013

3

これを行う:

bool lastNameSearch = true/false; // depending if they want to search by last name,

これをwhereステートメントに含める:

where (lastNameSearch && name.LastNameSearch == "smith")

場合は、最終的なクエリが作成されることを意味しlastNameSearchているfalseクエリは完全に姓検索のための任意のSQLを省略します。


データプロバイダーによって異なります。LINQ-to-Entitiesはそれを最適化しません。
Suncat2000 2016年

1

これはかわいらしいことではありませんが、ラムダ式を使用してオプションで条件を渡すことができます。TSQLでは、パラメーターをオプションにするために以下の多くのことを行います。

WHEREフィールド= @FieldVar OR @FieldVar IS NULL

次のラムダ(認証を確認する例)を使用して同じスタイルを複製できます。

MyDataContext db = new MyDataContext();

void RunQuery(string param1、string param2、int?param3){

Func checkUser = user =>

((param1.Length> 0)?user.Param1 == param1:1 == 1)&&

((param2.Length> 0)?user.Param2 == param2:1 == 1)&&

((param3!= null)?user.Param3 == param3:1 == 1);

ユーザーfoundUser = db.Users.SingleOrDefault(checkUser);

}


1

最近同様の要件があり、最終的には彼のMSDNでこれを見つけました。 Visual Studio 2008のCSharpサンプル

ダウンロードのDynamicQueryサンプルに含まれるクラスを使用すると、実行時に次の形式で動的クエリを作成できます。

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

これを使用して、実行時にクエリ文字列を動的に作成し、それをWhere()メソッドに渡すことができます。

string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
var q = from c in db.Customers.Where(queryString, null)
        orderby c.CompanyName
        select c;

1

この拡張メソッドを作成して使用できます

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}

0

C#の&&演算子を使用するだけです。

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

編集:ああ、もっと注意深く読む必要があります。追加の句を条件付きで追加する方法を知りたいと思っていました。その場合、私にはわかりません。:)おそらく私がやろうとしていることは、いくつかのクエリを準備し、必要なものに応じて適切なクエリを実行することです。


0

外部メソッドを使用できます:

var results =
    from rec in GetSomeRecs()
    where ConditionalCheck(rec)
    select rec;

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

これは機能しますが、式ツリーに分解できません。つまり、Linq to SQLはすべてのレコードに対してチェックコードを実行します。

または:

var results =
    from rec in GetSomeRecs()
    where 
        (!filterBySeverity || rec.Severity == severity) &&
        (!filterByUser|| rec.User == user)
    select rec;

これは式ツリーで機能する可能性があります。つまり、Linq to SQLが最適化されます。


0

さて、私が考えたのは、フィルター条件を述語の一般的なリストに入れることができるということでした。

    var list = new List<string> { "me", "you", "meyou", "mow" };

    var predicates = new List<Predicate<string>>();

    predicates.Add(i => i.Contains("me"));
    predicates.Add(i => i.EndsWith("w"));

    var results = new List<string>();

    foreach (var p in predicates)
        results.AddRange(from i in list where p.Invoke(i) select i);               

その結果、「me」、「meyou」、および「mow」を含むリストが作成されます。

すべての述語のORを取るまったく別の関数で述語を使用してforeachを実行することで、これを最適化できます。

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