由于各种原因,我需要能够允许用户根据他们选择的列和值从数据库中选择项目。例如,如果我有一张桌子:
Name | Specialty | Rank
-------+-----------------+-----
John | Basket Weaving | 12
Sally | Basket Weaving | 6
Smith | Fencing | 12
用户可以请求1,2或更多列,并且他们请求的列可能不同。例如,用户可以请求Specialty == Basket Weaving
和Rank == 12. What I do currently is gather the user's request and create a list of
KeyValuePair where the
Key is the column name and the
Value`是列的所需值的条目:
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");
使用此KeyValuePair
列表,我如何能够最简洁地选择符合所有条件的数据库项目?
using (var db = new MyDbContext)
{
// Where each column name (key) in criteria matches
// the corresponding value in criteria.
var query = db.MyTable.Where(???);
}
编辑:如果我能提供帮助,我想使用EntityFramework而不是原始SQL。
更新3 :我越来越近了。我已经发现了一种在我下载后使用LINQ的方法 表中的所有值。这显然不是超级理想,因为它下载 表中的一切。所以我想最后一步是找出一种方法 我不必每次都下载整个表格。以下是我正在做的事情的解释:
表格中的每一行
db.MyTable.ToList().Where(e => ...
我列出了表示列是否符合条件的bool列表。
criteria.Select(c => e.GetType()?.GetProperty(c.Key)?.GetValue(e)?.ToString() == c.Value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Basically just gets the value of specific column
by string
然后我检查一下这个bool列表是否都是真的
.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 :(得分: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)");
另一种解决方案是使用 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
修改:回答评论
注意,参数值类型必须与属性类型兼容。例如,如果“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;
}
}
答案 1 :(得分: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:
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)
要在UserSearch类中填充这些列表:
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。这样,您可以轻松生成动态选择和where子句。您甚至可以查询表中的列列表,以动态确定哪些选项可用于选择/过滤。
答案 2 :(得分:0)
在此之后不确定你是什么。但这应该给你一个想法。
Fields 0,2
我甚至不确定为什么你甚至不得不使用List。因为List需要迭代。您可以先使用Key,将First条件和Value作为最后一个条件,以避免KeyValuePair的List。
答案 3 :(得分:0)
精细。让我给我两分钱。如果要使用动态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);
这将为您提供一个强大的动态表达式,其中包含两个条件,它们之间使用AND运算符,以便在LINQ的where扩展方法中使用。现在,您可以根据自己的策略根据需要传递参数。例如在params string []或键值对列表中......无关紧要。
你可以看到这里没有任何东西被修复..它完全是动态的,比反射更快,你可以制作尽可能多的表达式和许多标准......
答案 4 :(得分: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
{
// ...
}
按扩展名过滤
public static class MyTableExtensions
{
public static IQueryable<TEntity> FilterMyTablesByName<TEntity>(
this IQueryable<TEntity> query, string[] names)
where TEntity : class, IMyTable
{
if (query == null) { throw new ArgumentNullException(nameof(query)); }
if (!names.Any() || names.All(string.IsNullOrWhiteSpace))
{
return query; // Unmodified
}
// Modified
return query.Where(x => names.Contains(x.Name));
}
// Replicate per array/filter...
}
此外,在EF查询中使用Contains(...)或Any(...)会出现严重的性能问题。使用Predicate Builders的方法要快得多。这是一个带有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));
}
输出&#34; IN&#34;查询的语法非常快:
WHERE ID IN [1,2,3,4,5]