Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/jquery-ui/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Repository pattern 如何以简洁的方式实现工作单元模式?_Repository Pattern_Dapper_Unit Of Work - Fatal编程技术网

Repository pattern 如何以简洁的方式实现工作单元模式?

Repository pattern 如何以简洁的方式实现工作单元模式?,repository-pattern,dapper,unit-of-work,Repository Pattern,Dapper,Unit Of Work,目前,我正在尝试使用工作单元+存储库模式的简洁ORM 我希望使用工作单元而不是简单的整洁的存储库,因为我的插入和更新需要一定程度的事务处理。我找不到任何有用的例子,因为大多数人似乎使用实体框架,并且在工作单元中存在泄漏问题 有人能给我指一下正确的方向吗?这很有帮助。我从同一个地方开始,根据我的需要做了一些改变 public sealed class DalSession : IDisposable { public DalSession() { _connecti

目前,我正在尝试使用工作单元+存储库模式的简洁ORM

我希望使用工作单元而不是简单的整洁的存储库,因为我的插入和更新需要一定程度的事务处理。我找不到任何有用的例子,因为大多数人似乎使用实体框架,并且在工作单元中存在泄漏问题

有人能给我指一下正确的方向吗?

这很有帮助。我从同一个地方开始,根据我的需要做了一些改变

public sealed class DalSession : IDisposable
{
    public DalSession()
    {
        _connection = new OleDbConnection(DalCommon.ConnectionString);
        _connection.Open();
        _unitOfWork = new UnitOfWork(_connection);
    }

    IDbConnection _connection = null;
    UnitOfWork _unitOfWork = null;

    public UnitOfWork UnitOfWork
    {
        get { return _unitOfWork; }
    }

    public void Dispose()
    {
        _unitOfWork.Dispose();
        _connection.Dispose();
    }
}

public sealed class UnitOfWork : IUnitOfWork
{
    internal UnitOfWork(IDbConnection connection)
    {
        _id = Guid.NewGuid();
        _connection = connection;
    }

    IDbConnection _connection = null;
    IDbTransaction _transaction = null;
    Guid _id = Guid.Empty;

    IDbConnection IUnitOfWork.Connection
    {
        get { return _connection; }
    }
    IDbTransaction IUnitOfWork.Transaction
    {
        get { return _transaction; }
    }
    Guid IUnitOfWork.Id
    {
        get { return _id; }
    }

    public void Begin()
    {
        _transaction = _connection.BeginTransaction();
    }

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

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

    public void Dispose()
    {
        if(_transaction != null)
            _transaction.Dispose();
        _transaction = null;
    }
}

interface IUnitOfWork : IDisposable
{
    Guid Id { get; }
    IDbConnection Connection { get; }
    IDbTransaction Transaction { get; }
    void Begin();
    void Commit();
    void Rollback();
}
现在,您的存储库应该以某种方式接受此UnitOfWork。我选择使用构造函数进行依赖注入

public sealed class MyRepository
{
    public MyRepository(IUnitOfWork unitOfWork) 
    {
        this.unitOfWork = unitOfWork;
    }

    IUnitOfWork unitOfWork = null;

    //You also need to handle other parameters like 'sql', 'param' ect. This is out of scope of this answer.
    public MyPoco Get()
    {
        return unitOfWork.Connection.Query(sql, param, unitOfWork.Transaction, .......);
    }

    public void Insert(MyPoco poco)
    {
        return unitOfWork.Connection.Execute(sql, param, unitOfWork.Transaction, .........);
    }
}
然后你这样称呼它:

关于交易:

using(DalSession dalSession = new DalSession())
{
    UnitOfWork unitOfWork = dalSession.UnitOfWork;
    unitOfWork.Begin();
    try
    {
        //Your database code here
        MyRepository myRepository = new MyRepository(unitOfWork);
        myRepository.Insert(myPoco);
        //You may create other repositories in similar way in same scope of UoW.

        unitOfWork.Commit();
    }
    catch
    {
        unitOfWork.Rollback();
        throw;
    }
}
using(DalSession dalSession = new DalSession())
{
    //Your database code here
    MyRepository myRepository = new MyRepository(dalSession.UnitOfWork);//UoW have no effect here as Begin() is not called.
    myRepository.Insert(myPoco);
}
无交易:

using(DalSession dalSession = new DalSession())
{
    UnitOfWork unitOfWork = dalSession.UnitOfWork;
    unitOfWork.Begin();
    try
    {
        //Your database code here
        MyRepository myRepository = new MyRepository(unitOfWork);
        myRepository.Insert(myPoco);
        //You may create other repositories in similar way in same scope of UoW.

        unitOfWork.Commit();
    }
    catch
    {
        unitOfWork.Rollback();
        throw;
    }
}
using(DalSession dalSession = new DalSession())
{
    //Your database code here
    MyRepository myRepository = new MyRepository(dalSession.UnitOfWork);//UoW have no effect here as Begin() is not called.
    myRepository.Insert(myPoco);
}
请注意,UnitOfWork比DBTransaction更重要

可以在上面的代码中找到有关存储库的更多详细信息

我已经发布了这个代码。但是这个问题对我来说更为相关,因为这是一个代码;所以我再次发布,而不是仅仅链接到原始答案

编辑2018-08-03:Amit的评论真的让我思考了一下,让我意识到存储库实际上不需要是上下文本身的属性。但是,存储库可能依赖于上下文。而不是继续对下面的代码示例进行增量更改。我将简单地引用一个包含这个概念的集合

站在这里其他人的肩膀上

考虑到这个答案,在大多数谷歌搜索中,与“整洁”和“工作单位”相关的搜索中,这个答案是最重要的。我想提供我的方法,我已经使用了很多次了

使用一个不确定(且过于简化)的示例:

请注意
IDbContext
IUnitOfWorkFactory
如何实现IDisposable。这样做是有目的的,以避免出现错误。相反,依赖于
Commit()
/
Rollback()
来进行清理和处置

共享实现之前的几点

  • IUnitOfWorkFactory
    负责实例化
    UnitOfWork
    并代理数据库连接
  • IDbContext
    是存储库主干
  • IUnitOfWork
    IDbTransaction
    的封装,确保在使用多个存储库时,它们共享一个数据库上下文
实施
IUnitOfWorkFactory
i工作单元的实现
实现
IPProductRepository
对于这个简单的只读操作,显然需要
Commit()。很显然,它提供了一个较小的性能优势。在简单的读取操作中,您“可以”省略
db.Commit()
,方法是让连接保持打开状态,并将清理任务交给垃圾收集器。所以不建议这样做

我通常将
DbContext
放在服务层的折叠中,它与其他服务协同工作,形成“ServiceContext”。然后,我在实际的MVC层中引用这个ServiceContext


另一点值得一提的是,如果可以,建议在整个堆栈中使用
async
。为了简单起见,这里省略了它。

我注意到在您的github repo中,您删除了UnitOfWorkFactory,并在访问连接时将其实例化

这种方法的问题是,我无法完全理解

想象一下下面的场景,如果我将DBContext注册为作用域,将存储库注册为瞬态

1. UserService CreateUserProfile
    a. UserRepositoryGetByEmail("some@email.com")
    b. UserRepository.Add(user)
    c. AddressRepository.Add(new address)
2. UserService Commit?
在本例中,上面的(1.)都是一个事务,然后我想在(2)中提交

对于一个大型业务层,多个服务使用同一范围的dbcontext实例,我可以看到事务重叠


现在,我可以将dbcontext设置为瞬态,但每次注入时,UnitOfWork都会有所不同,因此无法工作。

好的,OP提问已经过去五年了,但当我使用Dapper开发时,我不断遇到这个问题(或者说,这并不是非常Dapper-specific)。这是我的两分钱

首先让我们谈谈其他答案:

IDbContext
管理工作单元的方式与实体框架的方式非常相似。这是非常明智和容易理解的。但主要的缺点是,您最终将
IDbContext
传递给所有业务代码。这有点像上帝的东西。就像EF一样。我更喜欢注入单独的存储库,并明确我将要做的数据库工作,而不是让我的域模型中的所有东西总是只有一个
。然而,如果你不同意我的“上帝反对”的反对意见,皮姆的答案听起来很适合你

使
MyRepository
将工作单元作为构造函数参数。这意味着您不能再注入存储库。这可以通过注入存储库工厂来解决,但这肯定是它自己的麻烦

旁白:在一些答案中,“事务”和“工作单元”可以互换使用。实际上,他们的关系是1:1,但他们不是一回事。“事务”是db实现,“工作单元”更多的是一个更高层次的概念。如果我们有更多的持久性,而仅仅是一个数据库,那么就会有差异,UOW将包含不止一个事务。因此,为了避免混淆,“事务”在我们的UOW接口中可能不是一个好词

这是我的路

我将从用法开始

//业务代码。我将要编写一个方法,但是一个具有依赖关系的类更为现实
静态异步任务MyBusinessCode(IUnitOfWorkContext c
public class UnitOfWork : IUnitOfWork
{
    private IDbTransaction transaction;

    public UnitOfWork(IDbConnection connection)
    {
        transaction = connection.BeginTransaction();
    }

    public IDbTransaction Transaction =>
        transaction;

    public void Commit()
    {
        try
        {
            transaction.Commit();
            transaction.Connection?.Close();
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
        finally
        {
            transaction?.Dispose();
            transaction.Connection?.Dispose();
            transaction = null;
        }
    }

    public void Rollback()
    {
        try
        {
            transaction.Rollback();
            transaction.Connection?.Close();
        }
        catch
        {
            throw;
        }
        finally
        {
            transaction?.Dispose();
            transaction.Connection?.Dispose();
            transaction = null;
        }
    }
}
public class ProductRepository : IProductRepository
{
    protected readonly IDbConnection connection;
    protected readonly IDbTransaction transaction;

    public ProductRepository(UnitOfWork unitOfWork)
    {
      connection = unitOfWork.Transaction.Connection;
      transaction = unitOfWork.Transaction;
    }

    public Product Read(int id)
    {
        return connection.QuerySingleOrDefault<Product>("select * from dbo.Product where Id = @id", new { id }, transaction: Transaction);
    }
}
var unitOfWorkFactory = new UnitOfWorkFactory<SqlConnection>("your connection string");
var db = new DbContext(unitOfWorkFactory);

Product product = null;

try 
{
    product = db.Product.Read(1);
    db.Commit();
}
catch (SqlException ex)
{
    //log exception
    db.Rollback();
}
1. UserService CreateUserProfile
    a. UserRepositoryGetByEmail("some@email.com")
    b. UserRepository.Add(user)
    c. AddressRepository.Add(new address)
2. UserService Commit?
/// <summary>
/// Register a single instance using whatever DI system you like.
/// </summary>
class ConnectionFactory
{
    private string _connectionString;

    public ConnectionFactory(string connectionString)
    {
        _connectionString = connectionString;
    }

    public IDbConnection CreateConnection()
    {
        return new SqlConnection(_connectionString);
    }
}


/// <summary>
/// Generally, in a properly normalized database, your repos wouldn't map to a single table,
/// but be an aggregate of data from several tables.
/// </summary>
class ProductRepo
{
    private ConnectionFactory _connectionFactory;

    public ProductRepo(ConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public Product Get(int id)
    {
        // Allow connection pooling to worry about connection lifetime, that's its job.
        using (var con = _connectionFactory.CreateConnection())
        {
            return con.Get<Product>(id);
        }
    }

    // ...
}

class OrderRepo
{
    // As above.
    // ...
}

class ProductController : ControllerBase
{
    private ProductRepo _productRepo;
    private OrderRepo _orderRepo;

    public ProductController(ProductRepo productRepo, OrderRepo orderRepo)
    {
        _productRepo = productRepo;
        _orderRepo = orderRepo;
    }

    [HttpGet]
    public Task<IAsyncResult> Get(int id)
    {
        // This establishes your transaction.
        // Default isolation level is 'serializable' which is generally desirable and is configurable.
        // Enable async flow option in case subordinate async code results in a thread continuation switch.
        // If you don't need this transaction here, don't use it, or put it where it is needed.
        using (var trn = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            Product product = _productRepo.Get(id);

            // Use additional repositories and do something that actually requires an explicit transaction.
            // A single SQL statement does not require a transaction on SQL Server due to default autocommit mode.
            // ...

            return Ok(product);
        }
    }
}