不确定这是否完全可能;我发现了一些关于表达式和谓词构建器的东西,但到目前为止,还没有任何东西可以让您在不事先了解它们的情况下运行任意查询。
基本上,我有一个来自大型SQL数据库的对象集合,我正在构建一个网页(ASP.NET MVC 4),允许用户显示和筛选这些对象。用户将要输入的查询的复杂性各不相同。让他们输入这些查询的最简单、最整洁的方法类似于Visual Studio TFS插件让你搜索工作项的方式:一个条件表,你可以在其中不断添加行。您为联接条件选择"and"或"or",然后选择一个字段,输入一个值,然后选择是否需要与之匹配的内容:
1. show items where [Field] [is|is not] [value]
2. [and|or] [Field] [is|is not] [value]
3. [and|or] [Field] [is|is not] [value]
etc...
把它变成LINQ风格的东西,我可以把.ToList()
放在它的末尾,最简单的方法是什么?到目前为止,我提出的唯一解决方案是使用一个相当大且丑陋的开关块,其中包含匹配各种字段的案例,并在.Where()
上添加,但为了允许用户选择"或"作为条件,我最终会做这样的事情:
- 当条件为AND时:
- 使用大开关匹配字段
query = query.Where(ThisField == value);
- 当您遇到OR条件时:
- 将当前结果附加到临时列表
- 来自完整未筛选列表的新查询
- 使用大开关匹配字段
query = fullList.Where(ThisField == value);
- 像以前一样继续
- 当条件用完时,将当前结果集附加到一直使用的临时列表中,然后返回该列表
这似乎没有我想要的那么优雅。
你可以这样做:
class Program
{
public enum Operator
{
And,
Or
}
public class Condition
{
public Operator Operator { get; set; }
public string FieldName { get; set; }
public object Value { get; set; }
}
public class DatabaseRow
{
public int A { get; set; }
public string B { get; set; }
}
static void Main(string[] args)
{
var conditions = new List<Condition>
{
new Condition { Operator = Operator.And, FieldName = "A", Value = 1 },
new Condition { Operator = Operator.And, FieldName = "B", Value = "Asger" },
new Condition { Operator = Operator.Or, FieldName = "A", Value = 2 },
};
var parameter = Expression.Parameter(typeof (DatabaseRow), "x");
var currentExpr = MakeExpression(conditions.First(), parameter);
foreach (var condition in conditions.Skip(1))
{
var nextExpr = MakeExpression(condition, parameter);
switch (condition.Operator)
{
case Operator.And:
currentExpr = Expression.And(currentExpr, nextExpr);
break;
case Operator.Or:
currentExpr = Expression.Or(currentExpr, nextExpr);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
var predicate = Expression.Lambda<Func<DatabaseRow, bool>>(currentExpr, parameter).Compile();
var input = new[]
{
new DatabaseRow {A = 1, B = "Asger"},
new DatabaseRow {A = 2, B = "Hans"},
new DatabaseRow {A = 3, B = "Grethe"}
};
var results = input.Where(predicate).ToList();
}
static BinaryExpression MakeExpression(Condition condition, ParameterExpression parameter)
{
return Expression.Equal(
Expression.MakeMemberAccess(parameter, typeof (DatabaseRow).GetMember(condition.FieldName)[0]),
Expression.Constant(condition.Value));
}
}
这假设您有一个类作为具有正确类型的数据库行的模型。然后,您可以通过regex将条件解析为上面显示的类型化条件列表,所提供的代码可以将其转换为表达式树。生成的表达式可以编译并运行(如图所示),也可以转换为SQL(只需将谓词填充到IQueryable.Where中)。
您可以使用LINQKit中的PredicateBuilder
来执行此操作。使用它的And()
和Or()
扩展方法,可以为查询构建表达式树。然后可以使用该表达式树作为Where()
的条件。您还需要调用AsExpandable()
或query
,或者对创建的表达式调用Expand()
。
您可以使用Dynamic Linq来动态添加条件。
请参阅http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
我修改了@asgerhallas answer以使用用表示的子属性。并根据需要转换为适当类型的
static BinaryExpression MakeExpression(Condition condition, ParameterExpression parameter)
{
var memberPath = condition.FieldName.Split(".");
var left = Expression.Property(parameter, memberPath[0]);
foreach (var mp in memberPath.Skip(1))
{
left = Expression.Property(left, ((PropertyInfo)left.Member).PropertyType.GetProperty(mp));
}
var rightType = ((PropertyInfo)left.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(rightType);
if (!converter.CanConvertFrom(typeof(string))) throw new NotSupportedException();
var rightValue = converter.ConvertFrom(condition.Value);
return Expression.Equal(
left,
Expression.Constant(rightValue));
}
然后您可以使用";SomeProperty.SomeThing";以及";任意字符串";它将基于子内容创建一个表达式,如果可以的话,将字符串转换为适当的值类型。