由于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 Result.FromCode<TDto>(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写法。
让我知道你们有更好的想法!