基于字典元素的动态 lambda 参数
Dynamic lambda parameters based on dictionary elements
我的计划是创建一个查询,但参数基于一个字典。
字典包含字符串键和布尔值。
可以是字典中的2个或3个或更多项。
Dictionary<string, bool> items = new Dictionary<string, bool>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", true);
这是我要发送的词典,我想基于此动态创建一个查询,例如
.Where(e => e.CostFree == true || Visible == true || Closed == true)
但字典可以包含 2、3 或 4 个项目。
我该如何解决这个问题?
提前致谢
这样做的简单(但不够优雅)的方法是链接一系列 Union
语句。您可以使用查找字典,其中包含与您的字符串匹配的键和包含适当谓词的值。
下面是一个使用扩展方法的例子:
static public IQueryable<Foo> WithFlags(this IQueryable<Foo> source, string[] flags)
{
var map = new Dictionary<string, Expression<Func<Foo, bool>>>()
{
{ "Closed", x => x.Closed },
{ "CostFree", x => x.CostFree },
{ "Visible", x => x.Visible }
};
//Start with a query that returns nothing
var query = source.Where(x => false);
//For each flag supplied by the caller, add an additional set
foreach (var flag in flags)
{
query = query.Union(query.Where(map[flag]));
}
return query;
}
使用:
var results = DbContext.Foo.WithFlags( new string[] { "Closed", "Visible" }).ToList();
更优雅的方法是构建一个包含 Or
逻辑的谓词表达式。这会有点牵扯。我建议寻找第三方工具包。参见 this answer。
可以通过 System.Linq.Expressions.Expression
class 上公开的静态方法轻松构建 LINQ 表达式。
这是一个包含您的需求的示例,假设您要针对命名 SomeClass
构建表达式的实体
[TestMethod]
public void MyTestMethod()
{
var testData = new List<SomeClass>()
{
new SomeClass() {Id=1, CostFree = false, Closed='N', Visible=false},
new SomeClass() {Id=2, CostFree = true, Closed='N', Visible=false}, // expect only this one matching
};
var items = new Dictionary<string, object>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", 'Y');
// this one will be the "e" in "e => e.CostFree == true || Visible == true || Closed == 'Y'"
var paramExpression = Expression.Parameter(typeof(SomeClass));
// lets construct the body ("e.CostFree == true || Visible == true || Closed == 'Y'") part step-by-step
// the parts consists of binary "equals" expressions combined via logical "or" expression
var bodyExpression = (Expression)null;
foreach(var kvp in items)
{
// get the named property ("CostFree", ...) reference of paramExpression. this is the left hand side of "equals"
var propertyExpression = Expression.PropertyOrField(paramExpression, kvp.Key);
// get the constant with appropriate value to place on right hand side of "equals"
var constantExpression = Expression.Constant(kvp.Value, kvp.Value.GetType());
// combine them into "equals"
var binaryEqualsExpression = Expression.Equal(propertyExpression, constantExpression);
if (bodyExpression == null)
{
bodyExpression = binaryEqualsExpression;
}
else
{
// combine each "equals" parts with logical "or"
bodyExpression = Expression.OrElse(bodyExpression, binaryEqualsExpression);
}
}
// now construct the whole lambda...
var lambdaExpression = Expression.Lambda<Func<SomeClass, bool>>(bodyExpression, paramExpression);
// ...and make it useable in .Where()
var compiledExpression = lambdaExpression.Compile();
// lets execute in on our test data
var r = testData.Where(compiledExpression);
// only #2 should match
Assert.AreEqual(2, r.Single().Id);
}
更新:
我更改了解决方案:
items
值的类型为 object
constantExpression
遵循值的类型。
这样字典可以包含其他名称-值对和解决方案
仍然有效。字典内容规则:键必须匹配 SomeClass
属性 名称和值必须匹配给定的 属性 类型。
我的计划是创建一个查询,但参数基于一个字典。 字典包含字符串键和布尔值。 可以是字典中的2个或3个或更多项。
Dictionary<string, bool> items = new Dictionary<string, bool>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", true);
这是我要发送的词典,我想基于此动态创建一个查询,例如
.Where(e => e.CostFree == true || Visible == true || Closed == true)
但字典可以包含 2、3 或 4 个项目。
我该如何解决这个问题? 提前致谢
这样做的简单(但不够优雅)的方法是链接一系列 Union
语句。您可以使用查找字典,其中包含与您的字符串匹配的键和包含适当谓词的值。
下面是一个使用扩展方法的例子:
static public IQueryable<Foo> WithFlags(this IQueryable<Foo> source, string[] flags)
{
var map = new Dictionary<string, Expression<Func<Foo, bool>>>()
{
{ "Closed", x => x.Closed },
{ "CostFree", x => x.CostFree },
{ "Visible", x => x.Visible }
};
//Start with a query that returns nothing
var query = source.Where(x => false);
//For each flag supplied by the caller, add an additional set
foreach (var flag in flags)
{
query = query.Union(query.Where(map[flag]));
}
return query;
}
使用:
var results = DbContext.Foo.WithFlags( new string[] { "Closed", "Visible" }).ToList();
更优雅的方法是构建一个包含 Or
逻辑的谓词表达式。这会有点牵扯。我建议寻找第三方工具包。参见 this answer。
可以通过 System.Linq.Expressions.Expression
class 上公开的静态方法轻松构建 LINQ 表达式。
这是一个包含您的需求的示例,假设您要针对命名 SomeClass
[TestMethod]
public void MyTestMethod()
{
var testData = new List<SomeClass>()
{
new SomeClass() {Id=1, CostFree = false, Closed='N', Visible=false},
new SomeClass() {Id=2, CostFree = true, Closed='N', Visible=false}, // expect only this one matching
};
var items = new Dictionary<string, object>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", 'Y');
// this one will be the "e" in "e => e.CostFree == true || Visible == true || Closed == 'Y'"
var paramExpression = Expression.Parameter(typeof(SomeClass));
// lets construct the body ("e.CostFree == true || Visible == true || Closed == 'Y'") part step-by-step
// the parts consists of binary "equals" expressions combined via logical "or" expression
var bodyExpression = (Expression)null;
foreach(var kvp in items)
{
// get the named property ("CostFree", ...) reference of paramExpression. this is the left hand side of "equals"
var propertyExpression = Expression.PropertyOrField(paramExpression, kvp.Key);
// get the constant with appropriate value to place on right hand side of "equals"
var constantExpression = Expression.Constant(kvp.Value, kvp.Value.GetType());
// combine them into "equals"
var binaryEqualsExpression = Expression.Equal(propertyExpression, constantExpression);
if (bodyExpression == null)
{
bodyExpression = binaryEqualsExpression;
}
else
{
// combine each "equals" parts with logical "or"
bodyExpression = Expression.OrElse(bodyExpression, binaryEqualsExpression);
}
}
// now construct the whole lambda...
var lambdaExpression = Expression.Lambda<Func<SomeClass, bool>>(bodyExpression, paramExpression);
// ...and make it useable in .Where()
var compiledExpression = lambdaExpression.Compile();
// lets execute in on our test data
var r = testData.Where(compiledExpression);
// only #2 should match
Assert.AreEqual(2, r.Single().Id);
}
更新: 我更改了解决方案:
items
值的类型为object
constantExpression
遵循值的类型。
这样字典可以包含其他名称-值对和解决方案
仍然有效。字典内容规则:键必须匹配 SomeClass
属性 名称和值必须匹配给定的 属性 类型。