基於MongoDB.Driver的擴充套件

wu發表於2019-02-19

  由於MongoDB.Driver中的Find方法也支援表示式寫法,結合【通用查詢設計思想】這篇文章中的查詢思想,個人基於MongoDB擴充套件了一些常用的方法。

  首先我們從常用的查詢開始,由於MongoDB.Driver支援類似於AutoMapper返回的指定屬性(Project<TDto>方法),所以這裡都是基於泛型的擴充套件

  查詢

      /// <summary>
        /// 同步查詢指定條件的資料,並且返回指定型別TDto
        /// </summary>
        /// <typeparam name="TEntity">查詢實體</typeparam>
        /// <typeparam name="TDto">返回型別</typeparam>
        /// <param name="source"></param>
        /// <param name="query"></param>
        public static IFindFluent<TEntity, TDto> FindSync<TEntity, TDto>(this IMongoCollection<TEntity> source, IQuery<TEntity> query)
            where TEntity : class
        {
            var projection = GetTDtoReturnProperties<TEntity, TDto>();

            var expression = query?.GenerateExpression();
            if (null == expression)
            {
                var emptyExpression = Builders<TEntity>.Filter.Empty;
                return source.Find(emptyExpression).Project<TDto>(projection);
            }

            return source.Find(expression).Project<TDto>(projection);
        }

       /// <summary>
        /// 獲取指定的返回列
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TDto"></typeparam>
        /// <returns></returns>
        private static ProjectionDefinition<TEntity, TDto> GetTDtoReturnProperties<TEntity, TDto>()
            where TEntity : class
        {
            var returnType = typeof(TDto);

            var fieldList = new List<ProjectionDefinition<TEntity>>();
            foreach (var property in returnType.GetProperties())
            {
                fieldList.Add(Builders<TEntity>.Projection.Include(property.Name));
            }

            return Builders<TEntity>.Projection.Combine(fieldList);
        }

  這裡主要是利用了IQuery介面中的GenerateExpression方法,如果前端傳來了查詢引數,則拼裝返回我們的表示式,如果沒有,預設返回一個空的Filter,再通過Project<TDto>對映關係到TDto上。

  排序

     /// <summary>
        /// 排序方法
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TDto"></typeparam>
        /// <param name="source"></param>
        /// <param name="sortInfo"></param>
        /// <returns></returns>
        public static IFindFluent<TEntity, TDto> Sort<TEntity, TDto>(this IFindFluent<TEntity, TDto> source, ISortInfo sortInfo)
            where TEntity : class
        {
            var sort = Builders<TEntity>.Sort;
            SortDefinition<TEntity> sortDefinition = null;
            if (sortInfo != null)
            {
                if (!string.IsNullOrWhiteSpace(sortInfo.Order) && !string.IsNullOrWhiteSpace(sortInfo.Field))
                {
                    if (sortInfo.Order.Contains("asc"))
                        sortDefinition = sort.Ascending(sortInfo.Field);
                    if (sortInfo.Order.Contains("desc"))
                        sortDefinition = sort.Descending(sortInfo.Field);
                }
            }

            return source.Sort(sortDefinition);
        }

  這裡前端是使用了LayUI的表格,所以API中的引數是Order和Field,我們也可以結合例如JQuery的DataTable或者其他框架的表格,只是引數SortInfo稍微不一樣,大家結合實際情況來更改業務即可。

 

  分頁

    /// <summary>
        /// 查詢指定條件的資料
        /// </summary>
        /// <typeparam name="TEntity">查詢的實體</typeparam>
        /// <typeparam name="TDto">返回的型別</typeparam>
        /// <param name="source"></param>
        /// <param name="query">查詢條件</param>
        /// <param name="page">分頁資訊</param>
        public static async Task<PageResult<TDto>> ToPageResultAsync<TEntity, TDto>(this IMongoCollection<TEntity> source, IQuery<TEntity> query, IPageInfo page)
            where TEntity : class
        {
            var pageIndex = Math.Max(0, page.PageIndex);
            var pageSize = Math.Max(1, page.PageSize);

            var cursor = source.FindSync<TEntity, TDto>(query);

            var pageResult = new PageResult<TDto>
            {
                PageIndex = pageIndex,
                PageSize = pageSize,
                TotalCount = (int)await cursor.CountDocumentsAsync(),
                Data = await cursor.Skip(pageSize * (pageIndex - 1)).Limit(pageSize).Sort(page).ToListAsync()
            };

            return pageResult;
        }

   增和刪暫時不寫了,官方API也提供了批處理的介面,都可以直接用的。

  來看一下我們這幾個擴充套件方法的應用(基於aspnet core)

  public class MongoHelper
    {        
        private static readonly string DbName = ConfigHelper.GetSetting("MongoDb").ToString();
        private static readonly string ConnStr = ConfigHelper.GetSetting("MongoDbConnStr").ToString();
        private static IMongoDatabase Db { get; }

        private static readonly object LockHelper = new object();

        #region cotr

        static MongoHelper()
        {
            if (Db == null)
            {
                lock (LockHelper)
                {
                    if (Db == null)
                    {
                        var client = new MongoClient(ConnStr);
                        Db = client.GetDatabase(DbName);
                    }
                }
            }
        }

        #endregion

        #region query

        /// <summary>  
        /// 查詢一個集合中的所有資料 其集合的名稱為T的名稱  
        /// </summary>  
        /// <typeparam name="TEntity">該集合資料的所屬型別</typeparam>
        /// <typeparam name="TDto">返回型別</typeparam>
        /// <returns>返回一個Result<TDto />
        /// </returns>  
        public static async Task<Result<TDto>> QueryAsync<TEntity, TDto>(IQuery<TEntity> query, string collectionName = "")
            where TEntity : class
        {
            //檢查是否存在該文件
            var existed = await CollectionExists(Db, string.IsNullOrWhiteSpace(collectionName) ? DbName : collectionName);
            if (existed)
            {
                var collection = Db.GetCollection<TEntity>(string.IsNullOrWhiteSpace(collectionName) ? DbName : collectionName);
                var result = collection.FindSync<TEntity, TDto>(query);
                var listResult = await result.ToListAsync();
                return Result.FromData(listResult.FirstOrDefault());
            }
            else
            {
                return new Result<TDto>
                {
                    Code = ResultCode.NoSuchCollection
                };
            }
        }

        /// <summary>  
        /// 查詢一個集合中的所有資料 其集合的名稱為T的名稱  
        /// </summary>  
        /// <typeparam name="TEntity">該集合資料的所屬型別</typeparam>
        /// <typeparam name="TDto">返回資料型別</typeparam>
        /// <returns>返回一個List列表</returns>  
        public static async Task<Result<List<TDto>>> QueryListAsync<TEntity, TDto>(IQuery<TEntity> query, string collectionName = "")
            where TEntity : class
        {
            var collection = Db.GetCollection<TEntity>(string.IsNullOrWhiteSpace(collectionName) ? DbName : collectionName);
            var result = collection.FindSync<TEntity, TDto>(query);
            var listResult = await result.ToListAsync();
            return Result.FromData(listResult);
        }

        /// <summary>
        /// 分頁方法
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <typeparam name="TDto"></typeparam>
        /// <param name="collectionName">指定文件名稱</param>
        /// <param name="query"></param>
        /// <returns></returns>
        public static async Task<PageResult<TDto>> QueryPageResultAsync<TEntity, TDto>(PageQuery<TEntity> query, string collectionName = "")
            where TEntity : class
        {
            //檢查是否存在該文件
            var existed = await CollectionExists(Db, string.IsNullOrWhiteSpace(collectionName) ? DbName : collectionName);
            if (existed)
            {
                var collection = Db.GetCollection<TEntity>(string.IsNullOrWhiteSpace(collectionName) ? DbName : collectionName);
                return await collection.ToPageResultAsync<TEntity, TDto>(query, query);
            }
            else
            {
                return new PageResult<TDto>
                {
                    Code = ResultCode.NoSuchCollection
                };
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// 檢查是否存在該文件
        /// </summary>
        /// <param name="database">指定的資料庫</param>
        /// <param name="collectionName">文件名稱</param>
        /// <returns></returns>
        private static async Task<bool> CollectionExists(IMongoDatabase database, string collectionName)
        {
            var options = new ListCollectionsOptions
            {
                Filter = Builders<BsonDocument>.Filter.Eq("name", collectionName)
            };

            return await database.ListCollections(options).AnyAsync();
        }
        #endregion
    }

  有了這個幫助類,我們可以看一下應用層的具體應用

      /// <summary>
        /// TEntity的分頁查詢
        /// </summary>
        /// <typeparam name="TEntity">查詢實體</typeparam>
        /// <typeparam name="TDto">返回結果</typeparam>
        /// <param name="query">查詢條件</param>
        /// <returns></returns>
        public async Task<PageResult<TDto>> GetPagedLogsAsync<TEntity, TDto>(PageQuery<TEntity> query)
            where TEntity : class
        {
            return await MongoHelper.QueryPageResultAsync<TEntity, TDto>(query, typeof(TEntity).Name);
        }

        /// <summary>
        /// TEntity的查詢
        /// </summary>
        /// <typeparam name="TEntity">查詢實體</typeparam>
        /// <typeparam name="TDto">返回結果</typeparam>
        /// <param name="query">查詢條件</param>
        /// <returns></returns>
        public async Task<Result<TDto>> GetSpecifyLogAsync<TEntity, TDto>(IQuery<TEntity> query)
            where TEntity : class
        {
            return await MongoHelper.QueryAsync<TEntity, TDto>(query, typeof(TEntity).Name);
        }

  看一下我們Controller的呼叫

      /// <summary>
        /// 獲取指定條件的日誌分頁列表
        /// </summary>
        /// <param name="query">查詢引數</param>
        /// <returns></returns>
        [HttpPost]
        public async Task<PageResult<LogResult>> SearchPagedLogsAsync(LogPageQuery query)
        {
            return await _logBll.GetPagedLogsAsync<Log, LogResult>(query);
        }

        /// <summary>
        /// 獲取指定條件的日誌
        /// </summary>
        /// <param name="id">查詢引數</param>
        /// <returns></returns>
        public async Task<Result<LogResult>> GetLogByIdAsync(string id)
        {
            if (string.IsNullOrWhiteSpace(id))
                return Result.FromCode<LogResult>(ResultCode.Fail);
            return await _logBll.GetSpecifyLogAsync<Log, LogResult>(new Query<Log>(m => m.Id == new ObjectId(id)));
        }

   我這裡的例子是為了符合單一職責的設計原則,所以指定了GetLogByIdAsync這樣的單一介面,如果大家喜歡單個方法滿足更多功能,可以參照【通用查詢設計思想】文章中的Controller寫法。

  

  讓我知道你們有更好的想法!

 

相關文章