由于 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)
- /// <summary>
- /// 查询一个集合中的所有数据 其集合的名称为 T 的名称
- /// </summary>
- 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 写法.
让我知道你们有更好的想法!