繁体   English   中英

如何使用 Entity Framework 为我的存储库实现事务?

[英]How can I implement a transaction for my repositories with Entity Framework?

我试图在我的应用程序中使用存储库设计模式有两个原因

  1. 我喜欢将我的应用程序与 Entity 分离,以防我决定在某些时候不使用 Entity Framework

  2. 我希望能够重用与 model 交互的逻辑

我成功设置并使用了存储库模式。 但是,我有一个复杂性要处理,那就是事务。

我希望能够使用事务,以便可以多次调用存储库,然后提交或回滚。

这是我的存储库界面

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Support.Repositories.Contracts
{
    public interface IRepository<TModel> where TModel : class
    {
        // Get records by it's primary key
        TModel Get(int id);

        // Get all records
        IEnumerable<TModel> GetAll();

        // Get all records matching a lambda expression
        IEnumerable<TModel> Find(Expression<Func<TModel, bool>> predicate);

        // Get the a single matching record or null
        TModel SingleOrDefault(Expression<Func<TModel, bool>> predicate);

        // Add single record
        void Add(TModel entity);

        // Add multiple records
        void AddRange(IEnumerable<TModel> entities);

        // Remove records
        void Remove(TModel entity);

        // remove multiple records
        void RemoveRange(IEnumerable<TModel> entities);
    }
}

然后我像这样为实体框架创建一个实现

using Support.Repositories.Contracts;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace Support.Repositories
{
    public class EntityRepository<TEntity> : IRepository<TEntity>
        where TEntity : class
    {
        protected readonly DbContext Context;
        protected readonly DbSet<TEntity> DbSet;

        public EntityRepository(DbContext context)
        {
            Context = context;
            DbSet = context.Set<TEntity>();
        }

        public TEntity Get(int id)
        {
            return DbSet.Find(id);
        }

        public IEnumerable<TEntity> GetAll()
        {
            return DbSet.ToList();
        }

        public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {
            return DbSet.Where(predicate);
        }

        public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return DbSet.SingleOrDefault(predicate);
        }

        public void Add(TEntity entity)
        {
            DbSet.Add(entity);
        }

        public void AddRange(IEnumerable<TEntity> entities)
        {
            DbSet.AddRange(entities);
        }

        public void Remove(TEntity entity)
        {
            DbSet.Remove(entity);
        }

        public void RemoveRange(IEnumerable<TEntity> entities)
        {
            DbSet.RemoveRange(entities);
        }

    }
}

现在,我创建了一个IUnitOfWork来像这样与存储库进行交互

using System;

namespace App.Repositories.Contracts
{
    public interface IUnitOfWork : IDisposable
    {
        IUserRepository Users { get; }
        IAddressRepository Addresses { get;  }
    }
}

然后我为实体框架实现了这个接口,如下所示:

using App.Contexts;
using App.Repositories.Contracts;
using App.Repositories.Entity;

namespace App.Repositories
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly AppContext _context;
        public IUserRepository  Users { get; private set; }
        public IAddressRepository Addresses { get; private set; }

        public UnitOfWork(AppContext context)
        {
            _context = context;

            Users = new UserRepository(_context);
            Addresses = new AddressRepository(_context);
        }

        public UnitOfWork() : this(new AppContext())
        {
        }

        public int Save()
        {
            return _context.SaveChanges();
        }

        public void Dispose()
        {
            _context.Dispose();
        }
    }
}

我可以像这样使用存储库

using(var repository = new UnitOfWork())
{
     repository.Users.Add(new User(... User One ...))
     repository.Save();

     repository.Addresses(new Address(... Address For User One ...))
     repository.Save();

     repository.Users.Add(new User(... User Two...))
     repository.Save();

     repository.Addresses(new Address(... Address For User Two...))
     repository.Save();
}

现在,我希望能够使用数据库事务,因此只有在一切正常时才提交,否则回滚。

我的第一个方法是在我UnitOfWork class 中添加一个名为BeginTransaction()的新方法。 但只会将我的代码耦合到实体框架。

现在,我正在考虑创建一个提供BeginTransaction()Commit()Rollback()方法的新接口,这将允许我为任何 ORM 编写实现。

IE

namespace Support.Contracts
{
    public IRepositoryDatabase
    {
        SomethingToReturn BeginTransaction();

        void Commit();
        void Rollback();
    }
}

问题是我如何将IRepositoryDatabase绑定回我的 UnitOfWork 以便我可以正确实施? BeginTransaction()需要返回什么?

我想我想出了办法。 (我希望我做对了)

这是我所做的,我希望这可以帮助那些想做同样事情的人。

我像这样创建了一个新界面

using System;

    namespace Support.Repositories.Contracts
    {
        public interface IDatabaseTransaction : IDisposable
        {
            void Commit();
    
            void Rollback();
        }
    }

然后我为实体框架实现了IDatabaseTransaction像这样

using Support.Repositories.Contracts;
using System.Data.Entity;

namespace Support.Entity.Repositories
{
    public class EntityDatabaseTransaction : IDatabaseTransaction
    {
        private DbContextTransaction _transaction;

        public EntityDatabaseTransaction(DbContext context)
        {
            _transaction = context.Database.BeginTransaction();
        }

        public void Commit()
        {
            _transaction.Commit();
        }

        public void Rollback()
        {
            _transaction.Rollback();
        }

        public void Dispose()
        {
            _transaction.Dispose();
        }
    }
}

然后,我在IUnitOfWork合同中添加了一个名为BeginTransaction()的新方法,如下所示

using System;

namespace App.Repositories.Contracts
{
    public interface IUnitOfWork : IDisposable
    {
        IDatabaseTransaction BeginTransaction();
        IUserRepository Users { get; }
        IAddressRepository Addresses { get;  }
    }
}

最后,以下是我对实体的UnitOfwork实现

using App.Contexts;
using App.Repositories.Contracts;
using App.Repositories.Entity;
using Support.Repositories;


namespace App.Repositories
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly AppContext _context;
        public IUserRepository  Users { get; private set; }
        public IAddressRepository Addresses { get; private set; }

        public UnitOfWork(AppContext context)
        {
            _context = context;

            Users = new UserRepository(_context);
            Addresses = new AddressRepository(_context);
        }

        public UnitOfWork() : this(new AppContext())
        {
        }

        public int Save()
        {
            return _context.SaveChanges();
        }

        public void Dispose()
        {
            _context.Dispose();
        }

        public IDatabaseTransaction BeginTransaction()
        {
            return new EntityDatabaseTransaction(_context);
        }
    }
}

这是我从控制器使用 UnitOfWork 实现的方式

using(var unitOfWork = new UnitOfWork())
using(var transaction = new unitOfWork.BeginTransaction())
{
     try
     {
         unitOfWork.Users.Add(new User(... User One ...))
         unitOfWork.Save();

         unitOfWork.Addresses(new Address(... Address For User One ...))
         unitOfWork.Save();

         unitOfWork.Users.Add(new User(... User Two...))
         unitOfWork.Save();

         unitOfWork.Addresses(new Address(... Address For User Two...))
         unitOfWork.Save();
         transaction.Commit();
     } 
     catch(Exception)
     {
          transaction.Rollback();
     }

}

EF Core 中,虽然 UnitOfWork 模式是在内部实现的,但您可以简单地使用IDbContextTransaction接口如下(假设您使用依赖注入):

public interface IUnitOfWork 
{
    int SaveChanges();
    Task<int> SaveChangesAsync();
    IDbContextTransaction BeginTransaction();
    Task<IDbContextTransaction> BeginTransactionAsync();
    
    IUserRepository Users { get; }
    IAddressRepository Addresses { get;  }
}

和实施:

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private bool _disposed;
    private readonly AppDbContext _context;

    public UnitOfWork(AppDbContext context, 
                 IUserRepository userRepositpry, IAddressRepository addressRepository)
    {
        _context = context;
        Users = userRepositpry;
        Addresses = addressRepository;
    }

    public IUserRepository Users { get; }
    public IAddressRepository Addresses { get; }

    public int SaveChanges()
    {
        return _context.SaveChanges();
    }

    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public IDbContextTransaction BeginTransaction()
    {
        return _context.Database.BeginTransaction();
    }

    public async Task<IDbContextTransaction> BeginTransactionAsync()
    {
        return await _context.Database.BeginTransactionAsync();
    }

    protected void Dispose(bool disposing)
    {
        if (!this._disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        this._disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

用法:

 public class FooService
{
    private readonly IUnitOfWork _unitOfWork;
    public FooService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void Bar()
    {
        using (var transaction = _unitOfWork.BeginTransaction())
        {
            try
            {
                _unitOfWork.Users.Add(new UserModel("dummy username"));
                _unitOfWork.SaveChanges();
                _unitOfWork.Addresses.Add(new AddressModel("dummy address"));
                _unitOfWork.SaveChanges();

                transaction.Commit();
            }
            catch (Exception)
            {
                transaction.Rollback();
            }
        }
    }
}

虽然 Rufo 爵士的评论是正确的,但您确实说过想要一个独立于 EF 的解决方案,尽管通常从 ORM 中抽象出来是一种矫枉过正,但如果您仍然打算自己处理事务,则可以使用TransactionScope (这显然是在context.Database具有BeginTransaction之前实现对事务的控制)。

详情请参阅以下文章: https : //msdn.microsoft.com/en-us/data/dn456843.aspx

相关位是您可以将所有调用包含在TransactionScope (这实际上也可以在其他 ORM 中开箱即用):

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 

namespace TransactionsExamples 
{ 
    class TransactionsExample 
    { 
        static void UsingTransactionScope() 
        { 
            using (var scope = new TransactionScope(TransactionScopeOption.Required)) 
            { 
                using (var conn = new SqlConnection("...")) 
                { 
                    conn.Open(); 

                    var sqlCommand = new SqlCommand(); 
                    sqlCommand.Connection = conn; 
                    sqlCommand.CommandText = 
                        @"UPDATE Blogs SET Rating = 5" + 
                            " WHERE Name LIKE '%Entity Framework%'"; 
                    sqlCommand.ExecuteNonQuery(); 

                    using (var context = 
                        new BloggingContext(conn, contextOwnsConnection: false)) 
                    { 
                        var query = context.Posts.Where(p => p.Blog.Rating > 5); 
                        foreach (var post in query) 
                        { 
                            post.Title += "[Cool Blog]"; 
                        } 
                        context.SaveChanges(); 
                    } 
                } 

                scope.Complete(); 
            } 
        } 
    } 
}

但确实需要注意以下注意事项:

TransactionScope方法仍然有一些限制:

  • 需要 .NET 4.5.1 或更高版本才能使用异步方法
  • 除非您确定只有一个连接,否则不能在云场景中使用(云场景不支持分布式事务)
  • 它不能与前几节的Database.UseTransaction()方法结合使用
  • 如果您发出任何 DDL(例如,由于数据库初始值设定项)并且没有通过 MSDTC 服务启用分布式事务,它将抛出异常

除了上面的所有解决方案之外,我以简单的方式完成了它,我知道大多数解决方案都很方便,但我正在分享它,也许它可以帮助某人。 首先在 IUnitOfWork 我将规则设置为:

DatabaseFacade DbTransaction();

在我刚刚使用的实现中:

public DatabaseFacade DbTransaction()
{
return _myContext.Database;
}

现在在我的代码中的任何地方我都可以轻松地使用它,我可以调用它,简单性使我能够跟踪事物并记住我编码的内容。 调用过程:

var transaction = _unitOfWork.DbTransaction().BeginTransaction();
Try{
//Any operations you like
transaction.Commit();
}catch(Exception E){
//handling Exception
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM