對EFCORE分頁查詢的封裝的思路
在實際web專案中必須用到查詢+分頁,這些不可能一直重複寫,所以簡單做了一下封裝
思路:
主要分為兩部分:
1.分頁
2.查詢條件
首先新建一個QueryParameter用來接收分頁排序和查詢引數,這個類可以自定義,但是要保證查詢查詢和資料庫的欄位名一致,和後面的SqlQueryAndParameter中的接收引數一致,最好也和前端的傳到介面中的資料格式一致,這樣就不需要在進行其他處理,直接進行查詢就行了
public class QueryParameter<T> where T : class { public T data { get; set; } //查詢引數 public int pageNum { get; set; } = 1; public int pageSize { get; set; } = 10; public int Count { get; set; } = 0; /// <summary> /// 排序列 /// </summary> public string sidx { get; set; } = "Id"; /// <summary> /// 排序型別 /// </summary> public string sord { get; set; } = "desc"; }
然後需要根據具體業務和查詢建立一個返回的實體,用來接收查詢結果,和返回給前端
這樣,利用反射就可以獲取到查詢類的屬性和型別,進而用來判斷sql語句該怎麼拼
PropertyInfo[] propertys = datadto.GetType().GetProperties(); foreach (var item in propertys) { var type = item.PropertyType.FullName; var value = item?.GetValue(datadto); var valueS = Convert.ToString(item?.GetValue(datadto)); if (string.IsNullOrWhiteSpace(valueS)) { continue; } if (type.Contains("Int")) { if (Convert.ToInt32(value) <= 0) { continue; } param.Add(new SqlParameter("@" + item.Name, value)); strsql.Append(" and t." + item.Name + " = @" + item.Name); } else if (type.Contains("String")) { param.Add(new SqlParameter("@" + item.Name, "%" + value + "%")); strsql.Append(" and t." + item.Name + " like @" + item.Name); } else if (type.Contains("Datetime")) { var date = ((DateTime)value).ToString("yyyy/MM/dd"); param.Add(new SqlParameter("@" + item.Name, date)); strsql.Append(" and t." + item.Name + " = @" + item.Name); } else { param.Add(new SqlParameter("@" + item.Name, value)); strsql.Append(" and t." + item.Name + " = @" + item.Name); } }
但是這樣做太過簡單,很多業務無法涵蓋到,不符合實際需求,比如,string型別需要比大小,比如大於、小於,比如區間等查詢條件都無法識別,所以還需要進行改進
我的做法是使用特性進行區分:
首先需要新建一個ConditionsAttribute特性和兩個列舉:
/// <summary> /// </summary> [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)] public class ConditionsAttribute: Attribute { public ConditionsAttribute() { Enable = true; NotSelect = false; ConditionsTypes = ConditionsType.STRING; IsSplit = false; SymbolAttributes = SymbolAttribute.EQUAL; } /// <summary> /// /// </summary> /// <param name="notSelect">是否永不查詢,優先順序最高</param> public ConditionsAttribute(bool notSelect):this(true,notSelect, ConditionsType.STRING, SymbolAttribute.EQUAL,false,"") { } /// <summary> /// /// </summary> /// <param name="notSelect">是否永不查詢,優先順序最高</param> /// <param name="enable">是否啟用,如果不啟用特性,則預設使用欄位的型別,優先順序低於NotSelect</param> public ConditionsAttribute(bool notSelect,bool enable) : this(enable, notSelect, ConditionsType.STRING, SymbolAttribute.EQUAL, false, "") { } /// <summary> /// /// </summary> /// <param name="conditionsTypes">欄位型別</param> public ConditionsAttribute(ConditionsType conditionsTypes):this(true,false,conditionsTypes, SymbolAttribute.EQUAL, false,"") { } /// <summary> /// /// </summary> /// <param name="conditionsTypes">欄位型別</param> public ConditionsAttribute(ConditionsType conditionsTypes, SymbolAttribute symbolAttribute, bool isSplit, string splitString) : this(true, false, conditionsTypes, symbolAttribute, isSplit, splitString) { } /// <summary> /// /// </summary> /// <param name="symbolAttribute">字串型別,只在為string時生效</param> /// <param name="isSplit">是否字元分割,只在string或datetime型別下生效</param> /// <param name="splitString">指定分割字串,只在IsSplit為true時生效</param> public ConditionsAttribute(SymbolAttribute symbolAttribute, bool isSplit, string splitString) : this(true, false, ConditionsType.STRING, symbolAttribute, isSplit, splitString) { } /// <summary> /// /// </summary> /// <param name="enable">是否啟用,如果不啟用特性,則預設使用欄位的型別,優先順序低於NotSelect</param> /// <param name="notSelect">是否永不查詢,優先順序最高</param> /// <param name="conditionsTypes">欄位型別</param> /// <param name="symbolAttributes">字串型別,只在為string時生效</param> /// <param name="isSplit">是否字元分割,只在string或datetime型別下生效</param> /// <param name="splitString">指定分割字串,只在IsSplit為true時生效</param> public ConditionsAttribute(bool enable,bool notSelect, ConditionsType conditionsTypes, SymbolAttribute symbolAttributes,bool isSplit,string splitString = "") { Enable = enable; NotSelect = notSelect; ConditionsTypes = conditionsTypes; SymbolAttributes = symbolAttributes; IsSplit = isSplit; SplitString = splitString; } /// <summary> /// 是否啟用,如果不啟用特性,則預設使用欄位的型別,優先順序低於NotSelect /// </summary> public bool Enable { get; set; } /// <summary> /// 是否永不查詢,優先順序最高 /// </summary> public bool NotSelect { get; set; } /// <summary> /// 欄位型別 /// </summary> public ConditionsType ConditionsTypes { get; set; } /// <summary> /// 字串運算子 /// </summary> public SymbolAttribute SymbolAttributes { get; set; } /// <summary> /// 是否字元分割,只在string或datetime型別下生效 /// </summary> public bool IsSplit { get; set; } /// <summary> /// 指定分割字串,只在IsSplit為true時生效 /// </summary> public string SplitString { get; set; } }
public enum ConditionsType
{
/// <summary>
/// 字元
/// </summary>
INT,
/// <summary>
/// 字串
/// </summary>
STRING,
/// <summary>
/// 時間
/// </summary>
DATETIME,
}
public enum SymbolAttribute
{
/// <summary>
/// 等於
/// </summary>
EQUAL,
/// <summary>
/// 包含
/// </summary>
CONTAILS,
/// <summary>
/// 從左包含
/// </summary>
STARTSWITH,
/// <summary>
/// 從右包含
/// </summary>
ENDSWITH,
/// <summary>
/// 大於
/// </summary>
GREATER,
/// <summary>
/// 小於
/// </summary>
LESS,
/// <summary>
/// 大於等於
/// </summary>
GREATEREQUAL,
/// <summary>
/// 小於等於
/// </summary>
LESSEQUAL,
/// <summary>
/// 區間
/// </summary>
INTERVAL
}
在查詢類中將作為查詢條件的欄位給上特性
[Conditions]
public string BillNO { get; set; }
[Conditions(ConditionsType.DATETIME, SymbolAttribute.INTERVAL,true,",")]
public string CreationTime { get; set; }
這裡因為我的時間查詢欄位前端傳遞的格式是"yyyy-MM-dd,yyyy-MM-dd"所以我給CreationTime的特性附上DATETIME型別,區間查詢,有分隔符,分隔符為","
最後開始實現倉儲方法
先建立一個SqlRepositorys倉儲類,注入IDbContextProvider,然後開始拼接sql字串
寫一個SqlQueryAndParameter方法,使用兩個泛型,T是返回的實體,也就是查詢結果,D是查詢引數
考慮到可能會存在查詢引數類中沒有定義,但是需要進行查詢的情況,所以在引數中會有一個List
/// <summary>
/// 在查詢條件的DTO中給欄位加特性ConditionsAttribute
/// 沒有ConditionsAttribute特性時,會根據欄位自身的型別進行查詢,int時按=,string時按like,datetime時搜尋當天
/// 有特性時 跟根據特性一定的特性來進行搜尋
/// NotSelect為永不查詢,即使DTO中有值也不會查詢
/// Enable是否啟用,如果不啟用的話,同沒有特性的邏輯(已廢棄)
/// symbolAttribute是判斷字元運算子,根據運算子進行查詢,目前有=,>,<,>=,<=,like,範圍等,詳情請看SymbolAttribute列舉
/// IsSplit為是否有分隔符,只有在運算子為範圍時生效
/// SplitString是分隔符的字元,只有在IsSplit為true時生效
/// 另外,此方法還支援自定義寫查詢條件,可以在param中定義不在DTO中的查詢條件,寫在sql字串中即可
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="D"></typeparam>
/// <param name="sql"></param>
/// <param name="queryParameter"></param>
/// <param name="pages"></param>
/// <param name="param"></param>
/// <returns></returns>
public IEnumerable<T> SqlQueryAndParameter<T, D>(string sql, QueryParameter<D> queryParameter, List<SqlParameter> param = null) where T : class, new() where D : class, new()
{
StringBuilder strsql = new StringBuilder();
strsql.Append(@"select * from (" + sql + ") t where 1=1");
if (param == null)
{
param = new List<SqlParameter>();
}
//var param = new List<SqlParameter>();
var datadto = queryParameter.data;
if (datadto != null)
{
PropertyInfo[] propertys = datadto.GetType().GetProperties();
foreach (var item in propertys)
{
var attribute = item.GetCustomAttribute<ConditionsAttribute>();
var type = item.PropertyType.FullName;
var value = item?.GetValue(datadto);
var valueS = Convert.ToString(item?.GetValue(datadto));
if (string.IsNullOrWhiteSpace(valueS))
{
continue;
}
if (attribute == null)
{
if (type.Contains("Int"))
{
if (Convert.ToInt32(value) <= 0)
{
continue;
}
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
}
else if (type.Contains("String"))
{
param.Add(new SqlParameter("@" + item.Name, "%" + value + "%"));
strsql.Append(" and t." + item.Name + " like @" + item.Name);
}
else if (type.Contains("Datetime"))
{
var date = ((DateTime)value).ToString("yyyy/MM/dd");
param.Add(new SqlParameter("@" + item.Name, date));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
}
else
{
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
}
}
else
{
if (!attribute.NotSelect)
{
var conditionsType = attribute.ConditionsTypes;
var symbolAttribute = attribute.SymbolAttributes;
switch (symbolAttribute)
{
case SymbolAttribute.EQUAL:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
break;
case SymbolAttribute.CONTAILS:
param.Add(new SqlParameter("@" + item.Name, "%" + value + "%"));
strsql.Append(" and t." + item.Name + " like @" + item.Name);
break;
case SymbolAttribute.STARTSWITH:
param.Add(new SqlParameter("@" + item.Name, "%" + value));
strsql.Append(" and t." + item.Name + " like @" + item.Name);
break;
case SymbolAttribute.ENDSWITH:
param.Add(new SqlParameter("@" + item.Name, value + "%"));
strsql.Append(" and t." + item.Name + " like @" + item.Name);
break;
case SymbolAttribute.GREATER:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " > @" + item.Name);
break;
case SymbolAttribute.LESS:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " < @" + item.Name);
break;
case SymbolAttribute.GREATEREQUAL:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " >= @" + item.Name);
break;
case SymbolAttribute.LESSEQUAL:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " <= @" + item.Name);
break;
case SymbolAttribute.INTERVAL:
if (attribute.IsSplit)
{
string[] timestring = valueS.Split(attribute.SplitString);
if (timestring != null)
{
if (timestring.Length == 1)
{
param.Add(new SqlParameter("@" + item.Name + "start", timestring[0]));
strsql.Append(" and (t." + item.Name + " >= @" + item.Name + "start )");
}
else if (timestring.Length == 2)
{
if (timestring[0] != "" && timestring[1] != "")
{
param.Add(new SqlParameter("@" + item.Name + "start", timestring[0]));
param.Add(new SqlParameter("@" + item.Name + "end", timestring[1]));
strsql.Append(" and (t." + item.Name + " >= @" + item.Name + "start and t." + item.Name + " <= @" + item.Name + "end)");
}
else if (timestring[0] != "" && timestring[1] == "")
{
param.Add(new SqlParameter("@" + item.Name + "start", timestring[0]));
strsql.Append(" and (t." + item.Name + " >= @" + item.Name + "start )");
}
else if (timestring[0] == "" && timestring[1] != "")
{
param.Add(new SqlParameter("@" + item.Name + "start", timestring[1]));
strsql.Append(" and ( t." + item.Name + " <= @" + item.Name + "end)");
}
}
}
}
break;
default:
switch (conditionsType)
{
case ConditionsType.INT:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
break;
case ConditionsType.STRING:
param.Add(new SqlParameter("@" + item.Name, "%" + value + "%"));
strsql.Append(" and t." + item.Name + " like @" + item.Name);
break;
case ConditionsType.DATETIME:
var date = ((DateTime)value).ToString("yyyy/MM/dd");
param.Add(new SqlParameter("@" + item.Name, date));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
break;
default:
param.Add(new SqlParameter("@" + item.Name, value));
strsql.Append(" and t." + item.Name + " = @" + item.Name);
break;
}
break;
}
}
else
{
continue;
}
}
}
}
queryParameter.Count = _dbContextProvider.GetDbContext().Set<T>().FromSqlRaw(strsql.ToString(), param.ToArray()).Count();
if (queryParameter.sidx == "")
{
queryParameter.sidx = "Id";
queryParameter.sord = "desc";
}
strsql.Append($@" order by {queryParameter.sidx} {queryParameter.sord} offset {(queryParameter.pageNum - 1) * queryParameter.pageSize} rows fetch next {queryParameter.pageSize} rows only");
return SqlQueryForParameter<T>(strsql.ToString(), param.ToArray());
}
public IEnumerable<T> SqlQueryForParameter<T>(string sql, params object[] parameters) where T : class, new()
{
return _dbContextProvider.GetDbContext().Set<T>().FromSqlRaw(sql, parameters);
}
最後是如何使用:
在service的查詢方法中只需要,寫好sql語句,然後把引數傳過去就行了
public List<Table1List> GetListall(QueryParameter<Table1Dto> queryParameter, RequestPages page)
{
string sql = @"select * from table1"
return _sqlRepository.SqlQueryAndParameter<Table1List, Table1Dto>(sql, queryParameter,page).ToList();
}
Controller中也不需要多餘的程式碼,使用定義的QueryParameter接收前端的傳值(ToJson,Success是另外寫的公共方法,轉Json和返回成功資訊,意思懂了就行)
public IActionResult GetListall([FromBody] QueryParameter<QueryStockinDto> queryParameter)
{
var list = table1App.GetListall(queryParameter, pages).ToJson();
return Success(list);
}
OK這樣,查詢和分頁就封裝好了,只需要寫好T-sql語句就行了,也不再需要管查詢條件之類的。
如果有更好的想法,歡迎交流,一起成長