2016-02-10 32 views
6

さまざまな理由から、ユーザーは列と値の選択に基づいてデータベースから項目を選択できる必要があります。私はテーブルを持っている場合、例えば、:LINQ動的な列と値を選択

Name | Specialty  | Rank 
-------+-----------------+----- 
John | Basket Weaving | 12 
Sally | Basket Weaving | 6 
Smith | Fencing   | 12 

ユーザは、1,2、またはそれ以上の列と、それらが異なっていてもよく、要求列を要求することができます。 Specialty == Basket WeavingRank == 12. What I do currently is gather the user's request and create a list of KeyValuePair where theキーis the column name and the [値]列の目標値であり、たとえば、ユーザーがエントリを要求することができる:KeyValuePair年代のこのリストを使用して

class UserSearch 
{ 
    private List<KeyValuePair<string, string> criteria = new List<KeyValuePair<string, string>>(); 

    public void AddTerm(string column, string value) 
    { 
     criteria.Add(new KeyValuePair<string, string>(column, value); 
    } 

    public void Search() 
    { 
     using (var db = new MyDbContext()) 
     { 
      // Search for entries where the column's (key's) value matches 
      // the KVP's value. 
      var query = db.MyTable.Where(???); 
     } 
    } 
} 

/* ... Somewhere else in code, user adds terms to their search 
* effectively performing the following ... */ 
UserSearch search = new UserSearch(); 
search.Add("Specialty", "Basket Weaving"); 
search.Add("Rank", "12"); 

、どのように私は最も簡潔にどのデータベース項目を選択することができますすべての基準に一致しますか?

using (var db = new MyDbContext) 
{ 
    // Where each column name (key) in criteria matches 
    // the corresponding value in criteria. 
    var query = db.MyTable.Where(???); 
} 

編集:私はそれを助けることができる場合は、未処理のSQLの代わりにEntityFrameworkを使用したいと思います。

更新3:近づいています。私はテーブルからすべての値を をダウンロードしたら、LINQを使用する方法を発見しました。これは明らかにテーブルの中のすべてを ダウンロードするので、理想的ではありません。だから私は最後のステップはどこの方法を理解することだと思います 毎回テーブル全体をダウンロードする必要はありません。

行ごとにテーブル

db.MyTable.ToList().Where(e => ... 

で、私は列が基準に一致した場合に相当boolsのリストを作る:ここで私がやっていることの説明があります。

criteria.Select(c => e.GetType()?.GetProperty(c.Key)?.GetValue(e)?.ToString() == c.Value) 
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
         Basically just gets the value of specific column 
              by string 

それから私は、このブールリストにすべて該当

.All(c => c == true) 

完全なコードの例であるかどうかを確認する以下の通りです:私の問題はであるかのように思える

// This class was generated from the ADO.NET Entity Data Model template 
// from the database. I have stripped the excess stuff from it leaving 
// only the properties. 
public class MyTableEntry 
{ 
    public string Name { get; } 
    public string Specialty { get; } 
    public string Rank { get; } 
} 

class UserSearch 
{ 
    private List<KeyValuePair<string, string> criteria = new List<KeyValuePair<string, string>>(); 

    public void AddTerm(string column, string value) 
    { 
     criteria.Add(new KeyValuePair<string, string>(column, value); 
    } 

    public async Task<List<MyTableEntry>> Search() 
    { 
     using (var db = new MyDbContext()) 
     { 
      var entries = await db.MyTable.ToListAsync(); 
      var matches = entries.Where(e => criteria.Select(c => e.GetType() 
                    ?.GetProperty(c.Key) 
                    ?.GetValue(e) 
                    ?.ToString() == c.Value) 
                 .All(c => c == true)); 

      return matches.ToList(); 
     } 
    } 
} 

このコードセグメント:

e.GetType()?.GetProperty(c.Key)?.GetValue(e)?.ToString() 

私は表現木に慣れていないので、おそらく答えがそれらにあるでしょう。ダイナミックLINQも試してみます。

+0

私はこのポストを考えます助けてくれるでしょう。http://stackoverflow.com/questions/821365/how-to-convert-a-string-to-its-equivalent-expression-tree あなたが望む文字列をWhere実際に動作する式に変換します。 – Dylan

+0

私は、SQLで不要な比較をしているという懸念を軽減するために、linqから生成されたSQLを含めるように答えを更新しました。 – Jakotheshadows

+0

まだ解決していますか? –

答えて

0

ここであなたが何をしているのかよく分かりません。しかし、これはあなたにアイデアを与えるはずです。

var query = db.Mytable.Where(x=> x.Specialty == criteria[0].Value && c=> c.Rank == criteria[1].Value).ToString(); 

なぜあなたもリストを使用する必要はありません。 Listを反復処理する必要があります。 KeyValuePairのリストを避けるために、最初にKeyを使用して最後の条件の最初の条件と値を使用できます。

+0

おそらくもっと明確にする必要があります。列の数と名前は異なる場合があります。私は 'x.Specialty'のような列名を厳密にコード化することはできませんし、' .Where'節に一定数の検索語を持つことはできません。 – thndrwrks

1

ダイナミックwhere句のための一般的なパターンとしてこれを試してみてください:

//example lists, a solution for populating will follow 
List<string> Names = new List<string>() { "Adam", "Joe", "Bob" }; 
//these two deliberately left blank for demonstration purposes 
List<string> Specialties = new List<string>() { }; 
List<string> Ranks = new List<string>() { }; 
using(var dbContext = new MyDbContext()) 
{ 
    var list = dbContext.MyTable 
         .Where(x => (!Names.Any() || Names.Contains(x.Name)) && 
            (!Specialties.Any() || Specialties.Contains(x.Specialty)) && 
            (!Ranks.Any() || Ranks.Contains(x.Rank))).ToList(); 

} 

あなたの基礎となるデータに関するいくつかの仮定を作り、次はLINQによって生成される可能性が高い上に示されているSQLですあなたのUserSearchクラス内のこれらのリストを移入する

DECLARE @p0 NVarChar(1000) = 'Adam' 
DECLARE @p1 NVarChar(1000) = 'Joe' 
DECLARE @p2 NVarChar(1000) = 'Bob' 

SELECT [t0].[Name], [t0].[Specialty], [t0].[Rank] 
FROM [MyTable] AS [t0] 
WHERE [t0].[Name] IN (@p0, @p1, @p2) 

foreach(var kvp in criteria) 
{ 
    switch(kvp.Key) 
    { 
     case "Name": Names.Add(kvp.Value); break; 
     case "Specialty": Specialties.Add(kvp.Value); break; 
     case "Rank": Ranks.Add(kvp.Value); break; 
    } 
} 

保守性に懸念があり、テーブルの列が頻繁に変更される場合は、SqlCommandクラスを介して生のSQLを使用することに戻りたいと思うかもしれません。こうすることで、動的選択と句を簡単に生成できます。表の列のリストを照会して、選択/フィルター処理で使用できるオプションを動的に判別することもできます。

+0

これはより近いです。どの列でも比較を避け、代わりに動的に指定する列のみを比較する方法はありますか? – thndrwrks

+0

LINQではありません。あなたの心配がコードが醜いということであれば、それを回避するためにできることはごくわずかです。不必要な比較を実行することが懸念される場合は、名前にフィルタがない限り、各条件の!Names.Any()部分は基本的にフィルタを無視するため、心配する必要はありません。私の例では、SpecialtiesとRanksリストに対して追加のSQLは生成されません。 – Jakotheshadows

+0

私の懸念は保守性です。テーブルの列が変更された場合は、変更する必要があるだけです。 – thndrwrks

9

あなたの列とフィルタが動的であるので、ダイナミックLINQライブラリはここ

NuGetをあなたを助けることがあります。https://www.nuget.org/packages/System.Linq.Dynamic/

ドク:http://dynamiclinq.azurewebsites.net/

using System.Linq.Dynamic; //Import the Dynamic LINQ library 

//The standard way, which requires compile-time knowledge 
//of the data model 
var result = myQuery 
    .Where(x => x.Field1 == "SomeValue") 
    .Select(x => new { x.Field1, x.Field2 }); 

//The Dynamic LINQ way, which lets you do the same thing 
//without knowing the data model before hand 
var result = myQuery 
    .Where("Field1=\"SomeValue\"") 
    .Select("new (Field1, Field2)"); 

別の解決策は、Eval Expression.NETを使用しています実行時に動的にC#コードを評価できるようにします。

using (var ctx = new TestContext()) 
{ 
    var query = ctx.Entity_Basics; 

    var list = Eval.Execute(@" 
q.Where(x => x.ColumnInt < 10) 
.Select(x => new { x.ID, x.ColumnInt }) 
.ToList();", new { q = query }); 
} 

免責事項:私は編集プロジェクトEval Expression.NET

の持ち主だ:回答コメント

は注意してください、パラメータ値の型は、プロパティタイプと互換性がなければなりません。たとえば、「ランク」プロパティがINTの場合、INTと互換性のあるタイプのみが動作します(文字列ではありません)。

明らかに、アプリケーションに合わせてこのメソッドをリファクタリングする必要があります。しかし、わかるように、Entity Frameworkの非同期メソッドでも簡単に使用できます。

select(戻り値の型)をカスタマイズする場合は、reflectionを使用して非同期の結果を取得するか、ToList()でExecuteAsyncを使用する必要があります。

public async Task<List<Entity_Basic>> DynamicWhereAsync(CancellationToken cancellationToken = default(CancellationToken)) 
{ 
    // Register async extension method from entity framework (this should be done in the global.asax or STAThread method 
    // Only Enumerable && Queryable extension methods exists by default 
    EvalManager.DefaultContext.RegisterExtensionMethod(typeof(QueryableExtensions)); 

    // GET your criteria 
    var tuples = new List<Tuple<string, object>>(); 
    tuples.Add(new Tuple<string, object>("Specialty", "Basket Weaving")); 
    tuples.Add(new Tuple<string, object>("Rank", "12")); 

    // BUILD your where clause 
    var where = string.Join(" && ", tuples.Select(tuple => string.Concat("x.", tuple.Item1, " > p", tuple.Item1))); 

    // BUILD your parameters 
    var parameters = new Dictionary<string, object>(); 
    tuples.ForEach(x => parameters.Add("p" + x.Item1, x.Item2)); 

    using (var ctx = new TestContext()) 
    { 
     var query = ctx.Entity_Basics; 

     // ADD the current query && cancellationToken as parameter 
     parameters.Add("q", query); 
     parameters.Add("token", cancellationToken); 

     // GET the task 
     var task = (Task<List<Entity_Basic>>)Eval.Execute("q.Where(x => " + where + ").ToListAsync(token)", parameters); 

     // AWAIT the task 
     var result = await task.ConfigureAwait(false); 
     return result; 
    } 
} 
+0

クエリは次のようになります: 'await db.MyTable.Where(" Specialty == "Basket Weaving" && Rank == "12" ")ToListAsync()' – Eldho

+0

プロジェクトの 'Where'式のドキュメントを更新します。 。この図書館はありがとうございます – Eldho

+1

あなたのライブラリが素晴らしいです!それを書いてくれてありがとう:) – nawfal

0

Fine。私は私の2セントを教えてください。動的LINQを使用する場合は、式ツリーを選択する必要があります。 LINQステートメントは、必要に応じて動的に生成できます。以下のような何かが魔法を行うべきです。

// inside a generic class. 
public static IQueryable<T> GetWhere(string criteria1, string criteria2, string criteria3, string criteria4) 
{ 
    var t = MyExpressions<T>.DynamicWhereExp(criteria1, criteria2, criteria3, criteria4); 
    return db.Set<T>().Where(t); 
} 

ここで、別の汎用クラスでは、式を次のように定義できます。

public static Expression<Func<T, bool>> DynamicWhereExp(string criteria1, string criteria2, string criteria3, string criteria4) 
{ 
    ParameterExpression Param = Expression.Parameter(typeof(T)); 

    Expression exp1 = WhereExp1(criteria1, criteria2, Param); 
    Expression exp2 = WhereExp1(criteria3, criteria4, Param); 

    var body = Expression.And(exp1, exp2); 

    return Expression.Lambda<Func<T, bool>>(body, Param); 
} 

private static Expression WhereExp1(string field, string type, ParameterExpression param) 
{ 
    Expression aLeft = Expression.Property(param, typeof(T).GetProperty(field)); 
    Expression aRight = Expression.Constant(type); 
    Expression typeCheck = Expression.Equal(aLeft, aRight); 
    return typeCheck; 
} 

ここでメソッドをどこでも呼び出すことができます。

// get search criterias from user 
var obj = new YourClass<YourTableName>(); 
var result = obj.GetWhere(criteria1, criteria2, criteria3, criteria4); 

これは、LINQのあなたの場所を拡張メソッドで使用するために、それらの間の2と条件AND演算子であなたに力強くダイナミックな表現を与えるだろう。これで、戦略に基づいて必要に応じて引数を渡すことができます。例えばparams string []またはキー値のペアリストで...重要ではありません。

あなたは何もここに固定されていないことがわかります...その完全に動的と反射よりも早く、あなたのメイクのすべての余分なチェックを必要とするなど、多くの表現やなど、多くのcriterias ...

0
Jakotheshadowsの答え@継続

ではなく、 EFの出力で確認するための何もないとき、これは我々がここに家の中で何に近い:

// Example lists, a solution for populating will follow 
var Names = new List<string> { "Adam", "Joe", "Bob" }; 
// These two deliberately left blank for demonstration purposes 
var specialties = new List<string>(); 
var ranks = new List<string>(); 
using(var dbContext = new MyDbContext()) 
{ 
    var list = dbContext.MyTable 
     .FilterByNames(names) 
     .FilterBySpecialties(specialties) 
     .FilterByRanks(ranks) 
     .Select(...) 
     .ToList(); 
} 

[Table(...)] 
public class MyTable : IMyTable 
{ 
    // ... 
} 

Fiのまた、EFクエリ内でContains(...)またはAny(...)を使用すると、パフォーマンス上の問題が顕著になります。 Predicate Builderを使用する方がずっと高速です。これは、IDの配列(これはLinqKit nugetパッケージが必要)との一例である:

public static IQueryable<TEntity> FilterByIDs<TEntity>(
    this IQueryable<TEntity> query, int[] ids) 
    where TEntity : class, IBase 
{ 
    if (ids == null || !ids.Any(x => x > 0 && x != int.MaxValue)) { return query; } 
    return query.AsExpandable().Where(BuildIDsPredicate<TEntity>(ids)); 
} 
private static Expression<Func<TEntity, bool>> BuildIDsPredicate<TEntity>(
    IEnumerable<int> ids) 
    where TEntity : class, IBase 
{ 
    return ids.Aggregate(
     PredicateBuilder.New<TEntity>(false), 
     (c, id) => c.Or(p => p.ID == id)); 
} 

これは本当に速いですクエリの「IN」構文を出力します

WHERE ID IN [1,2,3,4,5]