這兩天一直想寫一個動態查詢的方式,先是網上查詢了一下,發現大家寫的差不多都是一樣的【如:http://www.cnblogs.com/ASPNET2008/archive/2012/10/28/2743053.html#commentform和http://www.cnblogs.com/lyj/archive/2008/03/25/1122157.html】,我覺得這里不好的地方就是在查詢的時候還是要知道查詢的是哪一個列,然后根據這個查詢的列是否為空等等的一些判斷來進行動態查詢的拼接,
這樣一來,每一個實體的查詢都要對查詢字段和數據進行逐一判斷,如第一篇例子里面的
if (planCondition.Project != 0) { predicate = predicate.And(c => c.ProjectId == planCondition.Project); }
這里在查詢時首先要知道查詢的是【ProjectId】字段,然后再根據此字段是否為空來進行查詢的拼接,我認為這樣就很不靈活了。所以我就准備自己寫一個。
我認為動態是這樣的:查詢的實體是動態的,查詢的列是動態的,查詢的數據是動態的,查詢的方式[如:OR還是And查詢,EQUAL查詢還是Like查詢]。最重要的一點就是動態查詢的方法要靈活並且通用。
所以就自己嘗試着寫了一個方法,由於對Expression的了解很有限,因此在中間也是反復的嘗試。剛開始實現的時候嘗試了其他的一些方法,最后都因為動態列的類型未知而失敗。不過最終還是實現了
下面是一個結合EasyUI 的一個例子:
頁面數據:
頁面查詢的是一個【DataDictionary】實體的數據,查詢的數據列是圖上的那些(隨着需求隨時可以改變),點擊【查詢】按鈕列表綁定查詢的值。前台的的查詢控件
<table> <tr> <td>所屬類型:</td> <td> <select id="SelParentID" name="SelParentID" style="width: 130px;"></select> </td> <td>字典名稱:</td> <td> <input type="text" id="txtDataText" /> </td> <td>序號:</td> <td> <input type="text" id="txtSortNumt" /> </td> <td><a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'" id="btnSearch" plain="true">查詢</a></td> </tr> </table>
按鈕事件:
//查詢 $("#btnSearch").click(function () { var jdata = { "[EQUAL][And]ParentID": 1, "[LIKE][And]DataText": "abc", "[EQUAL][And]SortNum": 789}; $("#DataDicList").datagrid("load", { queryJson: JSON.stringify(jdata) }); });
這里就是使用的方式
1 var jdata = { "[EQUAL][And]ParentID": $('#SelParentID').combobox("getValue"), "[LIKE][And]DataText": $("#txtDataText").val(), "[EQUAL][And]SortNum": $("#txtSortNumt").val() };
在頁面只需要指定查詢的方式,查詢連接的方式,查詢字段和字段的值,后台調用的時候再加上要查詢的實體,動態查詢方式自己去解析就行了,直接把解析的Lambda返給我 ,我拿着他去數據庫取數據就行了。
接下來看一下后台,加載列表數據的方法:
public ActionResult GetAll(int page, int rows, string value, string queryJson) { var query = SpecificationBuilder.Create<DataDictionary>(); query.Equals(t => t.DelFlag, 0); if (!string.IsNullOrEmpty(queryJson)) { var predicate = HelpClass.GetSerchExtensions<DataDictionary>(queryJson); query.Predicate = query.Predicate.And(predicate); } var allCount = 0; var listModel = _dataDictionaryBLL.GetAllPage(query.Predicate, a => a.CreateDate, page, rows, out allCount); var dateInfo = "{\"total\":" + allCount + ",\"rows\":" + JsonConvert.SerializeObject(listModel) + "}"; return Content(dateInfo); }
這里
var query = SpecificationBuilder.Create<DataDictionary>();
是我另外一個動態查詢的例子【這里就是需要知道並指定查詢的列名和數據值】,先不用管,關鍵就只用這樣的一句
var predicate = HelpClass.GetSerchExtensions<DataDictionary>(queryJson);
得到的就是一個直接可以查詢的Lambda表達式【如:{a => (True And (a.ParentID == 00000000-0000-0000-0000-000000000000))}】
下面就看一下這個方法的實現
#region 把查詢條件拼接為Extensions /// <summary> /// 把查詢條件拼接為Extensions /// </summary> /// <typeparam name="TEntity">查詢實體</typeparam> /// <param name="searchJson">查詢條件,例如:[like][or]name:123</param> /// <returns></returns> public static Expression<Func<TEntity, bool>> GetSerchExtensions<TEntity>(String searchJson) where TEntity : class, new() { try { var ja = (JArray)JsonConvert.DeserializeObject("[" + searchJson + "]"); //把查詢條件轉換為Json格式 var enumerableQuery = new EnumerableQuery<KeyValuePair<string, JToken>>(ja[0] as JObject); return GetSerchExtensions<TEntity>(enumerableQuery); } catch (Exception) { return null; } } /// <summary> /// 把查詢條件拼接為Extensions /// </summary> /// <typeparam name="TEntity">查詢實體</typeparam> /// <param name="enumerableQuery"></param> /// <returns></returns> public static Expression<Func<TEntity, bool>> GetSerchExtensions<TEntity>(EnumerableQuery<KeyValuePair<string, JToken>> enumerableQuery) where TEntity : class,new() { ParameterExpression paramExp = Expression.Parameter(typeof(TEntity), "a"); if (null == enumerableQuery || !enumerableQuery.Any()) { var valueEqual = Expression.Constant(1); var expEqual = Expression.Equal(valueEqual, valueEqual); return Expression.Lambda<Func<TEntity, bool>>(expEqual, paramExp); //如果參數為空,返回一個a=>1=1 的值 } var modeltypt = typeof(TEntity); //實體類型 var keyList = enumerableQuery.Select(e => e.Key).ToList(); //取出Json 的每個字符串 Expression whereExp = null; keyList.ForEach(s => { var searchTypeStr = s.Substring(1, s.LastIndexOf("][", StringComparison.Ordinal) - 1); //查詢方式 Like var ab = s.Substring(s.LastIndexOf("][", StringComparison.Ordinal) + 2); var joinTypeStr = ab.Remove(ab.LastIndexOf("]", StringComparison.Ordinal)); //連接方式 or var searchField = s.Substring(s.LastIndexOf("]", StringComparison.Ordinal) + 1); //查詢的列名 name var value = enumerableQuery.FirstOrDefault(v => v.Key == s).Value.ToString(); //值 123 var searchType = LogicOperation.LIKE; //查詢方式 var joinType = PredicateType.AND; //連接方式 if (Enum.TryParse(searchTypeStr.ToUpper(), out searchType) && Enum.TryParse(joinTypeStr.ToUpper(), out joinType) && modeltypt.GetProperties().Any(p => String.Equals(p.Name, searchField,
StringComparison.CurrentCultureIgnoreCase))) //這個實體有這個列名 { var firstOrDefault = modeltypt.GetProperties().FirstOrDefault(p => String.Equals(p.Name, searchField, StringComparison.CurrentCultureIgnoreCase)); if (firstOrDefault != null) { var selCol = firstOrDefault.Name; //查詢的列名 var splitList = value.Split(',').ToList(); for (var i = 0; i < splitList.Count; i++) { var expressionFuncEquals = PrepareConditionLambda<TEntity>(selCol, splitList[i], paramExp, searchType); //得到這個查詢的表達式 if (i != 0) //累加 { whereExp = whereExp == null ? expressionFuncEquals : Expression.Or(whereExp, expressionFuncEquals); } else { whereExp = joinType == PredicateType.OR ? (whereExp == null ? expressionFuncEquals : Expression.Or(whereExp, expressionFuncEquals)) : (whereExp == null ?
expressionFuncEquals : Expression.And(whereExp, expressionFuncEquals)); } } } } }); return Expression.Lambda<Func<TEntity, bool>>(whereExp, paramExp); ; } /// <summary> /// 得到字段查詢的表達式 /// </summary> /// <typeparam name="TEntity">實體</typeparam> /// <param name="name">查詢列名</param> /// <param name="dateValue">數據值</param> /// <param name="paramExp">參數</param> /// <param name="searchType">查詢方式(默認是等於查詢)</param> /// <returns></returns> private static Expression PrepareConditionLambda<TEntity>(string name, object dateValue, ParameterExpression paramExp, LogicOperation searchType = LogicOperation.EQUAL) { if (dateValue == null) throw new ArgumentNullException("dateValue"); var exp = Expression.Property(paramExp, name); var propertyType = typeof(TEntity).GetProperty(name).PropertyType; //得到此字段的數據類型 var value = propertyType == typeof(Guid?) ? new Guid(dateValue.ToString()) : Convert.ChangeType(dateValue, TypeHelper.GetUnNullableType(propertyType)); Expression expEqual = null; switch (searchType) { case LogicOperation.EQUAL: //等於查詢 var valueEqual = Expression.Constant(value, propertyType); //值 expEqual = Expression.Equal(exp, valueEqual); //拼接成 t=>t.name=valueEqual break; case LogicOperation.LIKE: //模糊查詢 var containsMethod = typeof(string).GetMethod("Contains"); var valueLike = Expression.Constant(value, propertyType); expEqual = Expression.Call(exp, containsMethod, valueLike); break; } return expEqual; } #endregion
/// <summary> /// 查詢方式 /// </summary> public enum PredicateType { AND, OR } /// <summary> /// 查詢方式 /// </summary> public enum SearchType { Between, Like, Equals } /// <summary> /// 查詢方式 /// </summary> public enum LogicOperation { LIKE, //包含,模糊查詢 EQUAL, //等於 LT, //小於 GT, //大於 CONTAINS, //包含,In查詢 NOTEQUAL //不等於 }
在拼接查詢時候有一個對查詢數據值的分割
var splitList = value.Split(',').ToList();
查詢方式目前先寫完了這兩個,其他的方式其實都是很簡單的了,暫時沒有用 ,也就沒有寫。
這樣做是因為可以實現多數據查詢,例如在查詢User的Name字段時,在Name文本框中輸入【張三,李四】,這樣就可以把"張三"和"李四"值都查詢出來【關聯是用的OR】,由於上面有注釋就不再詳細解釋了,在這里一是把這個方法和大家分享一下,在一個就是對自己這兩天工作的一個總結,同時也作為工作筆記放在這里,哪一天遺忘了自己還可以拿出來看看。
這里需要用到的有【json.js】和【Newtonsoft.Json】
補充:
今天(2017-03-23)突然看到了自己以前寫的這個小例子,想繼續完善一下,順便把代碼提取出來。
同時重載了GetSerchExtensions方式,直接解析一個對象,因為我感覺以前封裝的那個只支持json字符串的查詢不太友好,還多余,因為是對象轉json字符串,json字符串轉JArray,顯得很繁瑣,還很容易出錯。

1 /// <summary> 2 /// 查詢實體 3 /// </summary> 4 public class QueryEntity 5 { 6 /// <summary> 7 /// 查詢方式 8 /// </summary> 9 public LogicOperation LogicOperation { get; set; } 10 11 /// <summary> 12 /// 連接方式 13 /// </summary> 14 public PredicateType PredicateType { get; set; } 15 16 /// <summary> 17 /// 列名 18 /// </summary> 19 public string Column { get; set; } 20 21 /// <summary> 22 /// 列值 23 /// </summary> 24 public object Value { get; set; } 25 }
1 /// <summary> 2 /// 把查詢條件拼接為Extensions 3 /// </summary> 4 /// <typeparam name="TEntity">實體類</typeparam> 5 /// <param name="queryEntitys">查詢實體</param> 6 /// <returns></returns> 7 public static Expression<Func<TEntity, bool>> GetSerchExtensions<TEntity>(List<QueryEntity> queryEntitys) where TEntity : class, new() 8 { 9 var paramExp = Expression.Parameter(typeof(TEntity), "a"); 10 if (null == queryEntitys || !queryEntitys.Any()) 11 { 12 var valueEqual = Expression.Constant(1); 13 var expEqual = Expression.Equal(valueEqual, valueEqual); 14 return Expression.Lambda<Func<TEntity, bool>>(expEqual, paramExp); //如果參數為空,返回一個a=>1=1 的值 15 16 } 17 var modeltypt = typeof(TEntity); //實體類型 18 Expression whereExp = null; 19 20 queryEntitys.ForEach(q => 21 { 22 LogicOperation searchType = q.LogicOperation; //查詢方式 23 PredicateType joinType = q.PredicateType; //連接方式 24 var searchField = q.Column; //查詢的列名 name 25 var value = q.Value; //值 123 26 if (modeltypt.GetProperties().Any(p => String.Equals(p.Name, searchField, StringComparison.CurrentCultureIgnoreCase))) //這個實體有這個列名 27 { 28 var firstOrDefault = modeltypt.GetProperties().FirstOrDefault(p => String.Equals(p.Name, searchField, StringComparison.CurrentCultureIgnoreCase)); 29 if (firstOrDefault == null) return; 30 var selCol = firstOrDefault.Name; //查詢的列名 31 var splitList = value.ToString().Split(',').ToList(); //這個位置是的處理是默認認為當查詢值中包含,的視為或者的查詢:例如 A='abc,def' 處理成 (A='def' OR A='abc'),但是時間上這塊無法滿足就要查詢包含,的數據的求 32 for (var i = 0; i < splitList.Count; i++) 33 { 34 if (splitList[i] == null || string.IsNullOrWhiteSpace(splitList[i])) continue; 35 var expressionFuncEquals = PrepareConditionLambda<TEntity>(selCol, splitList[i], paramExp, searchType); //得到這個查詢的表達式 36 whereExp = i != 0 37 ? (whereExp == null ? expressionFuncEquals : Expression.Or(whereExp, expressionFuncEquals)) 38 : (joinType == PredicateType.OR ? (whereExp == null ? expressionFuncEquals : Expression.Or(whereExp, expressionFuncEquals)) 39 : (whereExp == null ? expressionFuncEquals : Expression.And(whereExp, expressionFuncEquals))); 40 } 41 } 42 }); 43 return Expression.Lambda<Func<TEntity, bool>>(whereExp, paramExp); ; 44 }
使用示例:
1 static void Main(string[] args) 2 { 3 var query = SpecificationBuilder.Create<VWDepartment>(); 4 query.Equals(d => d.DeptDelFlag, 0); 5 query.Equals(d => d.DeptParentID, Guid.NewGuid()); 6 7 #region 字符串 8 string queryJson = "{\"[EQUAL][And]DeptParentID\":\"86EE21E7-81C2-49BC-B7D6-76E865DA1D3A\",\"[EQUAL][And]DeptName\":\"abc,ccccc\",\"[EQUAL][And]DeptSort\":789}"; 9 10 if (!string.IsNullOrEmpty(queryJson)) 11 { 12 var predicate = Utils.GetSerchExtensions<VWDepartment>(queryJson); 13 query.Predicate = query.Predicate.And(predicate); 14 } 15 #endregion 16 17 #region 對象 18 19 QueryEntity queryEntity = new QueryEntity 20 { 21 LogicOperation = LogicOperation.EQUAL, 22 PredicateType = PredicateType.AND, 23 Column = "DeptParentID", 24 Value = Guid.NewGuid() 25 }; 26 var qqqqq = Utils.GetSerchExtensions<VWDepartment>(new List<QueryEntity>() { queryEntity }); 27 28 29 var li = new List<QueryEntity>() { }; 30 li.Add(new QueryEntity 31 { 32 LogicOperation = LogicOperation.EQUAL, 33 PredicateType = PredicateType.AND, 34 Column = "DeptParentID", 35 Value = Guid.NewGuid() 36 }); 37 38 li.Add(new QueryEntity 39 { 40 LogicOperation = LogicOperation.EQUAL, 41 PredicateType = PredicateType.AND, 42 Column = "DeptSort", 43 Value = 123 44 }); 45 li.Add(new QueryEntity 46 { 47 LogicOperation = LogicOperation.LIKE, 48 PredicateType = PredicateType.AND, 49 Column = "ParentDeptName", 50 Value = "大爺" 51 }); 52 qqqqq = Utils.GetSerchExtensions<VWDepartment>(li); 53 #endregion 54 }
代碼環境
win10 + Visual Studio Community 2017