上一章我们把系统所需要的MongoDB集合设计好了,这一章我们的主要任务是使用.NET Core应用程序连接MongoDB并且封装MongoDB数据仓储和工作单元模式,因为本章内容涵盖的有点多关于仓储和工作单元的使用就放到下一章节中讲解了。仓储模式(Repository )带来的好处是一套代码可以适用于多个类,把常用的CRUD通用方法抽象出来通过接口形式集中管理,从而解除业务逻辑层与数据访问层之间的耦合,使业务逻辑层在存储、访问数据库时无须关心数据的来源及存储方式。工作单元模式(UnitOfWork)它是用来维护一个由已经被业务修改(如增加、删除和更新等)的业务对象组成的列表,跨多个请求的业务,统一管理事务,统一提交从而保障事物一致性的作用。
MongoDB从入门到实战之MongoDB简介
MongoDB从入门到实战之MongoDB快速入门
MongoDB从入门到实战之Docker快速安装MongoDB
MongoDB从入门到实战之MongoDB工作常用操作命令
MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(1)-后端项目框架搭建
MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(2)-Swagger框架集成
MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(3)-系统数据集合设计
欢迎各位看官老爷review,有帮助的别忘了给我个Star哦!!!
GitHub地址:https://github.com/YSGStudyHards/YyFlight.ToDoList
MongoRepository地址:https://github.com/YSGStudyHards/YyFlight.ToDoList/tree/main/Repository/Repository
参阅MongoDB的事务
说明:
MongoDB单机服务器不支持事务【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才支持事务,因为博主接下来都是在单机环境下操作,所以无法来演示Mongo事务操作,但是方法都已经是封装好了的,大家可以自己搭建集群实操。
原因:
MongoDB在使用分布式事务时需要进行多节点之间的协调和通信,而单机环境下无法实现这样的分布式协调和通信机制。但是,在MongoDB部署为一个集群(cluster)后,将多个计算机连接为一个整体,通过协调和通信机制实现了分布式事务的正常使用。从数据一致性和可靠性的角度来看,在分布式系统中实现事务处理是至关重要的。而在单机环境下不支持事务,只有在集群情况下才支持事务的设计方式是为了保证数据一致性和可靠性,并且也符合分布式系统的设计思想。
Install-Package MongoDB.Driver
前往appsettings.json文件中配置Mongo数据库信息:
"MongoSettings": {
"Connection": "mongodb://root:123456@local:27017/yyflight_todolist?authSource=admin", //MongoDB连接字符串
"DatabaseName": "yyflight_todolist" //MongoDB数据库名称
}
现在我们将定义MongoDB DBContext上下文类,具体到一个业务对象或需要被持久化的对象,这个上下文类将封装数据库的连接和集合。
该类应负责建立与所需数据库的连接,在建立连接后,该类将在内存中或按请求持有数据库上下文(基于API管道中配置的生命周期管理。)
public interface IMongoContext : IDisposable
{
///
/// 添加命令操作
///
///
///
Task AddCommandAsync(Func func);
///
/// 保存更改
///
///
Task SaveChangesAsync();
///
/// 获取集合数据
///
///
///
///
IMongoCollection GetCollection(string name);
}
public class MongoContext : IMongoContext
{
private IMongoDatabase _database;
private MongoClient _mongoClient;
private readonly IConfiguration _configuration;
private readonly List> _commands;
public IClientSessionHandle? Session = null;
public MongoContext(IConfiguration configuration)
{
_configuration = configuration;
// Every command will be stored and it'll be processed at SaveChanges
_commands = new List>();
// Configure mongo (You can inject the config, just to simplify)
_mongoClient = new MongoClient(_configuration["MongoSettings:Connection"]);
_database = _mongoClient.GetDatabase(_configuration["MongoSettings:DatabaseName"]);
}
///
/// 添加命令操作
///
/// 委托
///
public async Task AddCommandAsync(Func func)
{
_commands.Add(func);
await Task.CompletedTask;
}
///
/// 保存更改
/// TODO:MongoDB单机服务器不支持事务【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才支持事务
/// 原因:MongoDB在使用分布式事务时需要进行多节点之间的协调和通信,而单机环境下无法实现这样的分布式协调和通信机制。但是,在MongoDB部署为一个集群(cluster)后,将多个计算机连接为一个整体,通过协调和通信机制实现了分布式事务的正常使用。从数据一致性和可靠性的角度来看,在分布式系统中实现事务处理是至关重要的。而在单机环境下不支持事务,只有在集群情况下才支持事务的设计方式是为了保证数据一致性和可靠性,并且也符合分布式系统的设计思想。
///
///
public async Task SaveChangesAsync()
{
using (Session = await _mongoClient.StartSessionAsync())
{
Session.StartTransaction();
var commandTasks = _commands.Select(c => c());
await Task.WhenAll(commandTasks);
await Session.CommitTransactionAsync();
}
return _commands.Count;
}
///
/// 获取MongoDB集合
///
///
/// 集合名称
///
public IMongoCollection GetCollection(string name)
{
return _database.GetCollection(name);
}
///
/// 释放上下文
///
public void Dispose()
{
Session?.Dispose();
GC.SuppressFinalize(this);
}
}
Repository(仓储)是DDD(领域驱动设计)中的经典思想,可以归纳为介于实际业务层(领域层)和数据访问层之间的层,能让领域层能在感觉不到数据访问层的情况下,完成与数据库的交互和以往的DAO(数据访问)层相比,Repository层的设计理念更偏向于面向对象,而淡化直接对数据表进行的CRUD操作。
定义一个泛型Repository通用接口,抽象常用的增加,删除,修改,查询等操作方法。
public interface IMongoRepository where T : class, new()
{
#region 事务操作示例
///
/// 事务添加数据
///
/// 添加数据
///
Task AddTransactionsAsync(T objData);
///
/// 事务数据删除
///
/// objectId
///
Task DeleteTransactionsAsync(string id);
///
/// 事务异步局部更新(仅更新一条记录)
///
/// 过滤器
/// 更新条件
///
Task UpdateTransactionsAsync(FilterDefinition filter, UpdateDefinition update);
#endregion
#region 添加相关操作
///
/// 添加数据
///
/// 添加数据
///
Task AddAsync(T objData);
///
/// 批量插入
///
/// 实体集合
///
Task InsertManyAsync(List objDatas);
#endregion
#region 删除相关操作
///
/// 数据删除
///
/// objectId
///
Task DeleteAsync(string id);
///
/// 异步删除多条数据
///
/// 删除的条件
///
Task DeleteManyAsync(FilterDefinition filter);
#endregion
#region 修改相关操作
///
/// 指定对象异步修改一条数据
///
/// 要修改的对象
/// 修改条件
///
Task UpdateAsync(T obj, string id);
///
/// 局部更新(仅更新一条记录)
/// x.Id == 1 && x.Age > 18 && x.Gender == 0]]>
/// new T{ RealName = "Ray", Gender = 1}]]>
///
/// 筛选条件
/// 更新条件
///
Task UpdateAsync(Expression> expression, Expression> entity);
///
/// 异步局部更新(仅更新一条记录)
///
/// 过滤器
/// 更新条件
///
Task UpdateAsync(FilterDefinition filter, UpdateDefinition update);
///
/// 异步局部更新(仅更新多条记录)
///
/// 筛选条件
/// 更新条件
///
Task UpdateManyAsync(Expression> expression, UpdateDefinition update);
///
/// 异步批量修改数据
///
/// 要修改的字段
/// 更新条件
///
Task UpdateManayAsync(Dictionary dic, FilterDefinition filter);
#endregion
#region 查询统计相关操作
///
/// 通过ID主键获取数据
///
/// objectId
///
Task GetByIdAsync(string id);
///
/// 获取所有数据
///
///
Task> GetAllAsync();
///
/// 获取记录数
///
/// 筛选条件
///
Task CountAsync(Expression> expression);
///
/// 获取记录数
///
/// 过滤器
///
Task CountAsync(FilterDefinition filter);
///
/// 判断是否存在
///
/// 条件
///
Task ExistsAsync(Expression> predicate);
///
/// 异步查询集合
///
/// 查询条件
/// 要查询的字段,不写时查询全部
/// 要排序的字段
///
Task> FindListAsync(FilterDefinition filter, string[]? field = null, SortDefinition? sort = null);
///
/// 异步分页查询集合
///
/// 查询条件
/// 当前页
/// 页容量
/// 要查询的字段,不写时查询全部
/// 要排序的字段
///
Task> FindListByPageAsync(FilterDefinition filter, int pageIndex, int pageSize, string[]? field = null, SortDefinition? sort = null);
#endregion
}
public class MongoBaseRepository : IMongoRepository where T : class, new()
{
protected readonly IMongoContext _context;
protected readonly IMongoCollection _dbSet;
private readonly string _collectionName;
protected MongoBaseRepository(IMongoContext context)
{
_context = context;
_collectionName = typeof(T).GetAttributeValue((TableAttribute m) => m.Name) ?? typeof(T).Name;
_dbSet = _context.GetCollection(_collectionName);
}
#region 事务操作示例
///
/// 事务添加数据
///
/// 添加数据
///
public async Task AddTransactionsAsync(T objData)
{
await _context.AddCommandAsync(async () => await _dbSet.InsertOneAsync(objData));
}
///
/// 事务数据删除
///
/// objectId
///
public async Task DeleteTransactionsAsync(string id)
{
await _context.AddCommandAsync(() => _dbSet.DeleteOneAsync(Builders.Filter.Eq(" _id ", id)));
}
///
/// 事务异步局部更新(仅更新一条记录)
///
/// 过滤器
/// 更新条件
///
public async Task UpdateTransactionsAsync(FilterDefinition filter, UpdateDefinition update)
{
await _context.AddCommandAsync(() => _dbSet.UpdateOneAsync(filter, update));
}
#endregion
#region 添加相关操作
///
/// 添加数据
///
/// 添加数据
///
public async Task AddAsync(T objData)
{
await _dbSet.InsertOneAsync(objData);
}
///
/// 批量插入
///
/// 实体集合
///
public async Task InsertManyAsync(List objDatas)
{
await _dbSet.InsertManyAsync(objDatas);
}
#endregion
#region 删除相关操作
///
/// 数据删除
///
/// objectId
///
public async Task DeleteAsync(string id)
{
await _dbSet.DeleteOneAsync(Builders.Filter.Eq("_id", new ObjectId(id)));
}
///
/// 异步删除多条数据
///
/// 删除的条件
///
public async Task DeleteManyAsync(FilterDefinition filter)
{
return await _dbSet.DeleteManyAsync(filter);
}
#endregion
#region 修改相关操作
///
/// 指定对象异步修改一条数据
///
/// 要修改的对象
/// 修改条件
///
public async Task UpdateAsync(T obj, string id)
{
//修改条件
FilterDefinition filter = Builders.Filter.Eq("_id", new ObjectId(id));
//要修改的字段
var list = new List>();
foreach (var item in obj.GetType().GetProperties())
{
if (item.Name.ToLower() == "id") continue;
list.Add(Builders.Update.Set(item.Name, item.GetValue(obj)));
}
var updatefilter = Builders.Update.Combine(list);
await _dbSet.UpdateOneAsync(filter, updatefilter);
}
///
/// 局部更新(仅更新一条记录)
/// x.Id == 1 && x.Age > 18 && x.Gender == 0]]>
/// new T{ RealName = "Ray", Gender = 1}]]>
///
/// 筛选条件
/// 更新条件
///
public async Task UpdateAsync(Expression> expression, Expression> entity)
{
var fieldList = new List>();
if (entity.Body is MemberInitExpression param)
{
foreach (var item in param.Bindings)
{
var propertyName = item.Member.Name;
object propertyValue = null;
if (item is not MemberAssignment memberAssignment) continue;
if (memberAssignment.Expression.NodeType == ExpressionType.Constant)
{
if (memberAssignment.Expression is ConstantExpression constantExpression)
propertyValue = constantExpression.Value;
}
else
{
propertyValue = Expression.Lambda(memberAssignment.Expression, null).Compile().DynamicInvoke();
}
if (propertyName != "_id") //实体键_id不允许更新
{
fieldList.Add(Builders.Update.Set(propertyName, propertyValue));
}
}
}
await _dbSet.UpdateOneAsync(expression, Builders.Update.Combine(fieldList));
}
///
/// 异步局部更新(仅更新一条记录)
///
/// 过滤器
/// 更新条件
///
public async Task UpdateAsync(FilterDefinition filter, UpdateDefinition update)
{
await _dbSet.UpdateOneAsync(filter, update);
}
///
/// 异步局部更新(仅更新多条记录)
///
/// 筛选条件
/// 更新条件
///
public async Task UpdateManyAsync(Expression> expression, UpdateDefinition update)
{
await _dbSet.UpdateManyAsync(expression, update);
}
///
/// 异步批量修改数据
///
/// 要修改的字段
/// 更新条件
///
public async Task UpdateManayAsync(Dictionary dic, FilterDefinition filter)
{
T t = new T();
//要修改的字段
var list = new List>();
foreach (var item in t.GetType().GetProperties())
{
if (!dic.ContainsKey(item.Name)) continue;
var value = dic[item.Name];
list.Add(Builders.Update.Set(item.Name, value));
}
var updatefilter = Builders.Update.Combine(list);
return await _dbSet.UpdateManyAsync(filter, updatefilter);
}
#endregion
#region 查询统计相关操作
///
/// 通过ID主键获取数据
///
/// objectId
///
public async Task GetByIdAsync(string id)
{
var queryData = await _dbSet.FindAsync(Builders.Filter.Eq("_id", new ObjectId(id)));
return queryData.FirstOrDefault();
}
///
/// 获取所有数据
///
///
public async Task> GetAllAsync()
{
var queryAllData = await _dbSet.FindAsync(Builders.Filter.Empty);
return queryAllData.ToList();
}
///
/// 获取记录数
///
/// 筛选条件
///
public async Task CountAsync(Expression> expression)
{
return await _dbSet.CountDocumentsAsync(expression);
}
///
/// 获取记录数
///
/// 过滤器
///
public async Task CountAsync(FilterDefinition filter)
{
return await _dbSet.CountDocumentsAsync(filter);
}
///
/// 判断是否存在
///
/// 条件
///
public async Task ExistsAsync(Expression> predicate)
{
return await Task.FromResult(_dbSet.AsQueryable().Any(predicate));
}
///
/// 异步查询集合
///
/// 查询条件
/// 要查询的字段,不写时查询全部
/// 要排序的字段
///
public async Task> FindListAsync(FilterDefinition filter, string[]? field = null, SortDefinition? sort = null)
{
//不指定查询字段
if (field == null || field.Length == 0)
{
if (sort == null) return await _dbSet.Find(filter).ToListAsync();
return await _dbSet.Find(filter).Sort(sort).ToListAsync();
}
//指定查询字段
var fieldList = new List>();
for (int i = 0; i < field.Length; i++)
{
fieldList.Add(Builders.Projection.Include(field[i].ToString()));
}
var projection = Builders.Projection.Combine(fieldList);
fieldList?.Clear();
//不排序
if (sort == null) return await _dbSet.Find(filter).Project(projection).ToListAsync();
//排序查询
return await _dbSet.Find(filter).Sort(sort).Project(projection).ToListAsync();
}
///
/// 异步分页查询集合
///
/// 查询条件
/// 当前页
/// 页容量
/// 要查询的字段,不写时查询全部
/// 要排序的字段
///
public async Task> FindListByPageAsync(FilterDefinition filter, int pageIndex, int pageSize, string[]? field = null, SortDefinition? sort = null)
{
//不指定查询字段
if (field == null || field.Length == 0)
{
if (sort == null) return await _dbSet.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
//进行排序
return await _dbSet.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
}
//指定查询字段
var fieldList = new List>();
for (int i = 0; i < field.Length; i++)
{
fieldList.Add(Builders.Projection.Include(field[i].ToString()));
}
var projection = Builders.Projection.Combine(fieldList);
fieldList?.Clear();
//不排序
if (sort == null) return await _dbSet.Find(filter).Project(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
//排序查询
return await _dbSet.Find(filter).Sort(sort).Project(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
}
#endregion
}
工作单元模式是“维护一个被业务事务影响的对象列表,协调变化的写入和并发问题的解决”。具体来说,在C#工作单元模式中,我们通过UnitOfWork对象来管理多个Repository对象,同时UnitOfWork还提供了对事务的支持。对于一组需要用到多个Repository的业务操作,我们可以在UnitOfWork中创建一个事务,并将多个Repository操作放在同一个事务中处理,以保证数据的一致性。当所有Repository操作完成后,再通过UnitOfWork提交事务或者回滚事务。
///
/// 工作单元接口
///
public interface IUnitOfWork : IDisposable
{
///
/// 提交保存更改
///
///
Task Commit();
}
///
/// 工作单元类
///
public class UnitOfWork : IUnitOfWork
{
private readonly IMongoContext _context;
public UnitOfWork(IMongoContext context)
{
_context = context;
}
///
/// 提交保存更改
///
///
public async Task Commit()
{
return await _context.SaveChangesAsync() > 0;
}
public void Dispose()
{
_context.Dispose();
}
}
//注册数据库基础操作和工作单元
builder.Services.AddScoped();
builder.Services.AddScoped();
NoSQL – MongoDB Repository Implementation in .NET Core with Unit Testing example
ASP.NET CORE – MONGODB REPOSITORY PATTERN & UNIT OF WORK
C#/.NET/.NET Core推荐学习书籍
C#/.NET/.NET Core学习视频汇总
C#/.NET/.NET Core优秀项目框架推荐
C#/.NET/.NET Core面试宝典(基础版)
C#/.NET/.NET Core学习、工作、面试指南
了解作者&获取更多学习资料&加入DotNetGuide技术交流群
学习是一个永无止境的过程,你知道的越多,你不知道的也会越多,在有限的时间内坚持每天多学一点,你一定能成为你想要成为的那个人。不积跬步无以至千里,不积小流无以成江河!!!
See you next good day