MongoDB和System.Transactions

此文为译文,原文地址请点击

如何在你的MongoDB操作使用TransactionScope?

简介
MongoDB是一个开源的NoSQL面向文档的数据库,以JSON格式的动态数据模式,以上是维基百科的描述。这篇文章的目的并不是提供一个高水平的MongoDB 速成课程,但是如果你不知道MongoDB这款华丽的软件,产品网站本身是更好的起点https://www.mongodb.com/what-is-mongodb。
本文档是不仅仅是作为一个C#使用MongoDB的例子,而且它是在一定程度上规范你的CRUD操作。
文档将描述如何建立自己的资源管理类,生成一个可靠的机制操作现有的MongoDB的数据。对我的很多项目都缺乏这个功能,过了一段时间,我决定实现这个功能。我认为解决方案应该满足的3个核心无可争辩的标准:
1.该解决方法不应该是可扩展的。MongoDB来设计这样的可扩展性和部署的问题,超出了本篇文章的范围。在这种情况下,任何解决方案不应该采取违反这个原则MongoDB结构的方式。因此,我需要一个类似的解决方案,促进业务交易和作用作为一种外包装(SiC)而不是作为一个插件。
2.它必须是简单的,在一个范例,是可以接受的,测试,可靠和适合企业需要。没有人愿意学习新的定制的东西,不管是什么甚至更糟的是嵌入到其他的生产环境,特别是当它涉及到作为交易和故障恢复的话题。自定义事务框架解决方案完全排除。
3.我们需要推动我们的数据业务交易,而不是从头开始建立一个交易框架。
鉴于这些边界条件,明显的答案是利用微软已交付的强大和可扩展的功能 .NET 2.0 in System.Transactions namespace。
背景
什么是资源管理器
参与事务的任何资源必须由资源管理器来管理,资源管理器有责任维护其资源的价值变化,并根据事务的结果来允许值更改提交或回滚到原来的结果。微软SQL Server、Oracle数据库、消息队列、内存数据结构定制可以都作为资源管理器。
资源管理器处理持久或不稳定的数据。这种变化是指资源管理器是否支持故障恢复。如果资源管理器支持的耐久性,它存在数据持久存储,如果系统经历了一个失败,死机,停电等,它可以重新恢复交易后,并执行所有必要的行动以完成中断交易。另一方面,不稳定的资源管理者处理他们的数据,如在内存中的数据结构,这意味着在故障恢复中断的交易是不是一个选项了。资源管理器使用,将是本文介绍的重点处理不稳定资源。尽管如此,构建一个可以支持故障恢复的资源管理器,不会远离这篇文章的关键点。所有这些draftly描述动作序列由事务管理器协调。
什么是事务管理器
事务管理是组件,编排的所有资源管理和确保交易将达到终点irrespectably提交或回滚。值得庆幸的是我们没有在这里发展微软给了我们一个很好的选择OOB(记得#决策标准3)。
轻量级的事务管理器(LTM):是一种执行事务管理器使用轻量级事务协议(在本文的范围,谷歌)管理本地事务(单AppDomain)
内核事务管理器(KTM):管理事务性资源的操作系统,文件系统和注册表的当代版本的Windows资源管理器(Vista及以后)。完全与我们的语境无关。
分布式事务协调器(DTC):支持事务的执行。
当我们通过System.Transactions创建事务,总是通过LTM初步处理。虽然我们已经登记的资源管理器只处理挥发性数据或最大的一个持久的单阶段通知资源(单域,没有硬盘的相互作用)的全过程是LTM的监督下。如果我们有更持久的资源,或至少一个资源不支持跨域边界单阶段通知或资源跨越;

MongoDB基本CRUD操作
虽然,正如我已经在介绍,这不是通过C#来使用MongoDB,为以后的使用打下基础。
当我设计一个系统时,我总喜欢在一个系统中建立一个强大的对象层次结构。所以每一个实体的文件,我们要保存我们的MongoDB数据库,将从一个父类包含的信息不多但包括以下:

  public class MongoEntity : IEntity
    {
        [BsonId]
        public string Id { get; set; }
    }

并为每一个集合,我们要在我们的数据库中,我们将创建相应的辅助类,这将使我们能够执行所有的CRUD操作对数据库(基于知识库的模式):

 public interface IEntityRepository<T> where T : IEntity
    {
        IEnumerable<T> All();
        IQueryable<T> All(int page, int pageSize);
        T Get(string id);
        IQueryable<T> GetFunc(Expression<Func<T, bool>> expression);
        T Add(T entity);
        int Add(IEnumerable<T> entities);
        void Remove(T entity);
        bool Remove(string id);
        bool RemoveAll();
        int Remove(Expression<Func<T, bool>> expression);
        T Update(T updatedEntity);
    }

我们为所有未来的数据处理构建了一个抽象类,作为我们的基础类:

 public abstract class EntityRepositoryBase<T> : IEntityRepository<T> where T : IEntity
    {
        private MongoServer m_Server;
        private MongoDatabase m_Index;

        private MongoCollection<T> m_Entities;
        public MongoCollection<T> Entities
        {
            get
            {
                return m_Entities;
            }
        }

        private string m_Collection;

        public EntityRepositoryBase(string collection) : this(collection, null, null)
        {

        }

        public EntityRepositoryBase(string collection, string connectionString, string database)
        {
            m_Collection = collection;
            m_Server = new MongoClient(connectionString).GetServer();
            m_Index = m_Server.GetDatabase(database);
            m_Entities = m_Index.GetCollection<T>(m_Collection);
        }

        public IEnumerable<T> All()
        {
            return this.m_Entities.AsQueryable<T>().ToList();
        }

        public IQueryable<T> All(int page, int pageSize)
        {
            //Out of the scope of this article
        }

        public IEnumerable<D> AllAs<D>()
        {
            return m_Entities.AsQueryable<T>().OfType<D>().ToList();
        }

        public T Get(string id)
        {
            IMongoQuery query = Query.EQ("_id", id);
            return this.m_Entities.Find(query).FirstOrDefault();
        }

        public IQueryable<T> GetFunc(System.Linq.Expressions.Expression<Func<T, bool>> expression)
        {
            return this.m_Entities.AsQueryable<T>().Where(expression);
        }

        public IQueryable<T> GetFunc(System.Linq.Expressions.Expression<Func<T, bool>> expression, int page, int pageSize)
        {
            return this.m_Entities.AsQueryable<T>().Where(expression).Skip((page - 1) * pageSize).Take(pageSize);
        }

        public IQueryable<T> GetAs<D>(System.Linq.Expressions.Expression<Func<T, bool>> expression)
        {
            return m_Entities.FindAllAs<D>().Cast<T>().ToList().AsQueryable().Where(expression);
        }

        public virtual T Add(T entity)
        {
            try
            {
                IEntity oEntity = (entity as IEntity);

                oEntity.Id = String.IsNullOrEmpty(oEntity.Id) ?
                    ObjectId.GenerateNewId().ToString() :
                    oEntity.Id;

                m_Entities.Insert(entity);

                return entity;
            }
            catch (Exception mongoException)
            {
                if (mongoException.HResult == -2146233088)
                {
                    throw new MongoEntityUniqueIndexException("Unique Index violation", mongoException);
                }
                else
                {
                    throw mongoException;
                }
            }

            return default(T);
        }

        public virtual int Add(IEnumerable<T> entities)
        {
            int addCount = 0;

            entities.ToList().ForEach(entity =>
            {
                if (Add(entity) != null)
                {
                    addCount++;
                }
            });

            return addCount;
        }

        public virtual void AddBatch(IEnumerable<T> entities)
        {
            int addCount = 0;

            entities.ToList().ForEach(entity =>
            {
                IEntity oEntity = (entity as IEntity);

                oEntity.Id = String.IsNullOrEmpty(oEntity.Id) ?
                    ObjectId.GenerateNewId().ToString() :
                    oEntity.Id;

                oEntity.Created = timeStamp;
                oEntity.LastModified = timeStamp;
            });

            try
            {
                m_Entities.InsertBatch(entities);
            }
            catch (Exception addBatchException)
            {

            }
        }

        public virtual void Remove(T entity)
        {
            Remove(entity.Id);
        }

        public virtual bool Remove(string id)
        {
            try
            {
                IMongoQuery query = Query.EQ("_id", id);
                var result = m_Entities.Remove(query);
                return result.DocumentsAffected == 1;
            }
            catch (Exception mongoException)
            {
            }

            return false;
        }

        public virtual bool RemoveAll()
        {
            try
            {
                var result = m_Entities.RemoveAll();
                return result.DocumentsAffected == 1;
            }
            catch (Exception mongoException)
            {

            }

            return false;
        }

        public virtual int Remove(System.Linq.Expressions.Expression<Func<T, bool>> expression)
        {
            int removeCount = 0;
            List<T> entitiesToRemove = this.m_Entities.AsQueryable<T>().Where(expression).ToList();

            entitiesToRemove.ForEach(entity =>
            {
                if (Remove((entity as IEntity).Id))
                {
                    removeCount++;
                }
            });

            return removeCount;
        }

        public virtual T Update(T updatedEntity)
        {
            return Update(updatedEntity);
        }

    }

我们已经取得了迄今为止:我们已经创建了一个常见的方式,我们可以发起一个连接到我们的MongoDB数据库进行基本的关键数据操作(不完全摘录如下):

 public EntityRepositoryBase(string collection) : this(collection, null, null)
    
    public EntityRepositoryBase(string collection, string connectionString, string database)

    public virtual T Add(T entity)

    public virtual void Remove(T entity)

    public virtual T Update(T updatedEntity)

建立事务支持
正如我们之前提到的,资源管理器需要建立来实施事务。这有两个要求。能够保存的值及其变化的资源和继承ienlistmentnotification接口。新建类transactionentity <T>是建立一个单独的类来保持资源的价值,另外还将包含提交和回滚功能。

public class TransactionalEntity<T> where T : IEntity
    {
        private T m_Original;
        public T Original
        {
          get { return m_Original; }
        }

        private T m_Current;
        public T Current
        {
            get { return m_Current; }
        }

        private TransactionalRepositoryBase<T> m_Repository;

        public TransactionalRepositoryBase<T> Repository
        {
            get { return m_Repository; }
        }

        private bool m_CommitWithSuccess = false;

        public bool CommitWithSuccess
        {
            get { return m_CommitWithSuccess; }
        }

        private bool m_RollbackWithSuccess = false;

        public bool RollbackWithSuccess
        {
            get { return m_RollbackWithSuccess; }
        }

        private bool m_Prepared = false;

        public bool Prepared
        {
            get { return m_Prepared; }
        }

        private EntityRepositoryCommandsEnum m_Command;

        public EntityRepositoryCommandsEnum Command
        {
            get { return m_Command; }
        }

        public TransactionalEntity(T original, T current, TransactionalRepositoryBase<T> repository, EntityRepositoryCommandsEnum command)
        {
            m_Original = original;
            m_Current = current;
            m_Repository = repository;
            m_Command = command;
        }

        public bool Commit()
        {
            // if it reached that far it means that all are OK, need just to inform
            // resource manager that he can vote for commit

            m_CommitWithSuccess = true;
            return m_CommitWithSuccess;
        }

        public bool Rollback()
        {
            if (m_Command == EntityRepositoryCommandsEnum.Update)
            {
                m_Repository.NonTxUpdate(this.m_Original);
            }

            if (m_Command == EntityRepositoryCommandsEnum.Add)
            {
                m_Repository.NonTxRemove(this.m_Current);
            }

            if (m_Command == EntityRepositoryCommandsEnum.Remove)
            {
                m_Repository.NonTxAdd(this.m_Original);
            }

            m_RollbackWithSuccess = true;
            return m_RollbackWithSuccess;
        }

        public T Add()
        {
            T result = m_Repository.NonTxAdd(this.m_Current);
            m_Prepared = true;

            return result;
        }

        public void Remove()
        {
            m_Repository.NonTxRemove(this.Original);
            m_Prepared = true;
        }

        public T Update()
        {
            T result =  m_Repository.NonTxUpdate(this.m_Current);
            m_Prepared = true;

            return result;
        }
    }

transactionentity <T>将泛型类,以作为一个参数的认同。它将在各自的属性中存储当前和原始值,它将知道它必须使用的知识库类,以便在数据库中执行操作。此外,通过属性的命令,它将自我意识的命令执行:

 public T Original
 
    public T Current
    
    public TransactionalEntity(T original, T current, TransactionalRepositoryBase<T> repository, EntityRepositoryCommandsEnum command)
    
    public EntityRepositoryCommandsEnum Command

该命令是有限的,是枚举的一部分:

  public enum EntityRepositoryCommandsEnum
    {
        Add,
        Remove,
        Update
    }

另外还有5个重要的方法。提交和回滚,它将代表资源管理器(在所有的投票都是投下和事务管理器的结果来决定这项事务的结果)。根据对数据库的请求的命令,回滚方法决定什么是补偿对策。

 public bool Commit()
        {
            m_CommitWithSuccess = true;
            return m_CommitWithSuccess;
        }

        public bool Rollback()
        {
            if (m_Command == EntityRepositoryCommandsEnum.Update)
            {
                m_Repository.NonTxUpdate(this.m_Original);
            }

            if (m_Command == EntityRepositoryCommandsEnum.Add)
            {
                m_Repository.NonTxRemove(this.m_Current);
            }

            if (m_Command == EntityRepositoryCommandsEnum.Remove)
            {
                m_Repository.NonTxAdd(this.m_Original);
            }

            m_RollbackWithSuccess = true;
            return m_RollbackWithSuccess;
        }

这些剩下的3种方法,添加,更新和删除,这是在做实际工作发布单阶段提交对数据库的操作。他们在调用相关的知识库类的代表,我们将在后面的文章中讨论。

   public T Add()
        {
            T result = m_Repository.NonTxAdd(this.m_Current);
            m_Prepared = true;

            return result;
        }

        public void Remove()
        {
            m_Repository.NonTxRemove(this.Original);
            m_Prepared = true;
        }

        public T Update()
        {
            T result =  m_Repository.NonTxUpdate(this.m_Current);
            m_Prepared = true;

            return result;
        }

下一个类的是最终期待已久的资源管理器。

 public class MongoResourceManager<T> : IEnlistmentNotification where T : IEntity 
    {
        private TransactionalEntity<T> m_TxEntity;

        public MongoResourceManager(TransactionalEntity<T> txEntity)
        {
            m_TxEntity = txEntity;
        }

        public MongoResourceManager(T entity, TransactionalRepositoryBase<T> repository, EntityRepositoryCommandsEnum command)
        {
            T current = entity;
            T original = repository.Get(entity.Id);

            TransactionalEntity<T> txEntity = new TransactionalEntity<T>(original, current, repository, command);

            m_TxEntity = txEntity;
        }

        public void Commit(Enlistment enlistment)
        {
            bool success = this.m_TxEntity.Commit();

            if (success)
            {
                enlistment.Done();
            }
        }

        public void InDoubt(Enlistment enlistment)
        {
            Rollback(enlistment);
        }

        public void Prepare(PreparingEnlistment preparingEnlistment)
        {
            if (this.m_TxEntity.Prepared)
            {
                preparingEnlistment.Prepared();
            }
        }

        public void Rollback(Enlistment enlistment)
        {
            bool success = this.m_TxEntity.Rollback();

            if (success)
            {
                enlistment.Done();
            }
        }
    }

为了实现我们的目标,我们必须创建一个新的子类的存储库。这个新的子类transactionalrepositorybase <T>将能够确定是否请求的命令是在事务上下文发布(在一个事务处理是准确的),或者创建一个新的mongoresourcemanager <T>和争取他或执行正常操作对数据库:

public abstract class TransactionalRepositoryBase<T> : EntityRepositoryBase<T> where T : IEntity
    {
        internal delegate T AddEntityHandler(T entity);
        internal delegate void RemoveEntityHandler(T entity);
        internal delegate T UpdateEntityHandler(T entity);

        internal AddEntityHandler NonTxAdd;
        internal RemoveEntityHandler NonTxRemove;
        internal UpdateEntityHandler NonTxUpdate;

        public TransactionalRepositoryBase(string collection) : this(collection, null, null)
        {
        }

        public TransactionalRepositoryBase(string collection, string connectionString, string database) : base(collection, connectionString, database)
        {
            NonTxAdd = new AddEntityHandler(base.Add);
            NonTxRemove = new RemoveEntityHandler(base.Remove);
            NonTxUpdate = new UpdateEntityHandler(base.Update);
        }

        public override T Add(T entity)
        {
            if (Transaction.Current != null)
            {
                TransactionalEntity<T> txEntity = new TransactionalEntity<T>(default(T), entity, this, EntityRepositoryCommandsEnum.Add);
                MongoResourceManager<T> txRm = new MongoResourceManager<T>(txEntity);

                Transaction.Current.EnlistVolatile(txRm, EnlistmentOptions.None);
                return txEntity.Add();
            }
            else 
            {
                return NonTxAdd(entity);
            }
        }

        public override void Remove(T entity)
        {
            if (Transaction.Current != null)
            {
                TransactionalEntity<T> txEntity = new TransactionalEntity<T>(entity, default(T), this, EntityRepositoryCommandsEnum.Remove);
                MongoResourceManager<T> txRm = new MongoResourceManager<T>(txEntity);

                Transaction.Current.EnlistVolatile(txRm, EnlistmentOptions.None);
                txEntity.Remove();
            }
            else
            {
                NonTxRemove(entity);
            }
        }

        public override T Update(T entity)
        {
            if (Transaction.Current != null)
            {
                T original = this.Get(entity.Id);
                TransactionalEntity<T> txEntity = new TransactionalEntity<T>(original, entity, this, EntityRepositoryCommandsEnum.Remove);
                MongoResourceManager<T> txRm = new MongoResourceManager<T>(txEntity);

                Transaction.Current.EnlistVolatile(txRm, EnlistmentOptions.None);
                return txEntity.Update();
            }
            else
            {
                return NonTxUpdate(entity);
            }
        }
    }

实现这一点的关键是要重写的添加,更新和删除方法的原始存储库抽象类如下。通过检查交易。当前属性可以确定如果我们在TransactionScope上下文。我们创造了我们transactionalentity <T>及其mongoresourcemanager <T>最后我们得到它的挥发性和我们执行的方法(添加、删除或更新)从transactionalentity <T>类提供。否则,我们将执行一个委托

 public override T Add(T entity)
        {
            if (Transaction.Current != null)
            {
                TransactionalEntity<T> txEntity = new TransactionalEntity<T>(default(T), entity, this, EntityRepositoryCommandsEnum.Add);
                MongoResourceManager<T> txRm = new MongoResourceManager<T>(txEntity);

                Transaction.Current.EnlistVolatile(txRm, EnlistmentOptions.None);
                return txEntity.Add();
            }
            else 
            {
                return NonTxAdd(entity);
            }
        }

这些代表都指向添加、删除和更新基类的方法(entityrepositorybase)将执行命令对数据库在单阶段提交。他们将利用从transactionalrepositorybase <T>和transactionalentity <T>。

 internal delegate T AddEntityHandler(T entity);         
        internal delegate void RemoveEntityHandler(T entity);         
        internal delegate T UpdateEntityHandler(T entity);  
       
        internal AddEntityHandler NonTxAdd;         
        internal RemoveEntityHandler NonTxRemove;         
        internal UpdateEntityHandler NonTxUpdate;  
       
        public TransactionalRepositoryBase(string collection, string connectionString, string database) : base(collection, connectionString, database)         
        {             
              NonTxAdd = new AddEntityHandler(base.Add);             
              NonTxRemove = new RemoveEntityHandler(base.Remove);             
              NonTxUpdate = new UpdateEntityHandler(base.Update);         
        }

使用实例
让我们创建一个新的实体和一个新的知识库类:

 public class TestDocument : MongoEntity
    {
        public string DocumentId { get; set; }
    }

    public class TestDocumentsRepository : TransactionalRepositoryBase<TestDocument>
    {
        public TestDocumentsRepository()
            : base("test_documents", "mongodb://localhost:27017", "tx_tests")
        {

        }
    }

 

 

然后让我们创建一个可以模拟随机成功和中止交易的情况。每一次的仓库。添加(文件)的方法被称为新一mongoresourcemanager创建和加入我们的事务处理范围内创建的交易。如果代码管理达到直到范围。complete()那么事务成功提交否则自动回滚和移除集合中的所有数据。

  private void ExecuteInTx(object sender, EventArgs e)
        {
            TestDocumentsRepository repository = new TestDocumentsRepository();
            repository.RemoveAll();

            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                try
                {
                    for (int docIdx = 0; docIdx < 5; docIdx++)
                    {
                        int poison = random.Next(1000);

                        if (poison != 0 && poison % 200 == 0)
                        {
                            throw new Exception("Poison killed my TX");
                        }

                        TestDocument document = new TestDocument();
                        document.DocumentId = Guid.NewGuid().ToString();

                        repository.Add(document);

                        Thread.Sleep(100);
                    }

                    scope.Complete();
                }
                catch (Exception)
                {

                }               
            }
        }

兴趣点
监控你所要做的只是打开你的Windows组件服务MMC LTM或直接转矩控制的活动:

在这篇文章中所提出的代码绝不是你的产品需求的复制粘贴样本。这是一个引导和基线怎么合并交易在MongoDB数据库。.NET通用方法。

链接: http://pan.baidu.com/s/1slADTAP 密码: nus6

posted @ 2016-06-07 20:26  glly  阅读(985)  评论(2编辑  收藏  举报