整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

在 Linux 上配置 mongodb

ongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

1.下载Linux安装包

如下图,放到本地的某个角落,要记得位置哦~

2.连接你的服务器

ssh root@你的IP //回车输入密码 

3.安装包上传

另开ssh窗口(command+n),如果是windows就打开新的cmd窗口,因为我们要操作本地文件,之前的窗口我们已经登了服务器了。传的方法很多,我只演示其中一种。
cd "安装包所在文件夹" // 去你刚才安装包放的位置
scp "本地文件" root@你的IP // 回车输入密码开始传送

如下图,我是传送到服务器的根目录下。其实不建议,你可以放在一个固有目录,比如/usr/local


ls -l //以列表的形式展开,我们可以看到已经下载成功了。

4.解压安装包

tar -zxvf mongodb-linux-x86_64-3.4.6.tgz //解压 不同文件有不同的解压命令,自己百度即可

5.填坑的一步

刚才说大家可以把每次安装的东东放到固定的文件夹,所以我把解压好的文件移到了 /usr/local/mongodb目录了,如果你已经放到了你喜欢的位置,可以跳过此步骤。如果你不知道怎么移,可以看下。
cd /user/local //进入local
mkdir mongodb //创建mongodb文件夹
cd / //进入根目录
mv mongodb-linux-x86_64-3.4.6 /usr/local/mongodb /*将刚才的解压包移入 /usr/local/mongodb*/

6.配置mongodb运行环境

mkdir data //创建data文件夹,存放数据库db文件  
mkdir logs //创建logs文件夹,存放日志文件  
cd logs //进入logs  
touch mongo.log //创建log文件  
cd .. //返回上一级  
mkdir etc //创建配置文件夹  
cd etc // 进入etc  
vim mongo.conf //编辑同时创建mongo.conf 文件

以下是mongo.conf文件里的代码,大家看好不要写错,进入文件之后要按一下键盘i才能开始编写代码。

dbpath = /usr/local/mongodb/data //路径一定要输入绝对的  
logpath = /usr/local/mongodb/logs/mongo.log //路径一定要输入绝对的  
logappend = true  
journal = true  
quiet = true  
port = 27017 //端口

写好了按esc键退出,然后按shift+:会在最下面出现:然后输入wq,保存并退出的意思。


到了这一步就接近尾声了。

7.启动mongodb

cd mongodb/mongodb-linux-x86_64-3.4.6/bin //进入安装包的bin目录下 
mongod -f /usr/local/mongodb/etc/mongo.conf  //启动1方法  或 ./mongod -f /usr/local/mongodb/etc/mongo.conf  //启动2方法  

如果输入这个命令,出现如下图,没什么变化,只有光标闪烁。

8.本地测试是否成功

我用的可视化工具 Robo 3t,新建链接,address输入服务器地址,端口填你mongo.conf里配置的,基本都是27027。


点击保存,如下图操作,进行 Connect。


如果到了这一步基本就是成功啦。

9.小坑警示

我之前在mongo.conf 里 路径写的是相对的,就出现了下面的问题。

然后就是这样子的。

10.总结

方法有很多,我写的只是其中一种,自己也是第一次安装。其中也有点坑坑,遇到问题不要认输,总会解决的,写的不对不好的地方,希望大家不吝赐教~

本文地址:https://www.linuxprobe.com/linux-mongodb.html

光闹钟app开发者,请关注我,后续分享更精彩!

坚持原创,共同进步!

概述

MongoDB是一个可扩展,高性能,开源,面向文档的,基于键/值类型的数据库。非常适合保存大对象及json格式的数据。本文将介绍MongoDB的单机和集群副本安装,记录下来以备后用。希望对有需要的朋友有所帮助和参考。

下载地址

# 历史产品和版本选择
https://www.mongodb.com/docs/legacy/

# 3.6版本文档地址
https://www.mongodb.com/docs/v3.6/

安装方式

  • StandAlone:单机环境,一般开发测试的时候用。
  • Replication:主从结构,一个Primary,多个Secondary,可能会有Arbitry。
- Primary挂掉之后,会选举出一个Secondary作为Primary,与zookeeper类似。
- Arbitry(仲裁节点)上面不存数据,只是为了凑数。选举算法要求节点数必须是奇数个,如果Primary+Secondary不是奇数个,就要用Arbitry凑数。
- 写数据只能在Primary,读数据默认也在Primary,可以配置成从Secondary读,可以选最近的节点。
- 数据在Primary上写成功之后,会将操作记录在oplog中,Secondary将oplog拷贝过去,然后照着操作一遍,就有数据了。
- Primary和Secondary上面的数据保证最终一致性,可以为写操作配置write concern,有几个级别:在Primary上写完就认为写成功;写到oplog后认为写成功;写到一个/多个/某个/某几个Secondary之后认为写成功,等等。
  • Sharding:share nothing的结构,每台机器只存一部分数据。mongod服务器存数据,mongos服务器负责路由读写请求,元数据存在config数据库中。

安装&准备

centos 系统 yum源创建

cat >/etc/yum.repos.d/mongodb-org-3.6.repo<<eof
[mongodb-org-3.6]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/6Server/mongodb-org/3.6/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.6.asc
eof

根据具体情况,选择版本安装

# 安装最近版本
# sudo yum install -y mongodb-org

# 安装指定版本
sudo yum install --disablerepo=kubernetes -y mongodb-org-3.6.8 mongodb-org-server-3.6.8 mongodb-org-shell-3.6.8 mongodb-org-mongos-3.6.8 mongodb-org-tools-3.6.8

StandAlone安装配置

默认配置文件/etc/mongod.conf

- storage.dbPath 数据存储目录
- systemLog.path 系统日志目录
- net.bindIp 访问ip绑定,默认127.0.0.1。需要外部机器连接时,需调整成新人的访问源ip,或者测试所有ip放开0.0.0.0

启动MongoDB

sudo systemctl start mongod
# 如果启动报如下报错
# Failed to start mongod.service: Unit mongod.service not found.
# 启动以下命令
# sudo systemctl daemon-reload

查看MongoDB状态

sudo systemctl status mongod
# 如果想要重启系统MongoDB服务自动启动,可设置以下命令
# sudo systemctl enable mongod

停止MongoDB

sudo systemctl stop mongod

重启MongoDB

sudo systemctl restart mongod

连接MongoDB

mongo --host 127.0.0.1:27017

Replication安装

参考文档

官方文档:https://www.mongodb.com/docs/v3.6/administration/replica-set-deployment/

环境

3个节点分别安装MongoDB实例

修改/etc/mongod.conf以下配置项:

#replica set的名字
replication:
   replSetName: "rs0"
   
#client访问ip绑定,开发测试0.0.0.0所有ip   
net:
   bindIp: localhost,<ip address>  

分别在3个节点上启动MongoDB服务

sudo systemctl start mongod
# 如果启动报如下报错
# Failed to start mongod.service: Unit mongod.service not found.
# 启动以下命令
# sudo systemctl daemon-reload

Initiate the replica set(初始化副本集)

任意一个节点控制台登录MongoDB

[root@dev2 ~]# mongo
MongoDB shell version v3.6.8
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.8
Server has startup warnings:
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten]
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten]
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten]
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten]
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten]
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten] ** WARNING: soft rlimits too low. rlimits set to 4096 processes, 64000 files. Number of processes should be at least 32000 : 0.5 times number of files.
2022-05-11T09:36:02.035+0800 I CONTROL  [initandlisten]
>

初始化副本集。 注:只需在集群一台机器上执行初始化命令。

rs.initiate( {
   _id : "rs0",
   members: [
      { _id: 0, host: "dev2:27017" },
      { _id: 1, host: "dev3:27017" },
      { _id: 2, host: "dev4:27017" }
   ]
})

执行结果如下:

> rs.initiate( {
...    _id : "rs0",
...    members: [
...       { _id: 0, host: "dev2:27017" },
...       { _id: 1, host: "dev3:27017" },
...       { _id: 2, host: "dev4:27017" }
...    ]
... })
{
        "ok" : 1,
        "operationTime" : Timestamp(1653010782, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1653010782, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

查看配置结果:控制台执行以下指令

rs.conf()

附录

家好,我是Edison。

最近工作中需要用到MongoDB的事务操作,因此参考了一些资料封装了一个小的组件,提供基础的CRUD Repository基类 和 UnitOfWork工作单元模式。今天,就来简单介绍一下这个小组件。

关于MongoDB的事务

MongoDB在4.2版本开始全面支持了多文档事务,至今已过了四年了,虽然我们可能没有在项目中用MongoDB来替代传统关系型数据库如MySQL/SQL Server,但是不能否认MongoDB已经在事务能力上愈发成熟了。

在MongoDB中,所谓的事务主要指的是多个文档的事务,其使用方式和传统关系型数据库差不多。但我们需要注意的是:多文档事务只能应用在副本集 或 mongos 节点上。如果你只是一个单点的mongo实例,是无法进行多文档事务实践的。

画外音:如果你对MongoDB感兴趣,不妨看看我的这个系列博客:《MongoDB入门到实践学习之旅

那么,如何快速进行事务操作呢?

在Mongo Shell中进行事务
下面演示了如何通过Mongo Shell来进行一个多文档操作的事务提交:
  • var session = db.getMongo().startSession();session.startTransaction({readConcern: { level: 'majority' },writeConcern: { w: 'majority' }});
    var coll1 = session.getDatabase('students').getCollection('teams');coll1.update({name: 'yzw-football-team'}, {$set: {members: 20}});
    var coll2 = session.getDatabase('students').getCollection('records');coll1.update({name: 'Edison'}, {$set: {gender: 'Female'}});
    // 成功提交事务session.commitTransaction();
    // 失败事务回滚session.abortTransaction();
    在.NET应用中进行事务
    下面展示了在.NET应用中通过MongoDB Driver来进行事务的示例:
    • using (var clientSession = mongoClient.StartSession()){ try { var contacts = clientSession.Client.GetDatabase("testDB").GetCollection<Contact>("contacts"); contacts.ReplaceOne(contact => contact.Id == "1234455", contact); var books = clientSession.Client.GetDatabase("testDB").GetCollection<Book>("books"); books.DeleteOne(book => book.Id == "1234455");
      clientSession.CommitTransaction(); } catch (Exception ex) { // to do some logging clientSession.AbortTransaction(); }}
      在大部分的实际应用中,我们通常都习惯使用数据仓储(Repository)的模式来进行CRUD,同时也习惯用工作单元(UnitOfWork)模式来进行协调多个Repository进行事务提交
      那么,如何在自己的项目中实现这个呢?
      参考了一些资料后,自己实现了一个基础小组件,暂且叫它:EDT.MongoProxy吧,我们来看看它是如何实现的。
      单例的MongoClient
      基于MongoDB的最佳时间,对于MongoClient最好设置为单例注入,这样可以避免反复实例化MongoClient带来的开销,避免在极端情况下的性能低下。
      这里暂且设计一个MongoDbConnection类,用于包裹这个MongoClient,然后将其以单例模式注入IoC容器中。
      • public class MongoDbConnection : IMongoDbConnection{ public IMongoClient DatabaseClient { get; } public string DatabaseName { get; }
        public MongoDbConnection(MongoDatabaseConfigs configs, IConfiguration configuration) { DatabaseClient = new MongoClient(configs.GetMongoClientSettings(configuration)); DatabaseName = configs.DatabaseName; }}
        其中,这个MongoDatabaseConfigs类主要是获取appsettings中的配置,用以生成MongoClient的对应Settings。
        • /** Config Example"MongoDatabaseConfigs": { "Servers": "xxx01.edisontalk.net,xxx02.edisontalk.net,xxx03.edisontalk.net", "Port": 27017, "ReplicaSetName": "edt-replica", "DatabaseName": "EDT_Practices", "AuthDatabaseName": "admin",
          "ApplicationName": "Todo", "UserName": "service_testdev", "Password": "xxxxxxxxxxxxxxxxxxxxxxxx", "UseTLS": true, "AllowInsecureTLS": true, "SslCertificatePath": "/etc/pki/tls/certs/EDT_CA.cer", "UseEncryption": true}**/public class MongoDatabaseConfigs{ private const string DEFAULT_AUTH_DB = "admin"; // Default AuthDB: admin
          public string Servers { get; set; } public int Port { get; set; } = 27017; // Default Port: 27017 public string ReplicaSetName { get; set; } public string DatabaseName { get; set; } public string DefaultCollectionName { get; set; } = string.Empty; public string ApplicationName { get; set; } public string UserName { get; set; } public string Password { get; set; } public string AuthDatabaseName { get; set; } = DEFAULT_AUTH_DB; // Default AuthDB: admin public string CustomProperties { get; set; } = string.Empty; public bool UseTLS { get; set; } = false; public bool AllowInsecureTLS { get; set; } = true; public string SslCertificatePath { get; set; } = string.Empty; public bool StoreCertificateInKeyStore { get; set; } = false;

          public MongoClientSettings GetMongoClientSettings(IConfiguration configuration = ) { if (string.IsOrWhiteSpace(Servers)) throw new ArgumentException("Mongo Servers Configuration is Missing!");
          if (string.IsOrWhiteSpace(UserName) || string.IsOrWhiteSpace(Password)) throw new ArgumentException("Mongo Account Configuration is Missing!");
          // Base Configuration MongoClientSettings settings = new MongoClientSettings { ApplicationName = ApplicationName, ReplicaSetName = ReplicaSetName };

          // Credential settings.Credential = MongoCredential.CreateCredential(AuthDatabaseName, UserName, Password);
          // Servers var mongoServers = Servers.Split(",", StringSplitOptions.RemoveEmptyEntries).ToList(); if (mongoServers.Count == 1) // Standalone { settings.Server = new MongoServerAddress(mongoServers.First(), Port); settings.DirectConnection = true; }
          if (mongoServers.Count > 1) // Cluster { var mongoServerAddresses = new List<MongoServerAddress>(); foreach (var mongoServer in mongoServers) { var mongoServerAddress = new MongoServerAddress(mongoServer, Port); mongoServerAddresses.Add(mongoServerAddress); } settings.Servers = mongoServerAddresses; settings.DirectConnection = false; }
          // SSL if (UseTLS) { settings.UseTls = true; settings.AllowInsecureTls = AllowInsecureTLS; if (string.IsOrWhiteSpace(SslCertificatePath)) throw new ArgumentException("SslCertificatePath is Missing!");
          if (StoreCertificateInKeyStore) { var localTrustStore = new X509Store(StoreName.Root); var certificateCollection = new X509Certificate2Collection(); certificateCollection.Import(SslCertificatePath); try { localTrustStore.Open(OpenFlags.ReadWrite); localTrustStore.AddRange(certificateCollection); } catch (Exception ex) { throw; } finally { localTrustStore.Close(); } }
          var certs = new List<X509Certificate> { new X509Certificate2(SslCertificatePath) }; settings.SslSettings = new SslSettings(); settings.SslSettings.ClientCertificates = certs; settings.SslSettings.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls13; }
          return settings; }}
          核心部分:MongoDbContext
          这里我们主要仿照DbContext的设计,设计一个MongoDbContext,它从IoC容器中获取到单例的MongoClient,封装了事务的开启和提交,简化了应用代码的编写
          • public class MongoDbContext : IMongoDbContext{ private readonly IMongoDatabase _database; private readonly IMongoClient _mongoClient; private readonly IList<Func<IClientSessionHandle, Task>> _commands = new List<Func<IClientSessionHandle, Task>>();
            public MongoDbContext(IMongoDbConnection dbClient) { _mongoClient = dbClient.DatabaseClient; _database = _mongoClient.GetDatabase(dbClient.DatabaseName); }
            public void AddCommand(Func<IClientSessionHandle, Task> func) { _commands.Add(func); }
            public async Task AddCommandAsync(Func<IClientSessionHandle, Task> func) { _commands.Add(func); await Task.CompletedTask; }
            /// <summary> /// NOTES: Only works in Cluster mode /// </summary> public int Commit(IClientSessionHandle session) { try { session.StartTransaction();
            foreach (var command in _commands) { command(session); }
            session.CommitTransaction(); return _commands.Count; } catch (Exception ex) { session.AbortTransaction(); return 0; } finally { _commands.Clear(); } }
            /// <summary> /// NOTES: Only works in Cluster mode /// </summary> public async Task<int> CommitAsync(IClientSessionHandle session) { try { session.StartTransaction();
            foreach (var command in _commands) { await command(session); }
            await session.CommitTransactionAsync(); return _commands.Count; } catch (Exception ex) { await session.AbortTransactionAsync(); return 0; } finally { _commands.Clear(); } }
            public IClientSessionHandle StartSession() { var session = _mongoClient.StartSession(); return session; }
            public async Task<IClientSessionHandle> StartSessionAsync() { var session = await _mongoClient.StartSessionAsync(); return session; }
            public IMongoCollection<T> GetCollection<T>(string name) { return _database.GetCollection<T>(name); }
            public void Dispose() { GC.SuppressFinalize(this); }}

            数据仓储:MongoRepositoryBase

            在实际项目中,我们都希望有一个基础的RepositoryBase类,将CRUD的方法都封装了,我们实际中就只需要创建一个对应的Repository集成这个RepositoryBase就行了,无需再重复编写CRUD的方法。那么,也就有了这个MongoRepositoryBase类:

            • public class MongoRepositoryBase<TEntity> : IMongoRepositoryBase<TEntity> where TEntity : MongoEntityBase, new(){ protected readonly IMongoDbContext _dbContext; protected readonly IMongoCollection<TEntity> _dbSet; private readonly string _collectionName; private const string _keyField = "_id";
              public MongoRepositoryBase(IMongoDbContext mongoDbContext) { _dbContext = mongoDbContext; _collectionName = typeof(TEntity).GetAttributeValue((TableAttribute m) => m.Name) ?? typeof(TEntity).Name; if (string.IsOrWhiteSpace(_collectionName)) throw new ArgumentException("Mongo DatabaseName can't be ! Please set the attribute Table in your entity class.");
              _dbSet = mongoDbContext.GetCollection<TEntity>(_collectionName); }
              #region Create Part
              public async Task AddAsync(TEntity entity, IClientSessionHandle session = ) { if (session == ) await _dbSet.InsertOneAsync(entity); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.InsertOneAsync(entity)); }
              public async Task AddManyAsync(IEnumerable<TEntity> entityList, IClientSessionHandle session = ) { if (session == ) await _dbSet.InsertManyAsync(entityList); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.InsertManyAsync(entityList)); }
              #endregion
              # region Delete Part
              public async Task DeleteAsync(string id, IClientSessionHandle session = ) { if (session == ) await _dbSet.DeleteOneAsync(Builders<TEntity>.Filter.Eq(_keyField, new ObjectId(id))); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.DeleteOneAsync(Builders<TEntity>.Filter.Eq(_keyField, new ObjectId(id)))); }
              public async Task DeleteAsync(Expression<Func<TEntity, bool>> expression, IClientSessionHandle session = ) { if (session == ) await _dbSet.DeleteOneAsync(expression); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.DeleteOneAsync(expression)); }
              public async Task<DeleteResult> DeleteManyAsync(FilterDefinition<TEntity> filter, IClientSessionHandle session = ) { if (session == ) return await _dbSet.DeleteManyAsync(filter);
              await _dbContext.AddCommandAsync(async (session) => await _dbSet.DeleteManyAsync(filter)); return new DeleteResult.Acknowledged(10); }
              public async Task<DeleteResult> DeleteManyAsync(Expression<Func<TEntity, bool>> expression, IClientSessionHandle session = ) { if (session == ) return await _dbSet.DeleteManyAsync(expression);
              await _dbContext.AddCommandAsync(async (session) => await _dbSet.DeleteManyAsync(expression)); return new DeleteResult.Acknowledged(10); }
              #endregion
              #region Update Part
              public async Task UpdateAsync(TEntity entity, IClientSessionHandle session = ) { if (session == ) await _dbSet.ReplaceOneAsync(item => item.Id == entity.Id, entity); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.ReplaceOneAsync(item => item.Id == entity.Id, entity)); }
              public async Task UpdateAsync(Expression<Func<TEntity, bool>> expression, Expression<Action<TEntity>> entity, IClientSessionHandle session = ) { var fieldList = new List<UpdateDefinition<TEntity>>();
              if (entity.Body is MemberInitExpression param) { foreach (var item in param.Bindings) { var propertyName = item.Member.Name; object propertyValue = ;
              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, ).Compile().DynamicInvoke(); }
              if (propertyName != _keyField) { fieldList.Add(Builders<TEntity>.Update.Set(propertyName, propertyValue)); } } }
              if (session == ) await _dbSet.UpdateOneAsync(expression, Builders<TEntity>.Update.Combine(fieldList)); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.UpdateOneAsync(expression, Builders<TEntity>.Update.Combine(fieldList))); }
              public async Task UpdateAsync(FilterDefinition<TEntity> filter, UpdateDefinition<TEntity> update, IClientSessionHandle session = ) { if (session == ) await _dbSet.UpdateOneAsync(filter, update); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.UpdateOneAsync(filter, update)); }
              public async Task UpdateManyAsync(Expression<Func<TEntity, bool>> expression, UpdateDefinition<TEntity> update, IClientSessionHandle session = ) { if (session == ) await _dbSet.UpdateManyAsync(expression, update); else await _dbContext.AddCommandAsync(async (session) => await _dbSet.UpdateManyAsync(expression, update)); }
              public async Task<UpdateResult> UpdateManayAsync(Dictionary<string, string> dic, FilterDefinition<TEntity> filter, IClientSessionHandle session = ) { var t = new TEntity(); // Fields to be updated var list = new List<UpdateDefinition<TEntity>>(); foreach (var item in t.GetType().GetProperties()) { if (!dic.ContainsKey(item.Name)) continue; var value = dic[item.Name]; list.Add(Builders<TEntity>.Update.Set(item.Name, value)); } var updatefilter = Builders<TEntity>.Update.Combine(list);
              if (session == ) return await _dbSet.UpdateManyAsync(filter, updatefilter);
              await _dbContext.AddCommandAsync(async (session) => await _dbSet.UpdateManyAsync(filter, updatefilter)); return new UpdateResult.Acknowledged(10, 10, ); }
              #endregion
              #region Read Part
              public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> expression, bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); var queryData = await _dbSet.WithReadPreference(readPreference) .Find(expression) .FirstOrDefaultAsync(); return queryData; }
              public async Task<TEntity> GetAsync(string id, bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); var queryData = await _dbSet.WithReadPreference(readPreference).FindAsync(Builders<TEntity>.Filter.Eq(_keyField, new ObjectId(id))); return queryData.FirstOrDefault(); }
              public async Task<IEnumerable<TEntity>> GetAllAsync(bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); var queryAllData = await _dbSet.WithReadPreference(readPreference).FindAsync(Builders<TEntity>.Filter.Empty); return queryAllData.ToList(); }
              public async Task<long> CountAsync(Expression<Func<TEntity, bool>> expression, bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); return await _dbSet.WithReadPreference(readPreference).CountDocumentsAsync(expression); }
              public async Task<long> CountAsync(FilterDefinition<TEntity> filter, bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); return await _dbSet.WithReadPreference(readPreference).CountDocumentsAsync(filter); }
              public async Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> predicate, bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); return await Task.FromResult(_dbSet.WithReadPreference(readPreference).AsQueryable().Any(predicate)); }
              public async Task<List<TEntity>> FindListAsync(FilterDefinition<TEntity> filter, string[]? field = , SortDefinition<TEntity>? sort = , bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); if (field == || field.Length == 0) { if (sort == ) return await _dbSet.WithReadPreference(readPreference).Find(filter).ToListAsync();
              return await _dbSet.WithReadPreference(readPreference).Find(filter).Sort(sort).ToListAsync(); }
              var fieldList = new List<ProjectionDefinition<TEntity>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<TEntity>.Projection.Include(field[i].ToString())); } var projection = Builders<TEntity>.Projection.Combine(fieldList); fieldList?.Clear();
              if (sort == ) return await _dbSet.WithReadPreference(readPreference).Find(filter).Project<TEntity>(projection).ToListAsync();
              return await _dbSet.WithReadPreference(readPreference).Find(filter).Sort(sort).Project<TEntity>(projection).ToListAsync(); }
              public async Task<List<TEntity>> FindListByPageAsync(FilterDefinition<TEntity> filter, int pageIndex, int pageSize, string[]? field = , SortDefinition<TEntity>? sort = , bool readFromPrimary = true) { var readPreference = GetReadPreference(readFromPrimary); if (field == || field.Length == 0) { if (sort == ) return await _dbSet.WithReadPreference(readPreference).Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
              return await _dbSet.WithReadPreference(readPreference).Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); }
              var fieldList = new List<ProjectionDefinition<TEntity>>(); for (int i = 0; i < field.Length; i++) { fieldList.Add(Builders<TEntity>.Projection.Include(field[i].ToString())); } var projection = Builders<TEntity>.Projection.Combine(fieldList); fieldList?.Clear();
              if (sort == ) return await _dbSet.WithReadPreference(readPreference).Find(filter).Project<TEntity>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync();
              return await _dbSet.WithReadPreference(readPreference).Find(filter).Sort(sort).Project<TEntity>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToListAsync(); }
              #endregion
              #region Protected Methods
              protected ReadPreference GetReadPreference(bool readFromPrimary) { if (readFromPrimary) return ReadPreference.PrimaryPreferred; else return ReadPreference.SecondaryPreferred; }
              #endregion}

              工作单元:UnitOfWork

              在实际项目中,在对多个Repository操作之后,我们希望有一个统一的提交操作来实现事务的原子性。因此,我们可以有一个UnitOfWork来作为代理:

              • public class UnitOfWork : IUnitOfWork{ private readonly IMongoDbContext _context;
                public UnitOfWork(IMongoDbContext context) { _context = context; }
                public bool SaveChanges(IClientSessionHandle session) { return _context.Commit(session) > 0; }
                public async Task<bool> SaveChangesAsync(IClientSessionHandle session) { return await _context.CommitAsync(session) > 0; }
                public IClientSessionHandle BeginTransaction() { return _context.StartSession(); }
                public async Task<IClientSessionHandle> BeginTransactionAsync() { return await _context.StartSessionAsync(); }
                public void Dispose() { _context.Dispose(); }}

                封装注入:ServiceCollectionExtensions

                为了便于应用中快速注入,我们可以简单封装一个扩展方法,快速注入相关的核心组成部分:

                • public static class ServiceCollectionExtensions{ /// <summary> /// MongoDB Config Injection /// </summary> public static IServiceCollection AddMongoProxy(this IServiceCollection services, IConfiguration configuration) { if (!configuration.GetSection(nameof(MongoDatabaseConfigs)).Exists()) return services;
                  services.Configure<MongoDatabaseConfigs>(configuration.GetSection(nameof(MongoDatabaseConfigs))); services.AddSingleton(sp => sp.GetRequiredService<IOptions<MongoDatabaseConfigs>>().Value); services.AddSingleton<IMongoDbConnection, MongoDbConnection>(); services.AddScoped<IMongoDbContext, MongoDbContext>(); services.AddScoped<IUnitOfWork, UnitOfWork>();
                  return services; }}

                  如何使用:三步上篮

                  第一步:注入MongoProxy核心部分

                  在appsettings中配置MongoDB的连接信息:

                  • "MongoDatabaseConfigs": { "Servers": "xxx01.edisontalk.net,xxx02.edisontalk.net,xxx03.edisontalk.net", "Port": 27017, "ReplicaSetName": "edt-replica", "DatabaseName": "EDT_Practices", "UserName": "xxxxxxxxxxxxx", "Password": "xxxxxxxxxxxxx"}

                    然后通过扩展方法注入MongoProxy相关部分:

                    builder.Services.AddMongoProxy(builder.Configuration);

                    第二步:添加Entity 和 Repository

                    示例Entity:

                    • [Table("Orders")]public class OrderEntity : MongoEntityBase, IEntity{ public string OrderNumber { get; set; } public List<TransmissionEntity> Transmissions { get; set; }}

                      示例Repository:

                      • public interface ITodoItemRepository : IMongoRepositoryBase<TodoItem>{}
                        public class TodoItemRepository : MongoRepositoryBase<TodoItem>, ITodoItemRepository{ public TodoItemRepository(IMongoDbContext mongoDbContext) : base(mongoDbContext) { }}
                        services.AddScoped<ITodoItemRepository, TodoItemRepository>();services.AddScoped<IOrderRepository, OrderRepository>();

                        第三步:使用Repository 和 UnitOfWork

                        • # 非事务模式await _taskRepository.AddManyAsync(newTasks);# 事务模式(借助UnitOfWork工作单元)private readonly IUnitOfWork _unitOfWork;
                          public OrderService(IUnitOfWork unitOfWork, ......){ _unitOfWork = unitOfWork; ......}
                          public async Task Example(){ using var session = await _unitOfWork.BeginTransactionAsync()) await _taskRepository.AddManyAsync(newTasks, session); await _orderRepository.AddAsync(newOrder, session);
                          await _unitOfWork.SaveChangesAsync(session);}

                          小结

                          本文介绍了MongoDB事务的基本概念和如何通过.NET操作事务,重点介绍了EDT.MongoProxy这个小组件的设计,让我们可以在ASP.NET 6应用中通过数据仓储(Repository)和工作单元(UnitOfWork)的模式来快速方便地操作MongoDB的事务。

                          参考代码

                          本文代码并未提供所有的,如需查看,请至下面的代码仓库中查看,也可以点个赞给点鼓励。

                          GitHub:https://github.com/Coder-EdisonZhou/EDT.MongoProxy

                          参考资料

                          追逐时光者,《.NET Core MongoDB数据仓储和工作单元实操》:https://www.cnblogs.com/Can-daydayup/p/17157135.html

                          *本文主要设计参考自这篇文章,值得一读!

                          TheCodeBuzz,《MongoDB Repository Implementation in .NET Core》:

                          https://www.thecodebuzz.com/mongodb-repository-implementation-unit-testing-net-core-example

                          Bryan Avery, 《ASP.NET Core - MongoDB Repository Pattern & Unit Of Work》:

                          https://bryanavery.co.uk/asp-net-core-mongodb-repository-pattern-unit-of-work

                          年终总结:Edison的2022年终总结
                          数字化转型:我在传统企业做数字化转型
                          C#刷题:C#刷剑指Offer算法题系列文章目录
                          .NET面试:.NET开发面试知识体系
                          .NET大会:2020年中国.NET开发者大会PDF资料