前言
现实中认识的一个搞java(百万富婆)的大佬,已经转行做抖音主播了,搞技术的只能赶在年前再水一篇博客,不足之处欢迎拍砖,以免误人子弟,呔,真是太难受了
环境准备
- .net core 3.1
- MongoDB
- Navicat Premium 16
创建项目
1.选择ASP.NET Core Web API 项目模板
2.配置项目信息 项目名称,项目路径
3.配置项目其它信息,这里选择使用.net core 3.1 不配置https
添加依赖
1.获取MongoDB.Driver包 版本 2.18.0
方案A. install-package MongoDB.Driver 方案B. nuget包管理工具搜索 MongoDB.Driver 方案C. 选中项目,双击,添加 <ItemGroup> <PackageReference Include="MongoDB.Driver" Version="2.18.0" /> </ItemGroup>
注册中间件
1.添加配置文件
"MongoDB": { "ConnName": "mongodb://root:123456@127.0.0.1:27017/", "DatabaseName": "test" }
2.添加配置类
public class MongoDBConfig { /// <summary> /// 连接字符串 /// </summary> public string ConnName { get; set; } /// <summary> /// 数据库名称 /// </summary> public string DatabaseName { get; set; } }
3.注册配置文件与中间件
//读取配置文件 var mongoDBOptions = Configuration.GetSection("MongoDB").Get<MongoDBConfig>(); //注册配置 services.Configure<MongoDBConfig>(this.Configuration.GetSection("MongoDB")); //注册中间件 services.AddSingleton(new MongoClient(mongoDBOptions.ConnName));
其实就是注册了一个mongodb的客户端实例,然后用这个客户端实例来实现crud,如果你想优雅点(强迫症),也可以自定义一个拓展,来注册这个实例,这是一个通用技巧,后面使用到的类请自行注册
public static class MongoDBExtensions { public static void AddMongoDB(this IServiceCollection services, Action<MongoDBConfig> setupAction) { if (setupAction == null) { throw new ArgumentNullException(nameof(setupAction)); } var options = new MongoDBConfig(); setupAction(options); services.Configure(setupAction); services.AddSingleton(services.AddSingleton(new MongoClient(options.ConnName))); } }
使用拓展,来注册中间件
var mongoDBOptions = Configuration.GetSection("MongoDB").Get<MongoDBConfig>(); services.AddMongoDB(m => { m.ConnName = mongoDBOptions.ConnName; m.DatabaseName = mongoDBOptions.DatabaseName; });
封装基础操作
最常用的办法,就是弄个DBHelper类,这种简单粗暴的方式,在.net core里使用并不优雅(网上教程太多了,不想重复的水),这里模拟常规的仓储模式,进行演示
1.创建 Service,Repertoty,Model 文件夹
2.创建 BaseDbContext 类
public class BaseDbContext { private readonly IMongoDatabase _database; public BaseDbContext(MongoClient client, IOptions<MongoDBConfig> options) { //决定使用哪个库 _database = client.GetDatabase(options.Value.DatabaseName); } /// <summary> /// 数据库对象 /// </summary> public IMongoDatabase MongoDatabase { get{ return _database; } } }
如果项目只操作一个数据库,直接在基类里指定操作的数据库即可,如果需要操作多个数据库,可以再新建一个子类(TestDbContext),在子类里指定要操作的数据库
前面我已经注册了mongodb的客户端跟配置文件,所以在Startup类下,可以直接注册这个类,容器会自动管理这些依赖
services.AddSingleton(typeof(BaseDbContext));
如果你想更优雅点(强迫症),可以创建一个IBaseDbContext 接口,在接口里定义相关的规范
services.AddSingleton<IBaseDbContext>();
3.创建 BaseRepertoty 类
public class BaseRepertoty<T> where T : class, new() { private readonly IMongoDatabase _database; private readonly string _collName; public BaseRepertoty(BaseDbContext baseDbContext) { _database = baseDbContext.MongoDatabase; _collName = typeof(T).GetAttributeValue((TableAttribute m) => m.Name); _collName = _collName ?? typeof(T).Name; } }
前面我们已经注册了BaseDbContext,它的主要作用是确定操作的库,那BaseRepertoty就是对具体集合(表)的操作,这里使用泛型,避免重复的CRUD操作
我新建了一个拓展,用于获取实体类上真实集合(表)的名称,如果用户没有指定,则取实体类的类名,做为集合(表)的名称
public static class AttributeExtensions { public static TValue GetAttributeValue<TAttribute, TValue>( this Type type, Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute { var att = type.GetCustomAttributes( typeof(TAttribute), true ).FirstOrDefault() as TAttribute; if (att != null) { return valueSelector(att); } return default(TValue); } }
4.创建 UserInfoRepertoty 类
public class UserInfoRepertoty : BaseRepertoty<UserInfo> { public UserInfoRepertoty(BaseDbContext baseDbContext) : base(baseDbContext) { } }
它的作用是指定操作具体的集合(表),这个类是非必须的,它只是指定了泛型的具体类型,也可以在server层调用的时候指定
5.创建 UserInfo 实体类
[Table("UserInfo")] public class UserInfo { /// <summary> /// 主键 /// </summary> [BsonId] public ObjectId Id { get; set; } /// <summary> /// 用户名 /// </summary> public string Name { get; set; } /// <summary> /// 年龄 /// </summary> public int Age { get; set; } /// <summary> /// 创建时间 /// </summary> [BsonDateTimeOptions(Kind = DateTimeKind.Local)] public DateTime CreateTime { get; set; } /// <summary> /// 合作伙伴 /// </summary> public List<Partner> PartnerList { get; set; } /// <summary> /// 其它信息 /// </summary> public Info Info { get; set; } } public class Partner { /// <summary> /// 伙伴名称 /// </summary> public string Name { get; set; } /// <summary> /// 合作状态 /// </summary> public int? Status { get; set; } } public class Info { /// <summary> /// 身份编码 /// </summary> public string Code { get; set; } /// <summary> /// 图片 /// </summary> public string Pic { get; set; } }
mongodb里默认的主键是_id,它会自动生成,类型为ObjectId,强烈推荐使用自动生成,速度快,且能提供获取对应的时间戳的方法
ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是: 前4个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时 接下来的3个字节是机器标识码 紧接的两个字节由进程 id 组成 PID 最后三个字节是随机数
时间类型要做对应的处理,标记为当前时间
其它常用注解说明
BsonId修饰的字段对应BsonDocument中的_id; BsonDefaultValue(value)用于指定默认值; BsonIgnore表示不映射,即使BsonDocument中包含该字段也不会赋值给属性; BsonExtraElements修饰的字段用于存储没有映射到类中的其他属性; BsonElement可以指定修饰的属性映射到BsonDocument中的哪个字段;
使用mongodb的一个主要原因,就是它灵活的存储方式,类json,我们就来体验一番,基于这个模型来进行CRUD,我定义的类型如下,基本上能覆盖99%的使用场景
ObjectId (主键)
string (字符串)
int (数字)
object (对象)
array (数组)
我们在BaseRepertoty类里,实现CRUD,完整代码如下
public class BaseRepertoty<T> where T : class, new() { private readonly IMongoDatabase _database; private readonly string _collName; public BaseRepertoty(BaseDbContext baseDbContext) { _database = baseDbContext.MongoDatabase; _collName = typeof(T).GetAttributeValue((TableAttribute m) => m.Name); _collName = _collName ?? typeof(T).Name; } #region Add 添加一条数据 /// <summary> /// 添加一条数据 /// </summary> /// <param name="t">添加的实体</param> /// <returns></returns> public bool Add(T t) { try { var client = _database.GetCollection<T>(_collName); client.InsertOne(t); return true; } catch (Exception ex) { return false; } } #endregion #region AddAsync 异步添加一条数据 /// <summary> /// 异步添加一条数据 /// </summary> /// <param name="t">添加的实体</param> /// <returns></returns> public async Task<bool> AddAsync(T t) { try { var client = _database.GetCollection<T>(_collName); await client.InsertOneAsync(t); return true; } catch { return false; } } #endregion #region InsertMany 批量插入 /// <summary> /// 批量插入 /// </summary> /// <param name="t">实体集合</param> /// <returns></returns> public bool InsertMany(List<T> t) { try { var client = _database.GetCollection<T>(_collName); client.InsertMany(t); return true; } catch (Exception ex) { return false; } } #endregion #region InsertManyAsync 异步批量插入 /// <summary> /// 异步批量插入 /// </summary> /// <param name="t">实体集合</param> /// <returns></returns> public async Task<bool> InsertManyAsync(List<T> t) { try { var client = _database.GetCollection<T>(_collName); await client.InsertManyAsync(t); return true; } catch { return false; } } #endregion #region UpdateOne 修改数据(单条) /// <summary> /// 批量修改数据 /// </summary> /// <param name="update">要修改的字段</param> /// <param name="filter">修改条件</param> /// <returns></returns> public UpdateResult UpdateOne(UpdateDefinition<T> update, FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return client.UpdateOne(filter, update); } catch (Exception ex) { throw ex; } } #endregion #region UpdateOneAsync 异步批量修改数据 /// <summary> /// 异步批量修改数据 /// </summary> /// <param name="update">要修改的字段</param> /// <param name="filter">修改条件</param> /// <returns></returns> public async Task<UpdateResult> UpdateOneAsync(UpdateDefinition<T> update, FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return await client.UpdateOneAsync(filter, update); } catch (Exception ex) { throw ex; } } #endregion #region UpdateManay 批量修改数据 /// <summary> /// 批量修改数据 /// </summary> /// <param name="update">要修改的字段</param> /// <param name="filter">修改条件</param> /// <returns></returns> public UpdateResult UpdateManay(UpdateDefinition<T> update, FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return client.UpdateMany(filter, update); } catch (Exception ex) { throw ex; } } #endregion #region UpdateManayAsync 异步批量修改数据 /// <summary> /// 异步批量修改数据 /// </summary> /// <param name="update">要修改的字段</param> /// <param name="filter">修改条件</param> /// <returns></returns> public async Task<UpdateResult> UpdateManayAsync(UpdateDefinition<T> update, FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return await client.UpdateManyAsync(filter, update); } catch (Exception ex) { throw ex; } } #endregion #region DeleteOne 删除一条数据 /// <summary> /// 删除多条数据 /// </summary> /// <param name="filter">删除的条件</param> /// <returns></returns> public DeleteResult DeleteOne(FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return client.DeleteOne(filter); } catch (Exception ex) { throw ex; } } #endregion #region DeleteOneAsync 异步删除一条数据 /// <summary> /// 异步删除多条数据 /// </summary> /// <param name="filter">删除的条件</param> /// <returns></returns> public async Task<DeleteResult> DeleteOneAsync(FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return await client.DeleteOneAsync(filter); } catch (Exception ex) { throw ex; } } #endregion #region DeleteMany 删除多条数据 /// <summary> /// 删除多条数据 /// </summary> /// <param name="filter">删除的条件</param> /// <returns></returns> public DeleteResult DeleteMany(FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return client.DeleteMany(filter); } catch (Exception ex) { throw ex; } } #endregion #region DeleteManyAsync 异步删除多条数据 /// <summary> /// 异步删除多条数据 /// </summary> /// <param name="filter">删除的条件</param> /// <returns></returns> public async Task<DeleteResult> DeleteManyAsync(FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return await client.DeleteManyAsync(filter); } catch (Exception ex) { throw ex; } } #endregion #region FindOne 根据id查询一条数据 /// <summary> /// 根据id查询一条数据 /// </summary> /// <param name="id">objectid</param> /// <param name="field">要查询的字段,不写时查询全部</param> /// <returns></returns> public T FindOne(string id, bool isObjectId = true, string[] field = null) { try { var client = _database.GetCollection<T>(_collName); FilterDefinition<T> filter; if (isObjectId) { filter = Builders<T>.Filter.Eq("_id", new ObjectId(id)); } else { filter = Builders<T>.Filter.Eq("_id", id); } //不指定查询字段 if (field == null || field.Length == 0) { return client.Find(filter).FirstOrDefault<T>(); } //制定查询字段 var fieldList = new List<ProjectionDefinition<T>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<T>.Projection.Include(field[i].ToString())); } var projection = Builders<T>.Projection.Combine(fieldList); fieldList?.Clear(); return client.Find(filter).Project<T>(projection).FirstOrDefault<T>(); } catch (Exception ex) { throw ex; } } #endregion #region FindOneAsync 异步根据id查询一条数据 /// <summary> /// 异步根据id查询一条数据 /// </summary> /// <param name="id">objectid</param> /// <returns></returns> public async Task<T> FindOneAsync(string id, bool isObjectId = true, string[] field = null) { try { var client = _database.GetCollection<T>(_collName); FilterDefinition<T> filter; if (isObjectId) { filter = Builders<T>.Filter.Eq("_id", new ObjectId(id)); } else { filter = Builders<T>.Filter.Eq("_id", id); } //不指定查询字段 if (field == null || field.Length == 0) { return await client.Find(filter).FirstOrDefaultAsync(); } //制定查询字段 var fieldList = new List<ProjectionDefinition<T>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<T>.Projection.Include(field[i].ToString())); } var projection = Builders<T>.Projection.Combine(fieldList); fieldList?.Clear(); return await client.Find(filter).Project<T>(projection).FirstOrDefaultAsync(); } catch (Exception ex) { throw ex; } } #endregion #region FindList 查询集合 /// <summary> /// 查询集合 /// </summary> /// <param name="filter">查询条件</param> /// <param name="field">要查询的字段,不写时查询全部</param> /// <param name="sort">要排序的字段</param> /// <returns></returns> public List<T> FindList(FilterDefinition<T> filter, string[] field = null, SortDefinition<T> sort = null) { try { var client = _database.GetCollection<T>(_collName); //不指定查询字段 if (field == null || field.Length == 0) { if (sort == null) return client.Find(filter).ToList(); //进行排序 return client.Find(filter).Sort(sort).ToList(); } //制定查询字段 var fieldList = new List<ProjectionDefinition<T>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<T>.Projection.Include(field[i].ToString())); } var projection = Builders<T>.Projection.Combine(fieldList); fieldList?.Clear(); if (sort == null) return client.Find(filter).Project<T>(projection).ToList(); //排序查询 return client.Find(filter).Sort(sort).Project<T>(projection).ToList(); } catch (Exception ex) { throw ex; } } #endregion #region FindListAsync 异步查询集合 /// <summary> /// 异步查询集合 /// </summary> /// <param name="filter">查询条件</param> /// <param name="field">要查询的字段,不写时查询全部</param> /// <param name="sort">要排序的字段</param> /// <returns></returns> public async Task<List<T>> FindListAsync(FilterDefinition<T> filter, string[] field = null, SortDefinition<T> sort = null) { try { var client = _database.GetCollection<T>(_collName); //不指定查询字段 if (field == null || field.Length == 0) { //return await client.Find(new BsonDocument()).ToListAsync(); if (sort == null) return await client.Find(filter).ToListAsync(); return await client.Find(filter).Sort(sort).ToListAsync(); } //制定查询字段 var fieldList = new List<ProjectionDefinition<T>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<T>.Projection.Include(field[i].ToString())); } var projection = Builders<T>.Projection.Combine(fieldList); fieldList?.Clear(); if (sort == null) return await client.Find(filter).Project<T>(projection).ToListAsync(); //排序查询 return await client.Find(filter).Sort(sort).Project<T>(projection).ToListAsync(); } catch (Exception ex) { throw ex; } } #endregion #region FindListByPage 分页查询集合 /// <summary> /// 分页查询集合 /// </summary> /// <param name="filter">查询条件</param> /// <param name="pageIndex">当前页</param> /// <param name="pageSize">页容量</param> /// <param name="count">总条数</param> /// <param name="field">要查询的字段,不写时查询全部</param> /// <param name="sort">要排序的字段</param> /// <returns></returns> public List<T> FindListByPage(FilterDefinition<T> filter, int pageIndex, int pageSize, out long count, string[] field = null, SortDefinition<T> sort = null) { try { var client = _database.GetCollection<T>(_collName); count = client.CountDocuments(filter); //不指定查询字段 if (field == null || field.Length == 0) { if (sort == null) return client.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList(); //进行排序 return client.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList(); } //制定查询字段 var fieldList = new List<ProjectionDefinition<T>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<T>.Projection.Include(field[i].ToString())); } var projection = Builders<T>.Projection.Combine(fieldList); fieldList?.Clear(); //不排序 if (sort == null) return client.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList(); //排序查询 return client.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList(); } catch (Exception ex) { throw ex; } } #endregion #region FindListByPageAsync 异步分页查询集合 /// <summary> /// 异步分页查询集合 /// </summary> /// <param name="filter">查询条件</param> /// <param name="pageIndex">当前页</param> /// <param name="pageSize">页容量</param> /// <param name="field">要查询的字段,不写时查询全部</param> /// <param name="sort">要排序的字段</param> /// <returns></returns> public async Task<List<T>> FindListByPageAsync(FilterDefinition<T> filter, int pageIndex, int pageSize, string[] field = null, SortDefinition<T> sort = null) { try { var client = _database.GetCollection<T>(_collName); //不指定查询字段 if (field == null || field.Length == 0) { if (sort == null) return await client.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); //进行排序 return await client.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); } //制定查询字段 var fieldList = new List<ProjectionDefinition<T>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<T>.Projection.Include(field[i].ToString())); } var projection = Builders<T>.Projection.Combine(fieldList); fieldList?.Clear(); //不排序 if (sort == null) return await client.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); //排序查询 return await client.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); } catch (Exception ex) { throw ex; } } #endregion #region Count 根据条件获取总数 /// <summary> /// 根据条件获取总数 /// </summary> /// <param name="filter">条件</param> /// <returns></returns> public long Count(FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return client.CountDocuments(filter); } catch (Exception ex) { throw ex; } } #endregion #region CountAsync 异步根据条件获取总数 /// <summary> /// 异步根据条件获取总数 /// </summary> /// <param name="filter">条件</param> /// <returns></returns> public async Task<long> CountAsync(FilterDefinition<T> filter) { try { var client = _database.GetCollection<T>(_collName); return await client.CountDocumentsAsync(filter); } catch (Exception ex) { throw ex; } } #endregion }
6.创建 UserInfoService 类
public class UserInfoService { private readonly UserInfoRepertoty _userInfoRepertoty; private readonly FilterDefinitionBuilder<UserInfo> _buildFilter; public UserInfoService(UserInfoRepertoty userInfoRepertoty) { _userInfoRepertoty = userInfoRepertoty; _buildFilter = Builders<UserInfo>.Filter; } }
体验mongodb独特的使用方式,这里挑出两个具有代表性的操作进行讲解,测试数据
分页查询集合(表)
- 1.动态条件查询
- 2.查询文档指定字段
- 3.排序
- 4.分页
定义查询操作
FilterDefinition<UserInfo> filter = _buildFilter.Empty;
查询条件由查询条件构造器构建,这个地方网上很多示例写的有bug,当查询所有集合(表)数据的时候,filter = null 在转换的时候会抛出异常
//创建查询条件构造器 FilterDefinitionBuilder<UserInfo> _buildFilter = Builders<UserInfo>.Filter; //定义查询条件 FilterDefinition<UserInfo> filter = null; if(!string.IsNullOrEmpty(request.Name)) filter = _buildFilter.Eq(m => m.Name, request.Name);
这里有很多种类型的构造器,对应不同的操作
//排序操作 SortDefinition<UserInfo> sort //排序构造器 Builders<UserInfo>.Sort 修改操作 UpdateDefinition<UserInfo> update //修改构造器 Builders<UserInfo>.Update
完整的查询示例,比较麻烦的地方,是针对数组的条件查询,如果嵌套的层数比较多,查询也是同理的套娃查询
public class GetUserListRequest { /// <summary> /// 名称 /// </summary> public string Name { get; set; } /// <summary> /// 年龄 /// </summary> public int? Age { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime CreateTime { get; set; } /// <summary> /// 合作伙伴 /// </summary> public Partner PartnerList { get; set; } /// <summary> /// 信息 /// </summary> public Info Info { get; set; } } public async Task<List<UserInfo>> GetUserPageList(GetUserListRequest request) { if (request == null) return new List<UserInfo>(); FilterDefinition<UserInfo> filter = _buildFilter.Empty; SortDefinition<UserInfo> sort = Builders<UserInfo>.Sort.Ascending(m => m.Age); if (!string.IsNullOrEmpty(request.Name)) { filter = _buildFilter.Eq(m => m.Name, request.Name); } if (request.Age.HasValue) { filter = _buildFilter.Eq(m => m.Age, request.Age); } if (request.PartnerList != null) { if (request.PartnerList.Status.HasValue) { filter = _buildFilter.ElemMatch("PartnerList", Builders<Partner>.Filter.Eq(m => m.Status, request.PartnerList.Status)); } } if (request.Info != null) { if (!string.IsNullOrEmpty(request.Info.Code)) { filter = _buildFilter.Eq(m => m.Info.Code, request.Info.Code); } } var list = await _userInfoRepertoty.FindListByPageAsync(filter,1,3, new string[] { "Name","Age", "CreateTime" }, sort); return list; }
动态条件查询(查询全部)
var list = await _userInfoRepertoty.FindListByPageAsync(filter, 1, 30);
条件查询,排序,分页,查询指定字段
var list = await _userInfoRepertoty.FindListByPageAsync(filter,1,3, new string[] { "Name","Age", "CreateTime" }, sort);
除了使用对象模型进行操作,也可以直接使用mongodb原生的BsonDocument进行操作
var buildFilter = Builders<BsonDocument>.Filter; FilterDefinition<BsonDocument> filter = _buildFilter.Empty;; BsonDocument bson = new BsonDocument { { "Age",20}, {"Info.Status",3 } }; foreach (var bs in bson) { filter = _buildFilter.Eq(bs.Name, bs.Value); } var sort = Builders<BsonDocument>.Sort; var list = await collection.Find(filter).Sort(sort.Descending("age")).ToListAsync();
修改集合(表)的数据
- 1.修改满足条件的文档
- 2.修改文档的部分字段
public async Task UpdateUserInfo() { //修改条件 FilterDefinition<UserInfo> filter = _buildFilter.Empty; filter = _buildFilter.Eq(m => m.Name, "tibos"); filter = _buildFilter.ElemMatch(list => list.PartnerList, child => child.Status == 1); //修改字段 var update = Builders<UserInfo>.Update.Set("Age",100).Set("PartnerList.$.Name", "ppp").Set("PartnerList.$.Status", ""); await _userInfoRepertoty.UpdateManayAsync(update, filter); }