前文介绍了C#中表达式树的基本知识,在实际中,表达式树有很多用法,这里举几个例子,说明如何使用表达式树构建动态查询,从而扩展LINQ的查询方法。
在LINQ中,只要数据源实现了IQuerable<T>接口,表达式树就可以用来表示结构化查询。比如,LINQ提供了用来查询关系数据源的IQueryable<T>接口的实现,C#编译器在执行这类数据源查询时,会在运行时生成表达式树,然后,查询会遍历表达式树的数据结构,然后将其转换成针对特定数据源的合适的查询语言。
下面的几个例子演示了如何使用表达式树动态生成查询。
Example 1:动态生成Where和OrderBy
这个例子是MSDN上的例子,平常在C#中我们一般直接写LINQ代码,比如下面这个:
1
2
|
companies.Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) .OrderBy(company => company) |
对集合进行查询,然后排序等。这个时固定化了数据源,在有些时候,我们需要把这个查询“泛型化”,这就需要能够动态构造查询的能力,恰好我们可以使用表达式树,动态构建查询。
在System.Linq.Expression命名空间下有一些工厂方法能够用来表示各种查询,从而来组合出上述的查询条件。首先构造出一个表达式树,然后将表达式树传给要查询数据的CreateQuery<IElement>(Expression)方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
//数据源 string [] companies = { "Consolidated Messenger" , "Alpine Ski House" , "Southridge Video" , "City Power & Light" , "Coho Winery" , "Wide World Importers" , "Graphic Design Institute" , "Adventure Works" , "Humongous Insurance" , "Woodgrove Bank" , "Margie's Travel" , "Northwind Traders" , "Blue Yonder Airlines" , "Trey Research" , "The Phone Company" , "Wingtip Toys" , "Lucerne Publishing" , "Fourth Coffee" }; IQueryable< string > queryableData = companies.AsQueryable(); //company 参数 ParameterExpression pe = Expression.Parameter( typeof ( string ), "company" ); // ***** 开始构造 Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) ***** //构造表达式 company.ToLower() == "coho winery" Expression left = Expression.Call(pe, typeof ( string ).GetMethod( "ToLower" , System.Type.EmptyTypes)); Expression right = Expression.Constant( "coho winery" ); Expression e1 = Expression.Equal(left, right); //构造表达式 company.Length > 16 left = Expression.Property(pe, typeof ( string ).GetProperty( "Length" )); right = Expression.Constant(16, typeof ( int )); Expression e2 = Expression.GreaterThan(left, right); //构造上述两个表达式的或 // '(company.ToLower() == "coho winery" || company.Length > 16)'. Expression predictBody = Expression.OrElse(e1, e2); //构造where表达式 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))' MethodCallExpression whereCallExpression = Expression.Call( typeof (Queryable), "Where" , new Type[] { queryableData.ElementType }, queryableData.Expression, Expression.Lambda<Func< string , bool >>(predictBody, new ParameterExpression[] { pe })); // ***** Where 部分构造完成 ***** // *****开始构造 OrderBy(company => company) ***** // 构造表达式树,表示 'whereCallExpression.OrderBy(company => company)' MethodCallExpression orderByCallExpression = Expression.Call( typeof (Queryable), "OrderBy" , new Type[] { queryableData.ElementType, queryableData.ElementType }, whereCallExpression, Expression.Lambda<Func< string , string >>(pe, new ParameterExpression[] { pe })); //***** OrderBy 构造完成 ***** //创建查询 IQueryable< string > results = queryableData.Provider.CreateQuery< string >(orderByCallExpression); foreach ( string company in results) { Console.WriteLine(company); } |
上面代码中,在传递到Queryable.Where方法中,使用了固定数量的表达式,在实际中,可以动态根据用户输入,来合并各种查询操作。
Example 2: 扩展in查询
LINQ中,并没有提供In的操作,比如要查询xx 是否在["xx1","xx2"...]中时,需要手动编写SQL使用in,并将array拼成分割的字符串。如果使用LINQ,则可以变成xx==xx1,或者xx==xx2,实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static Expression<Func<TElement, bool >> BuildWhereInExpression<TElement, TValue>( Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values) { ParameterExpression p = propertySelector.Parameters.Single(); if (!values.Any()) return e => false ; var equals = values.Select(value => (Expression)Expression.Equal(propertySelector.Body, Expression.Constant(value, typeof (TValue)))); var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal)); return Expression.Lambda<Func<TElement, bool >>(body, p); } public static IQueryable<TElement> WhereIn<TElement, TValue>( this IQueryable<TElement> source, Expression<Func<TElement, TValue>> propertySelector, params TValue[] values) { return source.Where(BuildWhereInExpression(propertySelector, values)); } |
比如上例中,我们要判断在集合中是否存在以下数据,则可以直接使用where in
1
2
3
4
5
6
7
8
9
10
11
12
13
|
string [] companies = { "Consolidated Messenger" , "Alpine Ski House" , "Southridge Video" , "City Power & Light" , "Coho Winery" , "Wide World Importers" , "Graphic Design Institute" , "Adventure Works" , "Humongous Insurance" , "Woodgrove Bank" , "Margie's Travel" , "Northwind Traders" , "Blue Yonder Airlines" , "Trey Research" , "The Phone Company" , "Wingtip Toys" , "Lucerne Publishing" , "Fourth Coffee" }; string [] t = new string [] { "Consolidated Messenger" , "Alpine Ski House 1" }; IQueryable< string > results = companies.AsQueryable().WhereIn(x => x, t); foreach ( string company in results) { Console.WriteLine(company); } |
Example 3:封装分页逻辑
在很多地方都会用到分页,所以我们可以把查询排序跟分页封装到一起,这样用的时候就很方便,这里针对查询和排序,新建一个实体对象。
1
2
3
4
5
6
7
8
9
10
|
public class QueryOptions { public int CurrentPage { get ; set ; } = 1; public int PageSize { get ; set ; } = 10; public string OrderPropertyName { get ; set ; } public bool DescendingOrder { get ; set ; } public string SearchPropertyName { get ; set ; } public string SearchTerm { get ; set ; } } |
上面这个对象,包含了分页相关设置,以及查询和排序字段及属性。
将排序逻辑封装在了PageList对象中,该对象继承自List,每次分页就返回分页后的数据放在PageList中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public class PagedList<T> : List<T> { public int CurrentPage { get ; set ; } public int PageSize { get ; set ; } public int TotalPages { get ; set ; } public QueryOptions Options { get ; set ; } public bool HasPreviousPage => CurrentPage > 1; public bool HasNextPage => CurrentPage < TotalPages; public PagedList(IQueryable<T> query, QueryOptions o) { CurrentPage = o.CurrentPage; PageSize = o.PageSize; Options = o; if (o != null ) { if (! string .IsNullOrEmpty(o.OrderPropertyName)) { query = Order(query, o.OrderPropertyName, o.DescendingOrder); } if (! string .IsNullOrEmpty(o.SearchPropertyName) && ! string .IsNullOrEmpty(o.SearchTerm)) { query = Search(query, o.SearchPropertyName, o.SearchTerm); } } TotalPages = query.Count() / PageSize; AddRange(query.Skip((CurrentPage - 1) * PageSize).Take(PageSize)); } private IQueryable<T> Search(IQueryable<T> query, string searchPropertyName, string searchItem) { var parameter = Expression.Parameter( typeof (T), "x" ); var source = searchPropertyName.Split( "." ).Aggregate((Expression)parameter, Expression.Property); var body = Expression.Call(source, "Contains" , Type.EmptyTypes, Expression.Constant(searchItem, typeof ( string ))); var lambda = Expression.Lambda<Func<T, bool >>(body, parameter); return query.Where(lambda); } private IQueryable<T> Order(IQueryable<T> query, string orderPropertyName, bool descendingOrder) { var parameter = Expression.Parameter( typeof (T), "x" ); var source = orderPropertyName.Split( "." ).Aggregate((Expression)parameter, Expression.Property); var lambda = Expression.Lambda( typeof (Func<,>).MakeGenericType( typeof (T), source.Type), source, parameter); return typeof (Queryable).GetMethods().Single(method => method.Name == (descendingOrder ? "OrderByDescending" : "OrderBy" ) && method.IsGenericMethodDefinition && method.GetGenericArguments().Length == 2 && method.GetParameters().Length == 2) .MakeGenericMethod( typeof (T), source.Type) .Invoke( null , new object [] { query, lambda }) as IQueryable<T>; } } |
可以看到,在where和order部分,我们通过查询表达式,动态构造了这两部分的逻辑。使用的时候,非常方便:
1
2
3
4
|
public PagedList<Category> GetCategories(QueryOptions options) { return new PagedList<Category>(context.Categories, options); } |
这里context.Categories时DbSet集合,QueryOptions是用户传入的分页选项。每次输入查询排序及分页选项,就把分好的放在PageList里面返回。
Example 4 简化数据绑定
在Windows Form中,所有的控件都继承自Control基类,它有一个DataBindings属性,可以用来绑定对象,这样就能自动更新。通常,在需要给一些对象赋值,或者保存空间里设置的值到配置文件时,我们可以使用数据绑定,而不是手动的去一个一个的赋值。但DataBindings的原型方法Add为如下,比如我要将某个空间的值绑定到配置对象里,代码如下:
1
|
textBoxCustomerName.DataBindings.Add( "Text" , bindingSource, "CustomerName" ); |
上述代码将TextBox控件的Text属性跟bingSource对象的CustomerName属性进行了绑定,可以看到以上代码有很多地方需要手写字符串:TextBox控件的属性“Text”,bingSource对象的“CustomerName”属性,非常不友好,而且容易出错,一看这种需要手打字符串的地方,就是"Bad Smell",需要优化。。
可以感觉到,如果我们直接能像在Visual Studio里面那样,直接使用对象.属性的方式来赋值,比如这里直接用textBoxCustomerName.Text,和bindingSource.CustomerName,来替代两个手动输入的地方,整个体验就要好很多,比如,如果将上述代码写为下面的形式:
1
|
dataSource.CreateBinding(textBoxCustomerName, ctl => ctl.Text, data => data.Name); |
就要好很多。其实现方式为,我们先定义一个绑定的类,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class FlexBindingSource<TDataSource> { private BindingSource _winFormsBindingSource; /// <summary> /// /// </summary> /// <param name="source">object entity binding to the control</param> public FlexBindingSource(TDataSource source) { _winFormsBindingSource = new BindingSource(); _winFormsBindingSource.DataSource = source; } /// <summary> /// Creates a DataBinding between a control property and a datasource property /// </summary> /// <typeparam name="TControl">The control type, must derive from System.Winforms.Control</typeparam> /// <param name="controlInstance">The control instance on wich the databinding will be added</param> /// <param name="controlPropertyAccessor">A lambda expression which specifies the control property to be databounded (something like textboxCtl => textboxCtl.Text)</param> /// <param name="datasourceMemberAccesor">A lambda expression which specifies the datasource property (something like datasource => datasource.Property) </param> /// <param name="dataSourceUpdateMode">See control.DataBindings.Add method </param> public void CreateBinding<TControl>(TControl controlInstance, Expression<Func<TControl, object >> controlPropertyAccessor, Expression<Func<TDataSource, object >> datasourceMemberAccesor, DataSourceUpdateMode dataSourceUpdateMode = DataSourceUpdateMode.OnValidation) where TControl : Control { string controlPropertyName = FlexReflector.GetPropertyName(controlPropertyAccessor); string sourcePropertyName = FlexReflector.GetPropertyName(datasourceMemberAccesor); controlInstance.DataBindings.Add(controlPropertyName, _winFormsBindingSource, sourcePropertyName, true , dataSourceUpdateMode); } } |
这里面,构造函数里面的source,就是需要绑定的实体对象;在CreateBinding方法中,我们通过在FlexReflector在表达式中获取待绑定的字符串,然后调用传进去的Control对象的绑定方法来实现绑定。FlexReflector方法内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private static MemberExpression GetMemberExpression(Expression selector) { LambdaExpression lambdaExpression = selector as LambdaExpression; if (lambdaExpression == null ) { throw new ArgumentNullException( "The selector is not a valid lambda expression." ); } MemberExpression memberExpression = null ; if (lambdaExpression.Body.NodeType == ExpressionType.Convert) { memberExpression = ((UnaryExpression)lambdaExpression.Body).Operand as MemberExpression; } else if (lambdaExpression.Body.NodeType == ExpressionType.MemberAccess) { memberExpression = lambdaExpression.Body as MemberExpression; } if (memberExpression == null ) { throw new ArgumentException( "The property is not a valid property to be extracted by a lambda expression." ); } return memberExpression; } |
使用方法很简单,首先定义一个绑定源的实例。
1
2
|
private FlexBindingSource<CustomerInfo> dataSource; dataSource = new FlexBindingSource<CustomerInfo>(customerInstance); |
然后就可以使用CreateBinding方法直接绑定了。
1
|
dataSource.CreateBinding(textBoxCustomerName, ctl => ctl.Text, data => data.Name); |
以上就是C#用表达式树构建动态查询的方法的详细内容,更多关于c# 构建动态查询的资料请关注服务器之家其它相关文章!
原文链接:https://www.yycoding.xyz/post/2020/9/22/an-introduction-to-csharp-expression-tree-build-dynamic-queries