动态LINQ(Lambda表达式)构建
最近一直都忙于项目也没什么时间好好总结一下自己在项目中或平时的收获~大家都知道现在为了提高开发效率,有关于和数据库交互方面的部分我们一般都会用orm框架,例如EntityFramework, NHiberhate, Linq To Sql等,所以我们队lambda表达式部分的使用也十分常见了,在实际开发中我们经常会碰到多条件查询的各种组合查询的情况,以前在没有LINQ的年代如果会遇到动态查询的情况,我们一般是采用根据相应条件动态拼接相应的where条件上去达到相应效果, 如今在这个LINQ横行的年代,怎么能利用LINQ完成动态查询(即怎么样创建一个符合你业务环境的Lambda表达式呢)
但是也是百度了很多的文章包括这两篇文章,也从中得到了很多启发
李永京: http://www.cnblogs.com/killuakun/archive/2008/08/03/1259389.html, 肖坤:http://www.cnblogs.com/killuakun/archive/2008/08/03/1259389.html
一般如果对于假设的业务实体
/// <summary> /// 业务实体类-你可以想象成你业务中需要实际使用的类 /// </summary> public class TestUser { public TestUser() { } public int Id { get; set; } public string Name { get; set; } public DateTime Birth { get; set; } public bool IsStudent { get; set; } public string Cellphone { get; set; } public string Email { get; set; } public int Score { get; set; } }
如果我们希望那些查询条件有值我就查,没值就不查,而且还希望他根据页面所选的列排序,一般情况下最差我们会这样写
#region 一般的解决方案 //如果要根据对应的Linq的方式怎么完成 public List<TestUser> GetDataByGeneralQuery(QueryCondition queryCondition) { //此处一般会从数据库或者其他地方获取到业务所用到的数据源 List<TestUser> sourceLs = GetTestData(); /*根据不同情况添加不同查询条件,但是我们都知道平时开发中需求是不断变化的,怎么能更好的应对PM各种扭曲的要求尔不必一次一次的添加各种if条件呢 万一有一天,PM要求你将某些条件合并例如名字和ID查询条件关系怎么办,如果需要利用LINQ进行动态的排序怎么办,或者如果过滤的名字是一个 不定的字符串数组怎么办,这些都是我们经常会遇到的,我们不能因为每次这样的改动而去修改这里的东西, 而且有的时候我们知道在Where(n=>n.?==?) 但是编译器是不知道的,这是我们就要用到动态lambda表达式(动态linq的方式) */ if (queryCondition.QueryId.HasValue) sourceLs = sourceLs.Where(n => n.Id == queryCondition.QueryId).ToList<TestUser>(); if (!string.IsNullOrEmpty(queryCondition.QueryName)) sourceLs = sourceLs.Where(n => n.Name.ToLower().Contains(queryCondition.QueryName.ToLower())).ToList<TestUser>(); if (queryCondition.QueryStartTime.HasValue) sourceLs = sourceLs.Where(n => n.Birth >= queryCondition.QueryStartTime.Value).ToList<TestUser>(); if (queryCondition.QueryEndTime.HasValue) sourceLs = sourceLs.Where(n => n.Birth < queryCondition.QueryEndTime.Value).ToList<TestUser>(); if (queryCondition.QueryBoolean != null) sourceLs = sourceLs.Where(n => n.IsStudent = queryCondition.QueryBoolean.Value).ToList<TestUser>(); if (queryCondition.QueryScore.HasValue) sourceLs = sourceLs.Where(n => n.Score == queryCondition.QueryScore.Value).ToList<TestUser>(); switch (queryCondition.OrderField) { case 0: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Id).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Id).ToList<TestUser>(); }; break; case 1: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Name).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Name).ToList<TestUser>(); }; break; case 2: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Birth).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Birth).ToList<TestUser>(); }; break; case 3: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.IsStudent).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.IsStudent).ToList<TestUser>(); }; break; case 4: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Cellphone).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Cellphone).ToList<TestUser>(); }; break; case 5: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Email).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Email).ToList<TestUser>(); }; break; case 6: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Score).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Score).ToList<TestUser>(); }; break; default: break; } return sourceLs; } #endregion
那如果以后业务上又加了字段又有新的那种改动,在这种情况下我们会感到很麻烦,而且代码看上去回事很多的if-else,会对以后维护方面也有一定影响,当然不排除我有点代码洁癖的关系~
#region 动态构建Lambda表达式-动态Expression树 public List<TestUser> GetDataByDynamicQuery(QueryCondition queryCondition) { IQueryable<TestUser> sourceLs = GetTestData().AsQueryable<TestUser>(); string[] orderParams = new string[] { "OrderField", "IsDesc" }; Expression filter; Expression totalExpr = Expression.Constant(true); ParameterExpression param = Expression.Parameter(typeof(TestUser), "n"); Type queryConditionType = queryCondition.GetType(); foreach (PropertyInfo item in queryConditionType.GetProperties()) { //反射找出所有查询条件的属性值,如果该查询条件值为空或者null不添加动态lambda表达式 string propertyName = item.Name; var propertyVal = item.GetValue(queryCondition, null); if (!orderParams.Contains(propertyName) && propertyVal != null && propertyVal.ToString() != string.Empty) { //n.property Expression left = Expression.Property(param, typeof(TestUser).GetProperty(queryDict[propertyName])); //等式右边的值 Expression right = Expression.Constant(propertyVal); //此处如果有特殊的判断可以自行修改例如要是Contain的,要是时间大于小于的这种判断, 这里也可以用类似InitDynamicQueryMapping方法进行表驱动维护 if (propertyName == "QueryStartTime") filter = Expression.GreaterThanOrEqual(left, right); else if (propertyName == "QueryEndTime") filter = Expression.LessThan(left, right); else if (propertyName == "QueryName") filter = Expression.Call(Expression.Property(param, typeof(TestUser).GetProperty(queryDict[propertyName])), typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(propertyVal)); else filter = Expression.Equal(left, right); totalExpr = Expression.And(filter, totalExpr); } } //Where部分条件 Expression pred = Expression.Lambda(totalExpr, param); Expression whereExpression = Expression.Call(typeof(Queryable), "Where", new Type[] { typeof(TestUser) }, Expression.Constant(sourceLs), pred); //OrderBy部分排序 MethodCallExpression orderByCallExpression = Expression.Call(typeof(Queryable), queryCondition.IsDesc ? "OrderByDescending" : "OrderBy", new Type[] { typeof(TestUser), orderEntries[queryCondition.OrderField].OrderType }, whereExpression, Expression.Lambda(Expression.Property(param, orderEntries[queryCondition.OrderField].OrderStr), param)); //生成动态查询 sourceLs = sourceLs.Provider.CreateQuery<TestUser>(orderByCallExpression); return sourceLs.ToList<TestUser>(); } #endregion
下面是完整的测试程序和测试程序中使用到的类
/// <summary> /// 动态Lambda表达式类 /// </summary> public class DynamicLambda { public Dictionary<string, string> queryDict = new Dictionary<string, string>(); public OrderEntry[] orderEntries; public DynamicLambda() { InitDynamicQueryMapping(); orderEntries = new OrderEntry[] { new OrderEntry(){OrderStr="Id",OrderType=typeof(int)}, new OrderEntry(){OrderStr="Name",OrderType=typeof(string)}, new OrderEntry(){OrderStr="Birth",OrderType=typeof(string)}, new OrderEntry(){OrderStr="IsStudent",OrderType=typeof(string)}, new OrderEntry(){OrderStr="Cellphone",OrderType=typeof(string)}, new OrderEntry(){OrderStr="Email",OrderType=typeof(string)}, new OrderEntry(){OrderStr="Score",OrderType=typeof(int)} }; } /*在一般的业务环境中我们常常会遇到动态查询的情况,对于以前纯T-SQL情况下我们一般是采用根据相应条件动态拼接相应的where条件上去达到相应效果 如今在这个LINQ横行的年代,怎么能利用LINQ完成动态查询呢 */ #region 一般的解决方案 //如果要根据对应的Linq的方式怎么完成 public List<TestUser> GetDataByGeneralQuery(QueryCondition queryCondition) { //此处一般会从数据库或者其他地方获取到业务所用到的数据源 List<TestUser> sourceLs = GetTestData(); /*根据不同情况添加不同查询条件,但是我们都摘掉平时开发中需求是不断变化的,怎么能更好的应对PM各种扭曲的要求尔不必一次一次的添加各种if条件呢 万一有一天,PM要求你将某些条件合并例如名字和ID变为OR的关系怎么办,如果需要利用LINQ进行动态的排序怎么办,或者如果过滤的名字是一个 不定的字符串数组怎么办,这些都是我们经常会遇到的,我们不能因为每次这样的改动而去修改这里的东西, 而且有的时候我们知道在Where(n=>n.?==?) 但是编译器是不知道的,这是我们就要用到动态lambda表达式(动态linq的方式) */ if (queryCondition.QueryId.HasValue) sourceLs = sourceLs.Where(n => n.Id == queryCondition.QueryId).ToList<TestUser>(); if (!string.IsNullOrEmpty(queryCondition.QueryName)) sourceLs = sourceLs.Where(n => n.Name.ToLower().Contains(queryCondition.QueryName.ToLower())).ToList<TestUser>(); if (queryCondition.QueryStartTime.HasValue) sourceLs = sourceLs.Where(n => n.Birth >= queryCondition.QueryStartTime.Value).ToList<TestUser>(); if (queryCondition.QueryEndTime.HasValue) sourceLs = sourceLs.Where(n => n.Birth < queryCondition.QueryEndTime.Value).ToList<TestUser>(); if (queryCondition.QueryBoolean != null) sourceLs = sourceLs.Where(n => n.IsStudent = queryCondition.QueryBoolean.Value).ToList<TestUser>(); if (queryCondition.QueryScore.HasValue) sourceLs = sourceLs.Where(n => n.Score == queryCondition.QueryScore.Value).ToList<TestUser>(); switch (queryCondition.OrderField) { case 0: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Id).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Id).ToList<TestUser>(); }; break; case 1: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Name).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Name).ToList<TestUser>(); }; break; case 2: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Birth).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Birth).ToList<TestUser>(); }; break; case 3: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.IsStudent).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.IsStudent).ToList<TestUser>(); }; break; case 4: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Cellphone).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Cellphone).ToList<TestUser>(); }; break; case 5: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Email).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Email).ToList<TestUser>(); }; break; case 6: { if (queryCondition.IsDesc) sourceLs = sourceLs.OrderByDescending(n => n.Score).ToList<TestUser>(); else sourceLs = sourceLs.OrderBy(n => n.Score).ToList<TestUser>(); }; break; default: break; } return sourceLs; } #endregion #region 动态构建Lambda表达式-动态Expression树 public List<TestUser> GetDataByDynamicQuery(QueryCondition queryCondition) { IQueryable<TestUser> sourceLs = GetTestData().AsQueryable<TestUser>(); string[] orderParams = new string[] { "OrderField", "IsDesc" }; Expression filter; Expression totalExpr = Expression.Constant(true); ParameterExpression param = Expression.Parameter(typeof(TestUser), "n"); Type queryConditionType = queryCondition.GetType(); foreach (PropertyInfo item in queryConditionType.GetProperties()) { //反射找出所有查询条件的属性值,如果该查询条件值为空或者null不添加动态lambda表达式 string propertyName = item.Name; var propertyVal = item.GetValue(queryCondition, null); if (!orderParams.Contains(propertyName) && propertyVal != null && propertyVal.ToString() != string.Empty) { //n.property Expression left = Expression.Property(param, typeof(TestUser).GetProperty(queryDict[propertyName])); //等式右边的值 Expression right = Expression.Constant(propertyVal); //此处如果有特殊的判断可以自行修改例如要是Contain的,要是时间大于小于的这种判断, 这里也可以用类似InitDynamicQueryMapping方法进行表驱动维护 if (propertyName == "QueryStartTime") filter = Expression.GreaterThanOrEqual(left, right); else if (propertyName == "QueryEndTime") filter = Expression.LessThan(left, right); else if (propertyName == "QueryName") filter = Expression.Call(Expression.Property(param, typeof(TestUser).GetProperty(queryDict[propertyName])), typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(propertyVal)); else filter = Expression.Equal(left, right); totalExpr = Expression.And(filter, totalExpr); } } //Where部分条件 Expression pred = Expression.Lambda(totalExpr, param); Expression whereExpression = Expression.Call(typeof(Queryable), "Where", new Type[] { typeof(TestUser) }, Expression.Constant(sourceLs), pred); //OrderBy部分排序 MethodCallExpression orderByCallExpression = Expression.Call(typeof(Queryable), queryCondition.IsDesc ? "OrderByDescending" : "OrderBy", new Type[] { typeof(TestUser), orderEntries[queryCondition.OrderField].OrderType }, whereExpression, Expression.Lambda(Expression.Property(param, orderEntries[queryCondition.OrderField].OrderStr), param)); //生成动态查询 sourceLs = sourceLs.Provider.CreateQuery<TestUser>(orderByCallExpression); return sourceLs.ToList<TestUser>(); } #endregion /// <summary> /// 具体查询属性和实体里面属性作Mapping,当然你可以对名字规范做一个显示那样不用做映射用反射获取到直接构建表达式也行 /// 具体这里只是假设模拟了一个这种情况,使用者可以根据自身业务情况适当修改 /// </summary> public void InitDynamicQueryMapping() { //查询mapping queryDict.Add("QueryId", "Id"); queryDict.Add("QueryName", "Name"); queryDict.Add("QueryStartTime", "Birth"); queryDict.Add("QueryEndTime", "Birth"); queryDict.Add("QueryBoolean", "IsStudent"); queryDict.Add("QueryScore", "Score"); } /// <summary> /// 制造测试数据 /// </summary> /// <returns></returns> public List<TestUser> GetTestData() { List<TestUser> testLs = new List<TestUser>(); testLs.AddRange(new TestUser[] { new TestUser() { Id=1, Name="测试1", Birth=new DateTime(2013,1,1), IsStudent=true, Cellphone="123456789", Email="test001@qq.com", Score=100 }, new TestUser() { Id=2, Name="测试2", Birth=new DateTime(2013,1,2), IsStudent=false, Cellphone="23123513", Email="test002@qq.com", Score=60 }, new TestUser() { Id=3, Name="测试3", Birth=new DateTime(2013,1,3), IsStudent=true, Cellphone="36365656", Email="test003@qq.com", Score=98 }, new TestUser() { Id=4, Name="测试4", Birth=new DateTime(2013,1,4), IsStudent=false, Cellphone="23423525", Email="test004@qq.com", Score=86 }, new TestUser() { Id=5, Name="测试5", Birth=new DateTime(2013,1,5), IsStudent=true, Cellphone="9867467", Email="test006@qq.com", Score=96 }, new TestUser() { Id=6, Name="测试6", Birth=new DateTime(2013,1,6), IsStudent=false, Cellphone="536546345", Email="test007@qq.com", Score=99 }, new TestUser() { Id=7, Name="测试7", Birth=new DateTime(2013,1,7), IsStudent=true, Cellphone="45234552", Email="test008@qq.com", Score=98 }, new TestUser() { Id=8, Name="测试8", Birth=new DateTime(2013,1,8), IsStudent=false, Cellphone="536375636", Email="test009@qq.com", Score=97 }, new TestUser() { Id=9, Name="测试9", Birth=new DateTime(2013,2,1), IsStudent=true, Cellphone="123456789", Email="test010@qq.com", Score=88 }, new TestUser() { Id=10, Name="测试10", Birth=new DateTime(2013,2,2), IsStudent=false, Cellphone="4524245", Email="test011@qq.com", Score=88 }, new TestUser() { Id=11, Name="动态测试11", Birth=new DateTime(2013,2,3), IsStudent=false, Cellphone="64767484", Email="test012@qq.com", Score=87 }, new TestUser() { Id=12, Name="动态测试12", Birth=new DateTime(2013,2,4), IsStudent=true, Cellphone="78578568", Email="test013@qq.com", Score=86 }, new TestUser() { Id=13, Name="动态测试13", Birth=new DateTime(2013,2,5), IsStudent=false, Cellphone="123456789", Email="test014@qq.com", Score=60 }, new TestUser() { Id=14, Name="动态测试14", Birth=new DateTime(2013,2,6), IsStudent=true, Cellphone="123456789", Email="test015@qq.com", Score=60 }, new TestUser() { Id=15, Name="动态测试15", Birth=new DateTime(2013,2,7), IsStudent=false, Cellphone="123456789", Email="test016@qq.com", Score=59 }, new TestUser() { Id=16, Name="动态测试16", Birth=new DateTime(2013,2,8), IsStudent=true, Cellphone="34135134", Email="test017@qq.com", Score=58 }, new TestUser() { Id=17, Name="动态测试17", Birth=new DateTime(2013,3,1), IsStudent=false, Cellphone="123456789", Email="test018@qq.com", Score=100 }, new TestUser() { Id=18, Name="动态测试18", Birth=new DateTime(2013,3,2), IsStudent=true, Cellphone="34165451234", Email="test019@qq.com", Score=86 }, new TestUser() { Id=19, Name="动态测试19", Birth=new DateTime(2013,3,3), IsStudent=false, Cellphone="462645246", Email="test020@qq.com", Score=64 }, new TestUser() { Id=20, Name="动态测试20", Birth=new DateTime(2013,3,4), IsStudent=true, Cellphone="61454343", Email="test021@qq.com", Score=86 }, }); return testLs; } /// <summary> /// 打印测试数据 /// </summary> /// <param name="resultLs"></param> public void PrintResult(List<TestUser> resultLs) { foreach (TestUser item in resultLs) { Console.WriteLine("序号:{0},姓名:{1},生日:{2},是否在读:{3},联系手机:{4},邮箱:{5},分数:{6}", item.Id, item.Name, item.Birth, item.IsStudent ? "是" : "否", item.Cellphone, item.Email, item.Score); } } } /// <summary> /// 业务实体类-你可以想象成你业务中需要实际使用的类 /// </summary> public class TestUser { public TestUser() { } public int Id { get; set; } public string Name { get; set; } public DateTime Birth { get; set; } public bool IsStudent { get; set; } public string Cellphone { get; set; } public string Email { get; set; } public int Score { get; set; } } /// <summary> /// 排序帮助类 /// </summary> public class OrderEntry { public string OrderStr { get; set; } public Type OrderType { get; set; } } /// <summary> /// 业务查询条件类-实际使用中你可以根据自己的需要构建你的查询条件类 /// </summary> public class QueryCondition { public QueryCondition() { } public QueryCondition(int? queryId, string queryName, DateTime? queryStart, DateTime? queryEnd, bool? queryBoolean, int? queryScore, int orderField, bool isDesc) { this.QueryId = queryId; this.QueryName = queryName; this.QueryStartTime = queryStart; this.QueryEndTime = queryEnd; this.QueryBoolean = queryBoolean; this.QueryScore = queryScore; this.OrderField = orderField; this.IsDesc = isDesc; } public int? QueryId { get; set; } public string QueryName { get; set; } public DateTime? QueryStartTime { get; set; } public DateTime? QueryEndTime { get; set; } public bool? QueryBoolean { get; set; } public int? QueryScore { get; set; } public int OrderField { get; set; } public bool IsDesc { get; set; } }
static class Program { static void Main() { #region 动态Linq(动态lambda表达式构建) QueryCondition queryCondition = new QueryCondition(null, "动态测试", new DateTime(2013, 1, 1), new DateTime(2013, 3, 1), null, null, 0, true); QueryCondition queryCondition2 = new QueryCondition(null, string.Empty, null, null, null, 60, 1, false); QueryCondition queryCondition3 = new QueryCondition(null, string.Empty, null, new DateTime(2013, 5, 1), null, 60, 5, true); QueryCondition[] queryConditionLs = new QueryCondition[] { queryCondition, queryCondition2, queryCondition3 }; DynamicLambda dynamicLinq = new DynamicLambda(); List<TestUser> queryLs; queryLs = dynamicLinq.GetTestData(); Console.WriteLine("原始测试数据有{0}条,如下\n", queryLs.Count); dynamicLinq.PrintResult(queryLs); Console.WriteLine("---------------查询分隔符------------------\n"); //queryLs = dynamicLinq.GetDataByGeneralQuery(queryConditionLs[0]); queryLs = dynamicLinq.GetDataByDynamicQuery(queryConditionLs[0]); Console.WriteLine("满足查询结果的数据有{0}条,如下\n", queryLs.Count); dynamicLinq.PrintResult(queryLs); #endregion Console.ReadKey(); } }
希望以上对于有在项目中要使用动态LINQ的朋友有所启发~如发现文章中有错误内容欢迎指出~共勉之~^0^
如果你觉得这篇文章对你有用,欢迎推荐[推荐]
如果你觉得文章内有错误欢迎指出^0^~
如果您想转载本博客,请注明出处
如果您对本文有意见或者建议,欢迎留言
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则作者保留追究法律责任的权利。