动态 Linq 的可视化表达式生成器






4.73/5 (13投票s)
让用户使用您可以在应用程序中包含的 MVVM WPF 工具创建自己的 Linq 过滤器
已更新!源代码已更新,使动态类型更加健壮!
目录

引言
LINQ 是一种以类型安全、直观且极具表达力的方式声明性地过滤和查询数据的绝佳方法。但是,您的用户不应该每次有新的过滤或查询数据的方式时都必须联系您。
因此,这里提供了一种让您的用户过滤他们数据的方法,无论数据来自何处。无论是 Linq2SQL 还是内存结构,现在您的用户都可以拥有这种能力。
这是您可以为用户添加的最有价值的功能,只需 **4 行代码**。
我还建议您实际阅读代码。整个项目只有 233 行代码,包括 XAML。
Using the Code
真正的功能位于 ViewModel
中的两个类中。UserControl
只是 ExpressionList
的一个经过美化的 DataTemplate
。

我知道你们大多数人都想知道这个好东西是如何工作的,以便你们可以在自己的解决方案中使用相同的技术。但是,您可以直接将 UserControl
添加到 XAML 中,并将其绑定到 ExpressionList
来使用它,所以我就从这里开始。
<uc:FilterUserControl DataContext="{Binding ExpressionList1}" />
在您的 ViewModel
的某个地方
public ExpressionList ExpressionList1 { get; set; }
ExpressionList1 = new ExpressionList() {Type=typeof(Contact)} ;
然后,一旦您的用户选择了所有过滤器,您就可以这样获得最终查询:
var filteredContacts =
contacts.Where(ExpressionList1.GetCompleteExpression<Contact>());
背景
我首先尝试了许多其他动态 LINQ 解决方案。有一个“DynamicLinq
”项目,在 ScottGu 的博客 中进行了介绍,该项目允许您解析字符串中的表达式。因此,您的用户可以在文本框中编写自己的表达式,然后您可以将其应用于您的 LINQ。我从他们的实现中学到了很多关于表达式的知识。
然后,有一个我差点使用的解决方案 - VB 工具中的动态 LINQ 工具。 它几乎完全满足了我的需求,您会注意到它与我今天展示的工具非常相似。最大的问题是它为 WinForms 编写的,并且功能与表示层过度耦合。我实际上需要 MVVM(之前是 MVP)的一个主要好处——在 UI 消失时仍然能够持久化状态,并且可以轻松创建默认和已保存的过滤器集。
究竟发生了什么?
正如我之前所说,真正的功能位于 ViewModel
中的两个类中(我将内置的 Expression
类视为我的模型)
ExpressionVM
包含创建具有一个比较的表达式所需的数据和功能。ExpressionList
是ExpressionVM
的一个可观察集合,其中有一个属性用于从所有子表达式创建 lambda 表达式。
ExpressionList
仅仅是一个 ObservableCollection<ExpressionVM>
,并带有两个重要属性:
Type Type
- 用于生成表达式,并用于填充ExpressionVM
中的AvailableProperties
。Expression CompleteExpression
- 将所有ExpressionVM
中的Expressions
组合成一个 lambda 表达式。
ExpressionVM
的成员是:
Type ObjectType
- 被过滤的类型(通常从ExpressionList
设置)PropertyInfo PropertyInfo
- 关于用户选择在此行中进行比较的属性的信息。string PropertyName
- (只读) 来自PropertyInfo
。Type PropertyType
- (只读) 来自PropertyInfo
。CombineOperator
- 由用户选择,用于确定如何将此行与上一行组合。CompareOperator
- 由用户选择,用于确定如何将属性与常量进行比较。object Value
- 用于比较的常量。AvailableCombineOperators
,AvailableCompareOperators
,AvailableProperties
- 用于填充ComboBoxes
的查找列表。
GetSupportedTypes
- 因此我们不会给用户过滤图像的选项 :)MakeExpression
- 根据用户的选择创建表达式。
解决方案的核心内容
最有趣的代码包含在两个函数 MakeExpression
和 CompleteExpression
的 getter 中。
public LambdaExpression MakeExpression(ParameterExpression paramExpr = null)
{
if(paramExpr == null) paramExpr = Expression.Parameter(ObjectType, "left");
var callExpr = Expression.MakeMemberAccess(paramExpr, PropertyInfo);
var valueExpr = Expression.Constant(Value, PropertyType);
var expr = Expression.MakeBinary((ExpressionType)CompareOperator,
callExpr, valueExpr);
return Expression.Lambda( expr, paramExpr);
}
public Expression CompleteExpression
{
get
{
var paramExp = Expression.Parameter(Type, "left");
if (this.Count == 0) return Expression.Lambda
(Expression.Constant(true), paramExp);
LambdaExpression lambda1 = this.First().MakeExpression(paramExp);
var ret = lambda1.Body;
foreach (var c in this.Skip(1))
ret = Expression.MakeBinary((ExpressionType)c.CombineOperator,
ret, c.MakeExpression(paramExp).Body);
return Expression.Lambda(ret, paramExp);
}
}
CompleteExpression
使用 ObjectType
创建一个输入参数,并使用第一个 ExpressionVM
创建一个 lambda。然后它遍历其余的 ExpressionVM
,根据它们的 CombineOperator
将它们从 MakeExpression
获取的表达式附加到 lambda。
其他有趣的代码片段
我为每个 ComboBox
提供了查找列表。
每次 ObjectType
更改时,AvailableProperties
都会被填充。
set {
_ObjectType = value;
AvailableProperties = from p in value.GetProperties()
where GetSupportedTypes().Contains(p.PropertyType)
select p;
OnPropertyChanged("ObjectType");
}
CombineOperator
和 CompareOperator
都是 enum
。因此,它们的列表会像这样即时生成:
public Array AvailableCompareOperators
{
get { return Enum.GetValues(typeof(ComparisonOperators)); }
}
待办事项
是的,还有很多工作要做,并且还有很大的改进空间。
这里有一个我想到的小列表:
- 添加更多运算符
- 允许 AND 和 OR 嵌套
- 允许选择子属性
- 让 WPF 根据
PropertyType
选择专门的模板