C#用表达式树构建动态查询的方法
前文介绍了C#中表达式树的基本知识,在实际中,表达式树有很多用法,这里举几个例子,说明如何使用表达式树构建动态查询,从而扩展LINQ的查询方法。
在LINQ中,只要数据源实现了IQuerable
下面的几个例子演示了如何使用表达式树动态生成查询。
Example1:动态生成Where和OrderBy
这个例子是MSDN上的例子,平常在C#中我们一般直接写LINQ代码,比如下面这个:
companies.Where(company=>(company.ToLower()=="cohowinery"||company.Length>16)) .OrderBy(company=>company)
对集合进行查询,然后排序等。这个时固定化了数据源,在有些时候,我们需要把这个查询“泛型化”,这就需要能够动态构造查询的能力,恰好我们可以使用表达式树,动态构建查询。
在System.Linq.Expression命名空间下有一些工厂方法能够用来表示各种查询,从而来组合出上述的查询条件。首先构造出一个表达式树,然后将表达式树传给要查询数据的CreateQuery
//数据源 string[]companies={"ConsolidatedMessenger","AlpineSkiHouse","SouthridgeVideo","CityPower&Light", "CohoWinery","WideWorldImporters","GraphicDesignInstitute","AdventureWorks", "HumongousInsurance","WoodgroveBank","Margie'sTravel","NorthwindTraders", "BlueYonderAirlines","TreyResearch","ThePhoneCompany", "WingtipToys","LucernePublishing","FourthCoffee"}; IQueryablequeryableData=companies.AsQueryable(); //company参数 ParameterExpressionpe=Expression.Parameter(typeof(string),"company"); //*****开始构造Where(company=>(company.ToLower()=="cohowinery"||company.Length>16))***** //构造表达式company.ToLower()=="cohowinery" Expressionleft=Expression.Call(pe,typeof(string).GetMethod("ToLower",System.Type.EmptyTypes)); Expressionright=Expression.Constant("cohowinery"); Expressione1=Expression.Equal(left,right); //构造表达式company.Length>16 left=Expression.Property(pe,typeof(string).GetProperty("Length")); right=Expression.Constant(16,typeof(int)); Expressione2=Expression.GreaterThan(left,right); //构造上述两个表达式的或 //'(company.ToLower()=="cohowinery"||company.Length>16)'. ExpressionpredictBody=Expression.OrElse(e1,e2); //构造where表达式'queryableData.Where(company=>(company.ToLower()=="cohowinery"||company.Length>16))' MethodCallExpressionwhereCallExpression=Expression.Call( typeof(Queryable), "Where", newType[]{queryableData.ElementType}, queryableData.Expression, Expression.Lambda >(predictBody,newParameterExpression[]{pe})); //*****Where部分构造完成***** //*****开始构造OrderBy(company=>company)***** //构造表达式树,表示'whereCallExpression.OrderBy(company=>company)' MethodCallExpressionorderByCallExpression=Expression.Call( typeof(Queryable), "OrderBy", newType[]{queryableData.ElementType,queryableData.ElementType}, whereCallExpression, Expression.Lambda >(pe,newParameterExpression[]{pe})); //*****OrderBy构造完成***** //创建查询 IQueryable results=queryableData.Provider.CreateQuery (orderByCallExpression); foreach(stringcompanyinresults) { Console.WriteLine(company); }
上面代码中,在传递到Queryable.Where方法中,使用了固定数量的表达式,在实际中,可以动态根据用户输入,来合并各种查询操作。
Example2:扩展in查询
LINQ中,并没有提供In的操作,比如要查询xx是否在["xx1","xx2"...]中时,需要手动编写SQL使用in,并将array拼成分割的字符串。如果使用LINQ,则可以变成xx==xx1,或者xx==xx2,实现如下:
publicstaticExpression>BuildWhereInExpression ( Expression >propertySelector,IEnumerable values) { ParameterExpressionp=propertySelector.Parameters.Single(); if(!values.Any()) returne=>false; varequals=values.Select(value=>(Expression)Expression.Equal(propertySelector.Body, Expression.Constant(value,typeof(TValue)))); varbody=equals.Aggregate ((accumulate,equal)=>Expression.Or(accumulate,equal)); returnExpression.Lambda >(body,p); } publicstaticIQueryable WhereIn (thisIQueryable source, Expression >propertySelector,paramsTValue[]values) { returnsource.Where(BuildWhereInExpression(propertySelector,values)); }
比如上例中,我们要判断在集合中是否存在以下数据,则可以直接使用wherein
string[]companies={"ConsolidatedMessenger","AlpineSkiHouse","SouthridgeVideo","CityPower&Light", "CohoWinery","WideWorldImporters","GraphicDesignInstitute","AdventureWorks", "HumongousInsurance","WoodgroveBank","Margie'sTravel","NorthwindTraders", "BlueYonderAirlines","TreyResearch","ThePhoneCompany", "WingtipToys","LucernePublishing","FourthCoffee"}; string[]t=newstring[]{"ConsolidatedMessenger","AlpineSkiHouse1"}; IQueryableresults=companies.AsQueryable().WhereIn(x=>x,t); foreach(stringcompanyinresults) { Console.WriteLine(company); }
Example3:封装分页逻辑
在很多地方都会用到分页,所以我们可以把查询排序跟分页封装到一起,这样用的时候就很方便,这里针对查询和排序,新建一个实体对象。
publicclassQueryOptions { publicintCurrentPage{get;set;}=1; publicintPageSize{get;set;}=10; publicstringOrderPropertyName{get;set;} publicboolDescendingOrder{get;set;} publicstringSearchPropertyName{get;set;} publicstringSearchTerm{get;set;} }
上面这个对象,包含了分页相关设置,以及查询和排序字段及属性。
将排序逻辑封装在了PageList对象中,该对象继承自List,每次分页就返回分页后的数据放在PageList中。
publicclassPagedList:List { publicintCurrentPage{get;set;} publicintPageSize{get;set;} publicintTotalPages{get;set;} publicQueryOptionsOptions{get;set;} publicboolHasPreviousPage=>CurrentPage>1; publicboolHasNextPage=>CurrentPage query,QueryOptionso) { 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)); } privateIQueryable Search(IQueryable query,stringsearchPropertyName,stringsearchItem) { varparameter=Expression.Parameter(typeof(T),"x"); varsource=searchPropertyName.Split(".").Aggregate((Expression)parameter,Expression.Property); varbody=Expression.Call(source,"Contains",Type.EmptyTypes,Expression.Constant(searchItem,typeof(string))); varlambda=Expression.Lambda >(body,parameter); returnquery.Where(lambda); } privateIQueryable Order(IQueryable query,stringorderPropertyName,booldescendingOrder) { varparameter=Expression.Parameter(typeof(T),"x"); varsource=orderPropertyName.Split(".").Aggregate((Expression)parameter,Expression.Property); varlambda=Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(T),source.Type),source,parameter); returntypeof(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,newobject[]{query,lambda})asIQueryable ; } }
可以看到,在where和order部分,我们通过查询表达式,动态构造了这两部分的逻辑。使用的时候,非常方便:
publicPagedListGetCategories(QueryOptionsoptions) { returnnewPagedList (context.Categories,options); }
这里context.Categories时DbSet集合,QueryOptions是用户传入的分页选项。每次输入查询排序及分页选项,就把分好的放在PageList里面返回。
Example4简化数据绑定
在WindowsForm中,所有的控件都继承自Control基类,它有一个DataBindings属性,可以用来绑定对象,这样就能自动更新。通常,在需要给一些对象赋值,或者保存空间里设置的值到配置文件时,我们可以使用数据绑定,而不是手动的去一个一个的赋值。但DataBindings的原型方法Add为如下,比如我要将某个空间的值绑定到配置对象里,代码如下:
textBoxCustomerName.DataBindings.Add("Text",bindingSource,"CustomerName");
上述代码将TextBox控件的Text属性跟bingSource对象的CustomerName属性进行了绑定,可以看到以上代码有很多地方需要手写字符串:TextBox控件的属性“Text”,bingSource对象的“CustomerName”属性,非常不友好,而且容易出错,一看这种需要手打字符串的地方,就是"BadSmell",需要优化。。
可以感觉到,如果我们直接能像在VisualStudio里面那样,直接使用对象.属性的方式来赋值,比如这里直接用textBoxCustomerName.Text,和bindingSource.CustomerName,来替代两个手动输入的地方,整个体验就要好很多,比如,如果将上述代码写为下面的形式:
dataSource.CreateBinding(textBoxCustomerName,ctl=>ctl.Text,data=>data.Name);
就要好很多。其实现方式为,我们先定义一个绑定的类,如下:
publicclassFlexBindingSource{ privateBindingSource_winFormsBindingSource; /// /// /// ///objectentitybindingtothecontrol publicFlexBindingSource(TDataSourcesource) { _winFormsBindingSource=newBindingSource(); _winFormsBindingSource.DataSource=source; } /// ///CreatesaDataBindingbetweenacontrolpropertyandadatasourceproperty /// ///Thecontroltype,mustderivefromSystem.Winforms.Control /// Thecontrolinstanceonwichthedatabindingwillbeadded /// Alambdaexpressionwhichspecifiesthecontrolpropertytobedatabounded(somethingliketextboxCtl=>textboxCtl.Text) /// Alambdaexpressionwhichspecifiesthedatasourceproperty(somethinglikedatasource=>datasource.Property) /// Seecontrol.DataBindings.Addmethod publicvoidCreateBinding (TControlcontrolInstance,Expression >controlPropertyAccessor,Expression >datasourceMemberAccesor,DataSourceUpdateModedataSourceUpdateMode=DataSourceUpdateMode.OnValidation) whereTControl:Control { stringcontrolPropertyName=FlexReflector.GetPropertyName(controlPropertyAccessor); stringsourcePropertyName=FlexReflector.GetPropertyName(datasourceMemberAccesor); controlInstance.DataBindings.Add(controlPropertyName,_winFormsBindingSource,sourcePropertyName,true,dataSourceUpdateMode); } }
这里面,构造函数里面的source,就是需要绑定的实体对象;在CreateBinding方法中,我们通过在FlexReflector在表达式中获取待绑定的字符串,然后调用传进去的Control对象的绑定方法来实现绑定。FlexReflector方法内容如下:
privatestaticMemberExpressionGetMemberExpression(Expressionselector) { LambdaExpressionlambdaExpression=selectorasLambdaExpression; if(lambdaExpression==null) { thrownewArgumentNullException("Theselectorisnotavalidlambdaexpression."); } MemberExpressionmemberExpression=null; if(lambdaExpression.Body.NodeType==ExpressionType.Convert) { memberExpression=((UnaryExpression)lambdaExpression.Body).OperandasMemberExpression; } elseif(lambdaExpression.Body.NodeType==ExpressionType.MemberAccess) { memberExpression=lambdaExpression.BodyasMemberExpression; } if(memberExpression==null) { thrownewArgumentException("Thepropertyisnotavalidpropertytobeextractedbyalambdaexpression."); } returnmemberExpression; }
使用方法很简单,首先定义一个绑定源的实例。
privateFlexBindingSourcedataSource; dataSource=newFlexBindingSource (customerInstance);
然后就可以使用CreateBinding方法直接绑定了。
dataSource.CreateBinding(textBoxCustomerName,ctl=>ctl.Text,data=>data.Name);
以上就是C#用表达式树构建动态查询的方法的详细内容,更多关于c#构建动态查询的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。