這兩天一直想寫一個動态查詢的方式,先是網上查詢了一下,發現大家寫的差不多都是一樣的【如: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 }
View Code
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
代碼下載下傳