最近工作中需要用到MongoDB的事务操作,因此参考了一些资料封装了一个小的组件,提供基础的CRUD Repository基类 和 UnitOfWork工作单元模式。今天,就来简单介绍一下这个小组件。
MongoDB在4.2版本开始全面支持了多文档事务,至今已过了四年了,虽然我们可能没有在项目中用MongoDB来替代传统关系型数据库如MySQL/SQL Server,但是不能否认MongoDB已经在事务能力上愈发成熟了。
在MongoDB中,所谓的事务主要指的是多个文档的事务,其使用方式和传统关系型数据库差不多。但我们需要注意的是:多文档事务只能应用在副本集 或 mongos 节点上。如果你只是一个单点的mongo实例,是无法进行多文档事务实践的。
画外音:如果你对MongoDB感兴趣,不妨看看我的这个系列博客:《》
那么,如何快速进行事务操作呢?
下面演示了如何通过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应用中通过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吧,我们来看看它是如何实现的。
基于MongoDB的最佳时间,对于MongoClient最好设置为单例注入,因为在MongoDB.Driver中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