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