.NET Core MongoDB数据仓储和工作单元模式封装
前言
上一章我们把系统所需要的MongoDB集合设计好了,这一章我们的主要任务是使用.NET Core应用程序连接MongoDB并且封装MongoDB数据仓储和工作单元模式,因为本章内容涵盖的有点多关于仓储和工作单元的使用就放到下一章节中讲解了。仓储模式(Repository )带来的好处是一套代码可以适用于多个类,把常用的CRUD通用方法抽象出来通过接口形式集中管理,从而解除业务逻辑层与数据访问层之间的耦合,使业务逻辑层在存储、访问数据库时无须关心数据的来源及存储方式。工作单元模式(UnitOfWork)它是用来维护一个由已经被业务修改(如增加、删除和更新等)的业务对象组成的列表,跨多个请求的业务,统一管理事务,统一提交从而保障事物一致性的作用。
MongoDB从入门到实战的相关教程
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)-系统数据集合设计👉
MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(4)-MongoDB数据仓储和工作单元模式封装👉
YyFlight.ToDoList项目源码地址
欢迎各位看官老爷review,有帮助的别忘了给我个Star哦💖!!!
GitHub地址:GitHub - YSGStudyHards/YyFlight.ToDoList: 【.NET8 MongoDB 待办清单系统】.NET8 MongoDB从入门到实战基础教程,该项目后端使用的是.NET8、前端页面使用Blazor、使用MongoDB存储数据,更多相关内容大家可以看目录中的MongoDB从入门到实战的相关教程。该系列教程可作为.NET Core入门项目进行学习,感兴趣的小伙伴可以关注博主和我一起学习共同进步。
MongoRepository地址:https://github.com/YSGStudyHards/YyFlight.ToDoList/tree/main/Repository/Repository
MongoDB事务使用前提说明
参阅MongoDB的事务
说明:
MongoDB单机服务器不支持事务【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才支持事务,因为博主接下来都是在单机环境下操作,所以无法来演示Mongo事务操作,但是方法都已经是封装好了的,大家可以自己搭建集群实操。
原因:
MongoDB在使用分布式事务时需要进行多节点之间的协调和通信,而单机环境下无法实现这样的分布式协调和通信机制。但是,在MongoDB部署为一个集群(cluster)后,将多个计算机连接为一个整体,通过协调和通信机制实现了分布式事务的正常使用。从数据一致性和可靠性的角度来看,在分布式系统中实现事务处理是至关重要的。而在单机环境下不支持事务,只有在集群情况下才支持事务的设计方式是为了保证数据一致性和可靠性,并且也符合分布式系统的设计思想。
MongoDB.Driver驱动安装
1、直接命令自动安装
Install-Package MongoDB.Driver
2、搜索Nuget手动安装
MongoSettings数据库连接配置
前往appsettings.json文件中配置Mongo数据库信息:
"MongoSettings": {"Connection": "mongodb://root:123456@local:27017/yyflight_todolist?authSource=admin", //MongoDB连接字符串"DatabaseName": "yyflight_todolist" //MongoDB数据库名称}
定义单例的MongoClient
基于MongoDB的最佳实践对于MongoClient最好设置为单例注入,因为在MongoDB.Driver中MongoClient已经被设计为线程安全可以被多线程共享,这样可还以避免反复实例化MongoClient带来的开销,避免在极端情况下的性能低下。
我们这里设计一个MongoConnection类,用于包裹这个MongoClient,然后将其以单例模式注入IoC容器中。
定义IMongoConnection接口
public interface IMongoConnection{public MongoClient MongoDBClient { get; set; }public IMongoDatabase DatabaseName { get; set; }}
定义MongoConnection类
public class MongoConnection : IMongoConnection{//基于MongoDB的最佳实践对于MongoClient最好设置为单例注入,因为在MongoDB.Driver中MongoClient已经被设计为线程安全可以被多线程共享,这样可还以避免反复实例化MongoClient带来的开销,避免在极端情况下的性能低下。//我们这里设计一个MongoConnection类,用于包裹这个MongoClient,然后将其以单例模式注入IoC容器中。public MongoClient MongoDBClient { get; set; }public IMongoDatabase DatabaseName { get; set; }public MongoConnection(IConfiguration configuration){MongoDBClient = new MongoClient(configuration["MongoSettings:Connection"]);DatabaseName = MongoDBClient.GetDatabase(configuration["MongoSettings:DatabaseName"]);}}
定义Mongo DBContext上下文
现在我们将定义MongoDB DBContext上下文类,具体到一个业务对象或需要被持久化的对象,这个上下文类将封装数据库的连接和集合。
该类应负责建立与所需数据库的连接,在建立连接后,该类将在内存中或按请求持有数据库上下文(基于API管道中配置的生命周期管理。)
定义IMongoContext接口
public interface IMongoContext : IDisposable{/// <summary>/// 添加命令操作/// </summary>/// <param name="func">委托 方法接受一个 Func<IClientSessionHandle, Task> 委托作为参数,该委托表示一个需要 IClientSessionHandle 对象作为参数并返回一个异步任务的方法</param>/// <returns></returns>Task AddCommandAsync(Func<IClientSessionHandle, Task> func);/// <summary>/// 提交更改并返回受影响的行数/// TODO:MongoDB单机服务器不支持事务【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才支持事务/// 原因:MongoDB在使用分布式事务时需要进行多节点之间的协调和通信,而单机环境下无法实现这样的分布式协调和通信机制。但是,在MongoDB部署为一个集群(cluster)后,将多个计算机连接为一个整体,通过协调和通信机制实现了分布式事务的正常使用。从数据一致性和可靠性的角度来看,在分布式系统中实现事务处理是至关重要的。而在单机环境下不支持事务,只有在集群情况下才支持事务的设计方式是为了保证数据一致性和可靠性,并且也符合分布式系统的设计思想。/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <returns></returns>Task<int> SaveChangesAsync(IClientSessionHandle session);/// <summary>/// 初始化Mongodb会话对象session/// </summary>/// <returns></returns>Task<IClientSessionHandle> StartSessionAsync();/// <summary>/// 获取集合数据/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <returns></returns>IMongoCollection<T> GetCollection<T>(string name);}
定义MongoContext类
public class MongoContext : IMongoContext{private readonly IMongoDatabase _databaseName;private readonly MongoClient _mongoClient;//这里将 _commands 中的每个元素都定义为一个 Func<IClientSessionHandle, Task> 委托,此委托表示一个需要 IClientSessionHandle 对象作为参数并返回一个异步任务的方法//每个委托都表示一个MongoDB 会话(session)对象和要执行的命令private readonly List<Func<IClientSessionHandle, Task>> _commands = new List<Func<IClientSessionHandle, Task>>();public MongoContext(IMongoConnection mongoConnection){_mongoClient = mongoConnection.MongoDBClient;_databaseName = mongoConnection.DatabaseName;}/// <summary>/// 添加命令操作/// </summary>/// <param name="func">方法接受一个 Func<IClientSessionHandle, Task> 委托作为参数,该委托表示一个需要 IClientSessionHandle 对象作为参数并返回一个异步任务的方法</param>/// <returns></returns>public async Task AddCommandAsync(Func<IClientSessionHandle, Task> func){_commands.Add(func);await Task.CompletedTask;}/// <summary>/// 提交更改并返回受影响的行数/// TODO:MongoDB单机服务器不支持事务【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才支持事务/// 原因:MongoDB在使用分布式事务时需要进行多节点之间的协调和通信,而单机环境下无法实现这样的分布式协调和通信机制。但是,在MongoDB部署为一个集群(cluster)后,将多个计算机连接为一个整体,通过协调和通信机制实现了分布式事务的正常使用。从数据一致性和可靠性的角度来看,在分布式系统中实现事务处理是至关重要的。而在单机环境下不支持事务,只有在集群情况下才支持事务的设计方式是为了保证数据一致性和可靠性,并且也符合分布式系统的设计思想。/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <returns></returns>public async Task<int> SaveChangesAsync(IClientSessionHandle session){try{session.StartTransaction();//开始事务foreach (var command in _commands){await command(session);//语句实现了对事务中所有操作的异步执行,并等待它们完成。如果没有错误发生,程序会继续执行session.CommitTransactionAsync();方法,将之前进行的所有更改一起提交到MongoDB服务器上,从而实现事务提交。}await session.CommitTransactionAsync();//提交事务return _commands.Count;}catch (Exception ex){await session.AbortTransactionAsync();//回滚事务return 0;}finally{_commands.Clear();//清空_commands列表中的元素}}/// <summary>/// 初始化Mongodb会话对象session/// </summary>/// <returns></returns>public async Task<IClientSessionHandle> StartSessionAsync(){var session = await _mongoClient.StartSessionAsync();return session;}/// <summary>/// 获取MongoDB集合/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name">集合名称</param>/// <returns></returns>public IMongoCollection<T> GetCollection<T>(string name){return _databaseName.GetCollection<T>(name);}/// <summary>/// 释放上下文/// </summary>public void Dispose(){GC.SuppressFinalize(this);}}
定义通用泛型Repository
Repository(仓储)是DDD(领域驱动设计)中的经典思想,可以归纳为介于实际业务层(领域层)和数据访问层之间的层,能让领域层能在感觉不到数据访问层的情况下,完成与数据库的交互和以往的DAO(数据访问)层相比,Repository层的设计理念更偏向于面向对象,而淡化直接对数据表进行的CRUD操作。
定义IMongoRepository接口
定义一个泛型Repository通用接口,抽象常用的增加,删除,修改,查询等操作方法。
public interface IMongoRepository<T> where T : class, new(){#region 事务操作示例/// <summary>/// 事务添加数据/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <param name="objData">添加数据</param>/// <returns></returns>Task AddTransactionsAsync(IClientSessionHandle session, T objData);/// <summary>/// 事务数据删除/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <param name="id">objectId</param>/// <returns></returns>Task DeleteTransactionsAsync(IClientSessionHandle session, string id);/// <summary>/// 事务异步局部更新(仅更新一条记录)/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <param name="filter">过滤器</param>/// <param name="update">更新条件</param>/// <returns></returns>Task UpdateTransactionsAsync(IClientSessionHandle session, FilterDefinition<T> filter, UpdateDefinition<T> update);#endregion#region 添加相关操作/// <summary>/// 添加数据/// </summary>/// <param name="objData">添加数据</param>/// <returns></returns>Task AddAsync(T objData);/// <summary>/// 批量插入/// </summary>/// <param name="objDatas">实体集合</param>/// <returns></returns>Task InsertManyAsync(List<T> objDatas);#endregion#region 删除相关操作/// <summary>/// 数据删除/// </summary>/// <param name="id">objectId</param>/// <returns></returns>Task DeleteAsync(string id);/// <summary>/// 异步删除多条数据/// </summary>/// <param name="filter">删除的条件</param>/// <returns></returns>Task<DeleteResult> DeleteManyAsync(FilterDefinition<T> filter);#endregion#region 修改相关操作/// <summary>/// 指定对象异步修改一条数据/// </summary>/// <param name="obj">要修改的对象</param>/// <param name="id">修改条件</param>/// <returns></returns>Task UpdateAsync(T obj, string id);/// <summary>/// 局部更新(仅更新一条记录)/// <para><![CDATA[expression 参数示例:x => x.Id == 1 && x.Age > 18 && x.Gender == 0]]></para>/// <para><![CDATA[entity 参数示例:y => new T{ RealName = "Ray", Gender = 1}]]></para>/// </summary>/// <param name="expression">筛选条件</param>/// <param name="entity">更新条件</param>/// <returns></returns>Task UpdateAsync(Expression<Func<T, bool>> expression, Expression<Action<T>> entity);/// <summary>/// 异步局部更新(仅更新一条记录)/// </summary>/// <param name="filter">过滤器</param>/// <param name="update">更新条件</param>/// <returns></returns>Task UpdateAsync(FilterDefinition<T> filter, UpdateDefinition<T> update);/// <summary>/// 异步局部更新(仅更新多条记录)/// </summary>/// <param name="expression">筛选条件</param>/// <param name="update">更新条件</param>/// <returns></returns>Task UpdateManyAsync(Expression<Func<T, bool>> expression, UpdateDefinition<T> update);/// <summary>/// 异步批量修改数据/// </summary>/// <param name="dic">要修改的字段</param>/// <param name="filter">更新条件</param>/// <returns></returns>Task<UpdateResult> UpdateManayAsync(Dictionary<string, string> dic, FilterDefinition<T> filter);#endregion#region 查询统计相关操作/// <summary>/// 通过ID主键获取数据/// </summary>/// <param name="id">objectId</param>/// <returns></returns>Task<T> GetByIdAsync(string id);/// <summary>/// 获取所有数据/// </summary>/// <returns></returns>Task<IEnumerable<T>> GetAllAsync();/// <summary>/// 获取记录数/// </summary>/// <param name="expression">筛选条件</param>/// <returns></returns>Task<long> CountAsync(Expression<Func<T, bool>> expression);/// <summary>/// 获取记录数/// </summary>/// <param name="filter">过滤器</param>/// <returns></returns>Task<long> CountAsync(FilterDefinition<T> filter);/// <summary>/// 判断是否存在/// </summary>/// <param name="predicate">条件</param>/// <returns></returns>Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate);/// <summary>/// 异步查询集合/// </summary>/// <param name="filter">查询条件</param>/// <param name="field">要查询的字段,不写时查询全部</param>/// <param name="sort">要排序的字段</param>/// <returns></returns>Task<List<T>> FindListAsync(FilterDefinition<T> filter, string[]? field = null, SortDefinition<T>? sort = null);/// <summary>/// 异步分页查询集合/// </summary>/// <param name="filter">查询条件</param>/// <param name="pageIndex">当前页</param>/// <param name="pageSize">页容量</param>/// <param name="field">要查询的字段,不写时查询全部</param>/// <param name="sort">要排序的字段</param>/// <returns></returns>Task<List<T>> FindListByPageAsync(FilterDefinition<T> filter, int pageIndex, int pageSize, string[]? field = null, SortDefinition<T>? sort = null);#endregion}
实现泛型MongoBaseRepository基类
public class MongoBaseRepository<T> : IMongoRepository<T> where T : class, new(){protected readonly IMongoContext _context;protected readonly IMongoCollection<T> _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<T>(_collectionName);}#region 事务操作示例/// <summary>/// 事务添加数据/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <param name="objData">添加数据</param>/// <returns></returns>public async Task AddTransactionsAsync(IClientSessionHandle session, T objData){await _context.AddCommandAsync(async (session) => await _dbSet.InsertOneAsync(objData));}/// <summary>/// 事务数据删除/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <param name="id">objectId</param>/// <returns></returns>public async Task DeleteTransactionsAsync(IClientSessionHandle session, string id){await _context.AddCommandAsync((session) => _dbSet.DeleteOneAsync(Builders<T>.Filter.Eq(" _id ", id)));}/// <summary>/// 事务异步局部更新(仅更新一条记录)/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <param name="filter">过滤器</param>/// <param name="update">更新条件</param>/// <returns></returns>public async Task UpdateTransactionsAsync(IClientSessionHandle session, FilterDefinition<T> filter, UpdateDefinition<T> update){await _context.AddCommandAsync((session) => _dbSet.UpdateOneAsync(filter, update));}#endregion#region 添加相关操作/// <summary>/// 添加数据/// </summary>/// <param name="objData">添加数据</param>/// <returns></returns>public async Task AddAsync(T objData){await _dbSet.InsertOneAsync(objData);}/// <summary>/// 批量插入/// </summary>/// <param name="objDatas">实体集合</param>/// <returns></returns>public async Task InsertManyAsync(List<T> objDatas){await _dbSet.InsertManyAsync(objDatas);}#endregion#region 删除相关操作/// <summary>/// 数据删除/// </summary>/// <param name="id">objectId</param>/// <returns></returns>public async Task DeleteAsync(string id){await _dbSet.DeleteOneAsync(Builders<T>.Filter.Eq("_id", new ObjectId(id)));}/// <summary>/// 异步删除多条数据/// </summary>/// <param name="filter">删除的条件</param>/// <returns></returns>public async Task<DeleteResult> DeleteManyAsync(FilterDefinition<T> filter){return await _dbSet.DeleteManyAsync(filter);}#endregion#region 修改相关操作/// <summary>/// 指定对象异步修改一条数据/// </summary>/// <param name="obj">要修改的对象</param>/// <param name="id">修改条件</param>/// <returns></returns>public async Task UpdateAsync(T obj, string id){//修改条件FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", new ObjectId(id));//要修改的字段var list = new List<UpdateDefinition<T>>();foreach (var item in obj.GetType().GetProperties()){if (item.Name.ToLower() == "id") continue;list.Add(Builders<T>.Update.Set(item.Name, item.GetValue(obj)));}var updatefilter = Builders<T>.Update.Combine(list);await _dbSet.UpdateOneAsync(filter, updatefilter);}/// <summary>/// 局部更新(仅更新一条记录)/// <para><![CDATA[expression 参数示例:x => x.Id == 1 && x.Age > 18 && x.Gender == 0]]></para>/// <para><![CDATA[entity 参数示例:y => new T{ RealName = "Ray", Gender = 1}]]></para>/// </summary>/// <param name="expression">筛选条件</param>/// <param name="entity">更新条件</param>/// <returns></returns>public async Task UpdateAsync(Expression<Func<T, bool>> expression, Expression<Action<T>> entity){var fieldList = new List<UpdateDefinition<T>>();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<T>.Update.Set(propertyName, propertyValue));}}}await _dbSet.UpdateOneAsync(expression, Builders<T>.Update.Combine(fieldList));}/// <summary>/// 异步局部更新(仅更新一条记录)/// </summary>/// <param name="filter">过滤器</param>/// <param name="update">更新条件</param>/// <returns></returns>public async Task UpdateAsync(FilterDefinition<T> filter, UpdateDefinition<T> update){await _dbSet.UpdateOneAsync(filter, update);}/// <summary>/// 异步局部更新(仅更新多条记录)/// </summary>/// <param name="expression">筛选条件</param>/// <param name="update">更新条件</param>/// <returns></returns>public async Task UpdateManyAsync(Expression<Func<T, bool>> expression, UpdateDefinition<T> update){await _dbSet.UpdateManyAsync(expression, update);}/// <summary>/// 异步批量修改数据/// </summary>/// <param name="dic">要修改的字段</param>/// <param name="filter">更新条件</param>/// <returns></returns>public async Task<UpdateResult> UpdateManayAsync(Dictionary<string, string> dic, FilterDefinition<T> filter){T t = new T();//要修改的字段var list = new List<UpdateDefinition<T>>();foreach (var item in t.GetType().GetProperties()){if (!dic.ContainsKey(item.Name)) continue;var value = dic[item.Name];list.Add(Builders<T>.Update.Set(item.Name, value));}var updatefilter = Builders<T>.Update.Combine(list);return await _dbSet.UpdateManyAsync(filter, updatefilter);}#endregion#region 查询统计相关操作/// <summary>/// 通过ID主键获取数据/// </summary>/// <param name="id">objectId</param>/// <returns></returns>public async Task<T> GetByIdAsync(string id){var queryData = await _dbSet.FindAsync(Builders<T>.Filter.Eq("_id", new ObjectId(id)));return queryData.FirstOrDefault();}/// <summary>/// 获取所有数据/// </summary>/// <returns></returns>public async Task<IEnumerable<T>> GetAllAsync(){var queryAllData = await _dbSet.FindAsync(Builders<T>.Filter.Empty);return queryAllData.ToList();}/// <summary>/// 获取记录数/// </summary>/// <param name="expression">筛选条件</param>/// <returns></returns>public async Task<long> CountAsync(Expression<Func<T, bool>> expression){return await _dbSet.CountDocumentsAsync(expression);}/// <summary>/// 获取记录数/// </summary>/// <param name="filter">过滤器</param>/// <returns></returns>public async Task<long> CountAsync(FilterDefinition<T> filter){return await _dbSet.CountDocumentsAsync(filter);}/// <summary>/// 判断是否存在/// </summary>/// <param name="predicate">条件</param>/// <returns></returns>public async Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate){return await Task.FromResult(_dbSet.AsQueryable().Any(predicate));}/// <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){//不指定查询字段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<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 _dbSet.Find(filter).Project<T>(projection).ToListAsync();//排序查询return await _dbSet.Find(filter).Sort(sort).Project<T>(projection).ToListAsync();}/// <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){//不指定查询字段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<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 _dbSet.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();//排序查询return await _dbSet.Find(filter).Sort(sort).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();}#endregion}
工作单元模式
工作单元模式是“维护一个被业务事务影响的对象列表,协调变化的写入和并发问题的解决”。具体来说,在C#工作单元模式中,我们通过UnitOfWork对象来管理多个Repository对象,同时UnitOfWork还提供了对事务的支持。对于一组需要用到多个Repository的业务操作,我们可以在UnitOfWork中创建一个事务,并将多个Repository操作放在同一个事务中处理,以保证数据的一致性。当所有Repository操作完成后,再通过UnitOfWork提交事务或者回滚事务。
定义IUnitOfWork接口
/// <summary>/// 工作单元接口/// </summary>public interface IUnitOfWork : IDisposable{/// <summary>/// 提交保存更改/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <returns></returns>Task<bool> Commit(IClientSessionHandle session);/// <summary>/// 初始化MongoDB会话对象session/// </summary>/// <returns></returns>Task<IClientSessionHandle> InitTransaction();}
定义UnitOfWork类
/// <summary>/// 工作单元类/// </summary>public class UnitOfWork : IUnitOfWork{private readonly IMongoContext _context;public UnitOfWork(IMongoContext context){_context = context;}/// <summary>/// 提交保存更改/// </summary>/// <param name="session">MongoDB 会话(session)对象</param>/// <returns></returns>public async Task<bool> Commit(IClientSessionHandle session){return await _context.SaveChangesAsync(session) > 0;}/// <summary>/// 初始化MongoDB会话对象session/// </summary>/// <returns></returns>public async Task<IClientSessionHandle> InitTransaction(){return await _context.StartSessionAsync();}public void Dispose(){_context.Dispose();}}
注册数据库基础操作和工作单元
//注册数据库基础操作和工作单元 builder.Services.AddSingleton<IMongoConnection, MongoConnection>(); builder.Services.AddScoped<IMongoContext, MongoContext>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); builder.Services.AddScoped<IUserRepository, UserRepository>();
参考文章
NoSQL – MongoDB Repository Implementation in .NET Core with Unit Testing example
ASP.NET CORE – MONGODB REPOSITORY PATTERN & UNIT OF WORK
相关文章:

.NET Core MongoDB数据仓储和工作单元模式封装
前言 上一章我们把系统所需要的MongoDB集合设计好了,这一章我们的主要任务是使用.NET Core应用程序连接MongoDB并且封装MongoDB数据仓储和工作单元模式,因为本章内容涵盖的有点多关于仓储和工作单元的使用就放到下一章节中讲解了。仓储模式(R…...
lua:有关表访问的metamethod
针对在两种正常状态:表的不存在的域的查询和修改,Lua也提供了改变 tables的行为的方法。 index metamethod 我们可以通过index元方法来实现访问table内部不存在的域时人为操控返回数据。 比如以下测试代码: local set {1,2,3} setmetata…...

【MySQL】索引事务
MySQL索引事务 1. 索引1.1 概念1.2 作用1.3 使用场景1.4 使用1.5 案例 2. 事务2.2 事物的概念2.3 使用 3. 内容重点总结 1. 索引 1.1 概念 索引是一种特殊的文件,包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引, 并指定索引的类…...

ChatGPT重大升级:能自动记住用户的习惯和喜好,用户有权决定是否共享数据给OpenAI
OpenAI刚刚宣布了ChatGPT的一项激动人心的更新! OpenAI在ChatGPT中新加了记忆功能和用户控制选项,这意味着GPT能够在与用户的互动中记住之前的对话内容,并利用这些信息在后续的交谈中提供更加相关和定制化的回答。 这一功能目前正处于测试阶…...

CSS设置盒子阴影
语法 box-shadow: *h-shadow v-shadow blur spread color* inset; 注释: box-shadow向框添加一个或多个阴影. 该属性是由逗号分隔的阴影列表,每个阴影由2-4个长度值、可选的颜色值及可选的inset关键词来规定。省略长度的值是0。 外阴影 a、给元素右边框和下边框加外阴影——把…...

文件夹删不掉,显示在另一个文件中打开怎么办
问题: 一、想要删掉这个文件夹,却因为文件夹中的文件打开了删不掉,这里我因为做的测试,所以是知道打开了什么 二、一般情况下文件比较多时,是不知道打开了什么的,长这个样子 解决: 一、打开任…...

阿里云香港云服务器租用_BGP多线网络_CN2高速线路测试
阿里云香港服务器中国香港数据中心网络线路类型BGP多线精品,中国电信CN2高速网络高质量、大规格BGP带宽,运营商精品公网直连中国内地,时延更低,优化海外回中国内地流量的公网线路,可以提高国际业务访问质量。阿里云服务…...
C# 异步方法的使用场景
我一直认为C#的异步方法只是一堆华而不实的东西,坑特别多,比起直接自建线程也没有任何优势。 直到有一天,一个需求场景,让我再次想到了C#的异步方法。 需求场景如下:需要写一个程序控制机械臂完成各种动作。每个动作要…...

Lua 教程
Lua 教程 (今天又又又开新坑啦) Lua 教程 手册简介 Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放。 手册说明 Lua是什么? Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de …...

CleanMyMac X2024版本有哪些常见的使用场景?
CleanMyMac X作为一款Mac电脑清理和优化工具,具有多种使用场景。以下是一些常见的使用场景: 清理系统垃圾文件:CleanMyMac X可以智能扫描Mac磁盘空间,清理系统冗余文件和各种软件应用产生的垃圾文件,如缓存、日志文件…...
《Docker快速入门:从0到1构建你的第一个容器!》
《Docker快速入门:从0到1构建你的第一个容器!》 前言 欢迎来到Docker的世界,一个让应用程序打包、部署和运行更加容易的神奇平台。Docker改变了我们对于应用开发和分发的看法,它通过容器技术让软件的携带和运行变得前所未有的轻…...

NLP_Transformer架构
文章目录 Transformer架构剖析编码器-解码器架构各种注意力的应用Transformer中的自注意力Transformer中的多头自注意力Transformer中的编码器-解码器注意力Transformer中的注意力掩码和因果注意力 编码器的输入和位置编码编码器的内部结构编码器的输出和编码器-解码器的连接解…...
CVE-2012-2311 漏洞复现
CVE-2012-2311 这个漏洞被爆出来以后,PHP官方对其进行了修补,发布了新版本5.4.2及5.3.12,但这个修复是不完全的,可以被绕过,进而衍生出CVE-2012-2311漏洞。 PHP的修复方法是对-进行了检查: if(query_str…...

多线程面试题汇总
多线程面试题汇总 一、多线程1、线程的生命周期2、线程的创建(函数创建)3、线程的创建(使用类)4、守护线程 二、全局解释器锁1、使用单线程实现累加到5000000002、使用多线程实现累加到5000000003、总结 三、线程安全1、多线程之数…...

CentOS7.9+Kubernetes1.29.2+Docker25.0.3高可用集群二进制部署
CentOS7.9Kubernetes1.29.2Docker25.0.3高可用集群二进制部署 Kubernetes高可用集群(Kubernetes1.29.2Docker25.0.3)二进制部署二进制软件部署flannel v0.22.3网络,使用的etcd是版本3,与之前使用版本2不同。查看官方文档进行了解…...

STM32——OLED菜单(二级菜单)
文章目录 一.补充二. 二级菜单代码 简介:首先在我的51 I2C里面有OLED详细讲解,本期代码从51OLED基础上移植过来的,可以先看完那篇文章,在看这个,然后按键我是用的定时器扫描不会堵塞程序,可以翻开我的文章有单独的定时…...
配置Vite+React+TS项目
初始化 执行npm create vite并填写项目名、用那个框架。。 配置 路径别名 在vite.config.ts里面配置: import { defineConfig } from vite import react from vitejs/plugin-react import path from pathexport default defineConfig({plugins: [react()],reso…...

2.13:C语言测试题
21.(b) 6 22.(b) cd 23.b) 5 4 1 3 2 栈:先进后出 24. b,c,d:10,12,120 25.2,5 26.越界访问,可能正常输出,可能段错误 27. 0,41 28. a)11 b) 320 29. aab; ba-b; aa-b; 30. p150x801005; p250x810…...
ubuntu22.04 有一台机器说有4T硬盘,但是df的时候看不到,怎么查找
在 Ubuntu 22.04 上,如果你有一块硬盘在使用df命令时未显示,这通常意味着硬盘尚未被挂载或者根本未被分区和格式化。以下是一些步骤来帮助你识别和准备新硬盘: 1. 检查硬盘是否被系统识别 首先,使用lsblk命令来检查系统是否识别…...

【机器学习】数据清洗之识别重复点
🎈个人主页:甜美的江 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步…...

Bash shell四则运算
文章目录 四则运算1. expr 命令2. $(( )) 表达式(推荐)3. $[ ] 表达式(已弃用)4. let 命令小数运算i 和 i 区别 四则运算 算术运算: - * / %(取模,求余数) Bash sh…...
论对生产力决定生产关系的批判:突破决定论的桎梏
笔言: 在学生时代认为"生产力决定生产关系"很有道理,但是进入社会参与市场竞争时候,才发现这种想法太天真了,当生产力一只赔钱时候谁也不会感兴趣;当生产力产生利润,比如1%30%,100%,3…...

MVCC理解
MySQL的MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种高效的并发控制机制,通过维护数据的多个版本实现读写操作的并行执行,显著提升数据库的并发性能和数据一致性。 MVCC 的实现依赖于:隐…...

【C语言编译与链接】--翻译环境和运行环境,预处理,编译,汇编,链接
目录 一.翻译环境和运行环境 二.翻译环境 2.1--预处理(预编译) 2.2--编译 2.2.1--词法分析 2.2.2--语法分析 2.2.3--语义分析 2.3--汇编 2.4--链接 三.运行环境 🔥个人主页:草莓熊Lotso的个人主页 🎬作者简介:C研发…...
Linux 与 Windows:哪个操作系统适合你?
Linux vs Windows:系统选择的关键考量 在数字化转型浪潮中,操作系统作为底层基础设施的重要性日益凸显。Linux与Windows作为主流选择,其差异不仅体现在技术架构上,更深刻影响着开发效率、运维成本与安全性。本文将从7个核心维度展开对比分析,并提供典型应用场景建…...
AutoCompose - 携程自动编排【开源】
AutoCompose - 携程自动编排【开源】 AutoCompose是一款单事件驱动(无状态)的流程引擎。使用本框架,能够轻松实现复杂服务的自动化编排【零配置、零编码】,能够显著提高开发维护效率。支持同步编程、异步编程(已支持Co…...
Neo4j 数据可视化与洞察获取:原理、技术与实践指南
在关系密集型数据的分析领域,Neo4j 凭借其强大的图数据模型脱颖而出。然而,将复杂的连接关系转化为直观见解,需要专业的数据可视化技术和分析方法。本文将深入探讨 Neo4j 数据可视化的核心原理、关键技术、实用技巧以及结合图数据科学库&…...

【HarmonyOS 5】Laya游戏如何鸿蒙构建发布详解
【HarmonyOS 5】Laya游戏如何鸿蒙构建发布详解 一、前言 LayaAir引擎是国内最强大的全平台引擎之一,当年H5小游戏火的时候,腾讯入股了腊鸭。我还在游戏公司的时候,17年曾经开发使用腊鸭的H5小游戏,很怀念当年和腊鸭同事一起解决…...

从 iPhone 备份照片: 保存iPhone图片的5种方法
随着智能手机越来越融入我们的生活,我们的照片已成为我们设备上最有价值的数据形式之一。然而,iPhone内部存储空间仍然有限,因此我们需要将iPhone中的照片备份到另一个地方,以释放空间并确保珍贵的图像记忆的安全。阅读本指南&…...
vb.net oledb-Access 数据库本身不支持命名参数,赋值必须和参数顺序一致才行
参数顺序问题:OleDb 通常依赖参数添加的顺序而非名称,为什么顺序要一样? OleDbParameter 顺序依赖性的原因 OleDb 数据提供程序依赖参数添加顺序而非名称,这是由 OLE DB 规范和 Access 数据库的工作机制共同决定的。理解这个问题需要从数据库底层通信…...