LINQ选择动态列和值

thn*_*rks 9 c# linq wpf entity-framework

出于各种原因,我需要能够允许用户根据他们对列和值的选择从数据库中选择项目.例如,如果我有一张桌子:

Name   | Specialty       | Rank
-------+-----------------+-----
John   | Basket Weaving  | 12
Sally  | Basket Weaving  | 6
Smith  | Fencing         | 12
Run Code Online (Sandbox Code Playgroud)

用户可以请求1,2或更多列,并且他们请求的列可以是不同的.例如,用户可以请求的条目,其中Specialty == Basket WeavingRank == 12. What I do currently is gather the user's request and create a list ofKeyValuePair where the密钥is the column name and theValue`是列所需的值:

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");
Run Code Online (Sandbox Code Playgroud)

使用这个列表,KeyValuePair我怎样才能最简洁地选择符合所有条件的数据库项目?

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

编辑:如果我能帮助它,我想使用EntityFramework而不是原始SQL.

更新3:我越来越近了.一旦我从表中下载了所有值,我就发现了一种使用LINQ的方法.这显然不是非常理想,因为它会下载表格中的所有内容.所以我想最后一步是找出一种方法,我不必每次都下载整个表格.这是对我正在做的事情的解释:

对于表中的每一行

db.MyTable.ToList().Where(e => ...
Run Code Online (Sandbox Code Playgroud)

我列出了一个bool列表,表示列是否符合条件.

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

然后我检查一下这个bool列表是否都是真的

.All(c => c == true)
Run Code Online (Sandbox Code Playgroud)

完整代码的示例如下:

// 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎我的问题在于这段代码:

e.GetType()?.GetProperty(c.Key)?.GetValue(e)?.ToString()
Run Code Online (Sandbox Code Playgroud)

我不熟悉表达树,所以答案可能就在于它们.我也可以尝试动态LINQ.

Jon*_*nan 9

由于您的列和过滤器是动态的,因此动态LINQ库可以为您提供帮助

NuGet:https://www.nuget.org/packages/System.Linq.Dynamic/

Doc: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)");
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是使用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 });
}
Run Code Online (Sandbox Code Playgroud)

免责声明:我是Eval Expression.NET项目的所有者

编辑:回答评论

注意,参数值类型必须与属性类型兼容.例如,如果"Rank"属性是INT,则只有与INT兼容的类型才能工作(不是字符串).

显然,您需要重构此方法以使其更适合您的应用程序.但正如您所看到的,您可以轻松地使用Entity Framework中的异步方法.

如果您还自定义select(返回类型),则可能需要使用反射获取异步结果,或者使用ExecuteAsync而不是ToList().

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Aiz*_*zen 0

不确定你在这里追求什么。但这应该给你一个想法。

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

我什至不确定为什么你必须使用 List。因为 List 需要迭代。您可以仅在第一个条件中使用 Key,在最后一个条件中使用 Value,以避免使用 KeyValuePair 列表。