1. 程式人生 > 實用技巧 >對EFCORE分頁查詢的封裝的思路

對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語句就行了,也不再需要管查詢條件之類的。
如果有更好的想法,歡迎交流,一起成長