当前位置: 首页 > news >正文

.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集合设计好了&#xff0c;这一章我们的主要任务是使用.NET Core应用程序连接MongoDB并且封装MongoDB数据仓储和工作单元模式&#xff0c;因为本章内容涵盖的有点多关于仓储和工作单元的使用就放到下一章节中讲解了。仓储模式&#xff08;R…...

lua:有关表访问的metamethod

针对在两种正常状态&#xff1a;表的不存在的域的查询和修改&#xff0c;Lua也提供了改变 tables的行为的方法。 index metamethod 我们可以通过index元方法来实现访问table内部不存在的域时人为操控返回数据。 比如以下测试代码&#xff1a; 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 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引&#xff0c; 并指定索引的类…...

ChatGPT重大升级:能自动记住用户的习惯和喜好,用户有权决定是否共享数据给OpenAI

OpenAI刚刚宣布了ChatGPT的一项激动人心的更新&#xff01; OpenAI在ChatGPT中新加了记忆功能和用户控制选项&#xff0c;这意味着GPT能够在与用户的互动中记住之前的对话内容&#xff0c;并利用这些信息在后续的交谈中提供更加相关和定制化的回答。 这一功能目前正处于测试阶…...

CSS设置盒子阴影

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

文件夹删不掉,显示在另一个文件中打开怎么办

问题&#xff1a; 一、想要删掉这个文件夹&#xff0c;却因为文件夹中的文件打开了删不掉&#xff0c;这里我因为做的测试&#xff0c;所以是知道打开了什么 二、一般情况下文件比较多时&#xff0c;是不知道打开了什么的&#xff0c;长这个样子 解决&#xff1a; 一、打开任…...

阿里云香港云服务器租用_BGP多线网络_CN2高速线路测试

阿里云香港服务器中国香港数据中心网络线路类型BGP多线精品&#xff0c;中国电信CN2高速网络高质量、大规格BGP带宽&#xff0c;运营商精品公网直连中国内地&#xff0c;时延更低&#xff0c;优化海外回中国内地流量的公网线路&#xff0c;可以提高国际业务访问质量。阿里云服务…...

C# 异步方法的使用场景

我一直认为C#的异步方法只是一堆华而不实的东西&#xff0c;坑特别多&#xff0c;比起直接自建线程也没有任何优势。 直到有一天&#xff0c;一个需求场景&#xff0c;让我再次想到了C#的异步方法。 需求场景如下&#xff1a;需要写一个程序控制机械臂完成各种动作。每个动作要…...

Lua 教程

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

CleanMyMac X2024版本有哪些常见的使用场景?

CleanMyMac X作为一款Mac电脑清理和优化工具&#xff0c;具有多种使用场景。以下是一些常见的使用场景&#xff1a; 清理系统垃圾文件&#xff1a;CleanMyMac X可以智能扫描Mac磁盘空间&#xff0c;清理系统冗余文件和各种软件应用产生的垃圾文件&#xff0c;如缓存、日志文件…...

《Docker快速入门:从0到1构建你的第一个容器!》

《Docker快速入门&#xff1a;从0到1构建你的第一个容器&#xff01;》 前言 欢迎来到Docker的世界&#xff0c;一个让应用程序打包、部署和运行更加容易的神奇平台。Docker改变了我们对于应用开发和分发的看法&#xff0c;它通过容器技术让软件的携带和运行变得前所未有的轻…...

NLP_Transformer架构

文章目录 Transformer架构剖析编码器-解码器架构各种注意力的应用Transformer中的自注意力Transformer中的多头自注意力Transformer中的编码器-解码器注意力Transformer中的注意力掩码和因果注意力 编码器的输入和位置编码编码器的内部结构编码器的输出和编码器-解码器的连接解…...

CVE-2012-2311 漏洞复现

CVE-2012-2311 这个漏洞被爆出来以后&#xff0c;PHP官方对其进行了修补&#xff0c;发布了新版本5.4.2及5.3.12&#xff0c;但这个修复是不完全的&#xff0c;可以被绕过&#xff0c;进而衍生出CVE-2012-2311漏洞。 PHP的修复方法是对-进行了检查&#xff1a; if(query_str…...

多线程面试题汇总

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

CentOS7.9+Kubernetes1.29.2+Docker25.0.3高可用集群二进制部署

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

STM32——OLED菜单(二级菜单)

文章目录 一.补充二. 二级菜单代码 简介&#xff1a;首先在我的51 I2C里面有OLED详细讲解&#xff0c;本期代码从51OLED基础上移植过来的&#xff0c;可以先看完那篇文章&#xff0c;在看这个&#xff0c;然后按键我是用的定时器扫描不会堵塞程序,可以翻开我的文章有单独的定时…...

配置Vite+React+TS项目

初始化 执行npm create vite并填写项目名、用那个框架。。 配置 路径别名 在vite.config.ts里面配置&#xff1a; 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 栈&#xff1a;先进后出 24. b,c,d:10,12,120 25.2,5 26.越界访问&#xff0c;可能正常输出&#xff0c;可能段错误 27. 0&#xff0c;41 28. a&#xff09;11 b) 320 29. aab; ba-b; aa-b; 30. p150x801005; p250x810…...

ubuntu22.04 有一台机器说有4T硬盘,但是df的时候看不到,怎么查找

在 Ubuntu 22.04 上&#xff0c;如果你有一块硬盘在使用df命令时未显示&#xff0c;这通常意味着硬盘尚未被挂载或者根本未被分区和格式化。以下是一些步骤来帮助你识别和准备新硬盘&#xff1a; 1. 检查硬盘是否被系统识别 首先&#xff0c;使用lsblk命令来检查系统是否识别…...

【机器学习】数据清洗之识别重复点

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步…...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之NavDestination组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之NavDestination组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、NavDestination组件 作为NavRouter组件的子组件&#xff0c;用于显示导…...

tokio tcp通信

引入crate tokio { version "1.35.1", features ["full"] } 服务端 use std::time::Duration; use tokio::{io::{AsyncBufReadExt, AsyncWriteExt},net::{tcp::{OwnedReadHalf, OwnedWriteHalf},TcpListener, TcpStream,},sync::mpsc, };#[tokio::ma…...

LCR 122. 路径加密【简单】

LCR 122. 路径加密 假定一段路径记作字符串 path&#xff0c;其中以 "." 作为分隔符。现需将路径加密&#xff0c;加密方法为将 path 中的分隔符替换为空格 " "&#xff0c;请返回加密后的字符串。 示例 1&#xff1a; 输入&#xff1a;path "a.ae…...

SpringUtils 工具类,方便在非spring管理环境中获取bean

应用场景&#xff1a; 1 可用在工具类中&#xff0c; 2 spring【Controller,service】环境中&#xff0c; 3 其中的一个方法getAopProxy可获得代理对象&#xff0c;需要将 EnableAspectJAutoProxy(exposeProxy true) 允许获取代理对象 import org.springframework.aop.framew…...

JavaWeb之请求

请求 客户端请求由ServletRequest类型的request对象表示&#xff0c;在HTTP请求场景下&#xff0c;容器提供的请求对象的具体类型为HttpServletRequest HTTP的请求消息分为三部分&#xff1a;请求行、请求头、请求正文。 请求行对应方法 // 获取请求行中的协议名和版本public S…...

VsCode中常用的正则表达式操作

在vscode中可以使用正则表达式来进行搜索内容&#xff0c;极大的方便了我们对大量数据中需要查看的信息进行筛选&#xff0c;使用正则搜索时点击 .* 此文章会持续补充常用的正则操作 1.光标选中搜索到的内容 将搜索的内容进行全选&#xff0c;举例&#xff1a;在如下文件中我需…...

ubuntu22.04@laptop OpenCV Get Started: 007_color_spaces

ubuntu22.04laptop OpenCV Get Started: 007_color_spaces 1. 源由2. 颜色空间2.1 RGB颜色空间2.2 LAB颜色空间2.3 YCrCb颜色空间2.4 HSV颜色空间 3 代码工程结构3.1 C应用Demo3.2 Python应用Demo 4. 重点分析4.1 interactive_color_detect4.2 interactive_color_segment4.3 da…...

mysql 查询性能优化关键点总结

MySQL查询性能优化是数据库管理的重要环节&#xff0c;良好的性能优化可以提高查询效率&#xff0c;降低系统负载。以下是一些关键点&#xff0c;用于优化MySQL查询性能&#xff1a; 1. 索引优化 索引是MySQL查询优化的重要手段&#xff0c;合理的索引可以大大…...

React - 分页插件默认是英文怎么办

英文组件的通用解决方案 这里以分页插件为例&#xff1a; 大家可以看到&#xff0c;最后的这个页面跳转提示文字为Go to&#xff0c;不是中文&#xff0c;而官网里面的案例则是&#xff1a; 解决方案&#xff1a; import { ConfigProvider } from antd; import zhCN from an…...

揭开Markdown的秘籍:引用|代码块|超链接

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Markdown指南、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️Markdown 引用1.1 &#x1f514;引用1.2 &#x1f514;嵌套引用1.3 &…...

【C语言】Debian安装并编译内核源码

在Debian 10中安装并编译内核源码的过程如下&#xff1a; 1. 安装依赖包 首先需要确保有足够的权限来安装包。为了编译内核&#xff0c;需要有一些基础的工具和库。 sudo apt update sudo apt upgrade sudo apt install build-essential libncurses-dev bison flex libssl-d…...

使用 C++23 从零实现 RISC-V 模拟器(6):权限支持

本节内容增加了权限表示&#xff0c;设置了三种权限。当 cpu 初始化时默认的权限为 Machine 模式。接下来实现这三种特权模式&#xff0c;随后实现 sret 和 mret 指令。 RISC-V定义了三种特权等级&#xff0c;分别是用户态&#xff08;User Mode&#xff09;、监管态&#xff…...

针对某终端安全自检钓鱼工具的分析

前言 朋友微信找到我&#xff0c;说某微信群利用0day通告进行钓鱼&#xff0c;传播名为“终端安全自检工具”的恶意文件&#xff0c;然后还给了两个IP地址&#xff0c;如下&#xff1a; 咱们就来详细看看这个工具吧。 样本信息 拿到样本&#xff0c;样本的图标&#xff0c;如…...

XSS数据接收平台

一.使用xss数据接收平台的好处&#xff1a; 正常执行反射型xss和存储型xss&#xff0c;反射型xss在执行poc时&#xff0c;会直接在页面弹出执行注入的poc代码&#xff1b;存储型则是&#xff0c;在将poc代码注入用户的系统中后&#xff0c;用户访问有存储型xss的地方&#xff…...

MySQL 基础知识(六)之数据查询(一)

目录 1 基本查询 1.1 查询相关列 (select * / 列名) 1.2 别名 (as) 1.3 去重 (distinct) 1.4 对列中的数据进行运算 (、-、*、/) 2 条件查询 (where) 2.1 等值查询 () 2.2 非等值查询 (>、<、>、<、!、><) 2.3 逻辑判断 (and、or、not) 2.4 区间判…...

C#使用哈希表对XML文件进行查询

目录 一、使用的方法 1.Hashtable哈希表 2.Hashtable哈希表的Add方法 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;示例 3.XML文件的使用 二、实例 1.源码 2.生成效果 可以通过使用哈希表可以对XML文件进行查询。 一、使用的方法 1.Hashtable哈希表…...

vscode写MATLAB配置

vscode写MATLAB python下载 官网说明Versions of Python Compatible with MATLAB Products by Release - MATLAB & Simulink 不确定这三列都表示什么意思&#xff0c;尽量安装这三列都有的python版本吧&#xff0c;我安装的 MATLAB R2023b,python选择的是3.11.5 …...

第13章 网络 Page734 “I/O对象”的链式传递 单独的火箭发射函数,没有用对的智能指针

上一篇博文中&#xff0c;我们使用单独的火箭发射函数&#xff0c;结果什么结果也没有得到&#xff0c;原因是launch_rocket()函数结束时&#xff0c;其内的局部对象counter生命周期也结束了 那么可以将counter改为指针吗&#xff1f;在堆中分配&#xff0c;这样当函数退出时&…...

Git 存储大文件

Git 存储大文件处理方法 寻找大文件的后缀LFS的安装让仓库支持LFS添加到LFS提交 寻找大文件的后缀 find . -type f -size 10M | grep -v ".git" | rev | cut -d. -f1 | rev | sort | uniq这个命令的工作原理如下&#xff1a; find .-type f -size 10M&#xff1a;查…...

使用 Mermaid 创建流程图,序列图,甘特图

使用 Mermaid 创建流程图和图表 Mermaid 是一个流行的 JavaScript 库&#xff0c;用于创建流程图、序列图、甘特图和其他各种图表。它的简洁语法使得创建图表变得非常简单&#xff0c;无需复杂的绘图工具或专业的编程技能。在本文中&#xff0c;我们将讲解如何使用 Mermaid 来创…...

政安晨:在Jupyter中【示例演绎】Matplotlib的官方指南(二){Image tutorial}·{Python语言}

咱们接着上一篇&#xff0c;这次咱们讲使用Matplotlib绘制图像的简短尝试。 我的这个系列的上一篇文章在这里&#xff1a; 政安晨&#xff1a;在Jupyter中【示例演绎】Matplotlib的官方指南&#xff08;一&#xff09;{Pyplot tutorial}https://blog.csdn.net/snowdenkeke/ar…...

gem5学习(20):替换策略——Replacement Policies

目录 一、Random 二、Least Recently Used (LRU) 三、Tree Pseudo Least Recently Used (TreePLRU) 四、Bimodal Insertion Policy (BIP) 五、LRU Insertion Policy (LIP) 六、Most Recently Used (MRU) 七、Least Frequently Used (LFU) 八、First-In, First-Out (FIF…...

嵌入式Qt Qt中的字符串类

一.Qt中的字符串类 QString vs string&#xff1a; QString在Qt库中几乎是无所不在的 所有的Qt图形用户组件都依赖于QString 实验1 &#xff1a;QString 初体验 #include <QDebug> void Sample_1() {QString s "add";s.append(" "); // &q…...

函数高级(C++)

师从黑马程序员 函数默认参数 在C中&#xff0c;函数的形参列表中的形参是可以有默认值的 语法&#xff1a;返回值类型 函数名 &#xff08;参数默认值 {}&#xff09; #include <iostream> using namespace std;//函数默认参数//如果我们自己传入数据&#xff0c;…...

jmeter-10调试取样器

文章目录 作用设置使用举例 作用 jmeter中添加调试取样器&#xff0c;可以用于检测测试过程的值如&#xff1a;变量、参数、系统设置等 设置 选择线程组右键 >>> 添加 >>> 取样器 >>> 调试取样器&#xff08;Debug Sampler&#xff09; jmeter …...

C#,二进制数的按位旋转(Bits Rotate)算法与源代码

1 二进制数的按位旋转 二进制数的按位旋转&#xff08;翻转&#xff09;是编程中常见的按位运算方法。 二进制数的按位旋转分为左转、右转。 左转意味着数据变大&#xff0c;右转意味着数据变小&#xff08;有损&#xff09;。 2 源程序 using System; using System.Text; us…...

解决ubuntu登录密码问题

解决ubuntu登录密码问题 不要随便删除密码&#xff0c;不要随便改密码&#xff0c;很容导致密码过期&#xff0c;或者密码无效。参考了很多人的做法&#xff0c;都没有得到解决。下面的过程&#xff0c;够详细了&#xff0c;我就是这么搞好的。 1、重启虚拟机&#xff0c;不停…...

Ubuntu忘记登录密码重置步骤

Ubuntu忘记登录密码重置步骤 1.开机界面长按shitf键&#xff0c;进入grub&#xff0c;并选择Advanced options for ubuntu&#xff0c;按下回车 2.选择一个较新版本的recovery mode&#xff0c;按下回车 3.会跑一些数据&#xff0c;等待跑完后会出现下面的界面&#xff0c;选择…...

MySQL数据库基础(五):SQL语言讲解

文章目录 SQL语言讲解 一、SQL概述 二、SQL语句分类 1、DDL 2、DML 3、DQL 4、DCL 三、SQL基本语法 1、SQL语句可以单行或多行书写&#xff0c;以分号结尾 2、可使用空格和缩进来增强语句的可读性 3、MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写…...

python-使用ffmpeg批量修改文件的后缀名

import os import subprocessdef convert_ogg_to_mp3(directory):for filename in os.listdir(directory):if filename.endswith(".ogg"):# 获取文件的完整路径file_path os.path.join(directory, filename)# 创建一个新的文件名&#xff0c;只是将扩展名从.ogg更改…...