一个 DbContext Instance 跨越多个 Repositories
One DbContext Instance spans multiple Repositories
这些是我在 Web Api 设置中的 AutoFac-DI 定义:
builder.RegisterType<MyContext>().As<MyContext>().InstancePerRequest();
builder.RegisterType<TestRepository>().InstancePerRequest();
builder.RegisterType<SchoolclassCodeRepository>().InstancePerRequest();
builder.RegisterType<TestService>().InstancePerRequest();
TestService 构造函数接受 TestRepository 和 SchoolclassCodeRepository。两个存储库都接受相同的 MyContext 实例。
我同意:Is it wise to use same DbContext with multiple repositories?
尽管如此,共享上下文还有其他很好的理由,其中之一(恕我直言)是上下文必须跟踪实体的状态,如果你得到一个实体,处置context,对实体进行一些修改,然后附加到一个新的上下文,这个新的上下文必须访问数据库,这样它才能弄清楚实体的状态。同样,如果您正在处理实体图(发票及其所有 InvoiceItems),那么新上下文将必须获取图中的所有实体以确定它们的状态。
但现在我用这种架构撞到了单行道!
如果我必须进行跨越多个存储库的事务怎么办?
使用 EF6,您可以在没有存储库的情况下这样做:
using(NorthwindEntities db = new NorthwindEntities())
{
DbContextTransaction transaction = db.Database.BeginTransaction();
try
{
//insert record 1
Customer obj1 = new Customer();
obj1.CustomerID = "ABCDE";
db.Customers.Add(obj1);
db.SaveChanges();
//insert record 2
Customer obj2 = new Customer();
obj2.CustomerID = "PQRST";
db.Customers.Add(obj2);
db.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
}
当我现在使用上面的示例并尝试对服务中的 2 个存储库执行相同的操作时,我遇到了一个严重的问题。
- 我的服务中没有可用的 DbContext。
- DbContext 是一个 DataProvider/Layer 问题,应该保留在存储库中。
如何在不更改我的存储库的情况下在多个存储库上创建事务?
我想要的样例:
在我的 TestService 中我想做的大致是:
public void Save()
{
// Open Transaction
// testRepo.Insert();
// schoolclassCodeRepo.Delete();
// Commit Transaction
}
更新
在我的 TestService 中,我将存储库中的所有实体映射到 DTO 对象,然后在我的网络 api 控制器中通过数据 + 链接(Rest)丰富这些对象。
更新 2
- 存储库模式使数据访问方法可重用,这很好。
- 但它使得共享相同 DbContext 的多个存储库上的事务不可能。
将所有 Repository 方法实现为 DbContext 的扩展方法不是更好吗,这样我可以直接在注入到我的 TestService 中的一个 DbContext 上调用我的服务中的 "Repo extension methods"?
更新 3 解决方案代码
public async Task<bool> DeleteSchoolyearAsync(int id)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
try
{
await testRepository.DeleteTestTypes(id);
await schoolyearRepository.DeleteAsync(id);
scope.Complete(); // Rollback is done due to using statement...
return true;
}
catch (System.Exception)
{
return false;
}
}
}
此代码运行良好!
您可以使用事务范围而不是 dbcontexttransaction 来执行此操作:
using (var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
// Your code
scope.Complete();
}
请注意,当您跨数据库使用它时,它将使用 MSDTC。
您没有更改存储库,但您的体系结构中肯定缺少 工作单元。这是您为多个存储库共享单个上下文的地方。
将 UoW 视为 DbContext,其中存储库是 DbSet。
UoW uow = new UoW( context );
uow.BeginTransaction();
uow.Repository1.... // query, insert, update, delete
uow.Repository2....
uow.Commit();
一个典型的实现只是公开多个存储库:
public class UoW {
public UoW( DbContext ctx ) {
this._ctx = ctx;
}
private Repository1 _repo1;
public Repository1 Repo1
{
get
{
if ( _repo1 == null )
_repo1 = new Repository1( this._ctx );
return _repo1;
}
...
如果您需要有关这方面的完整教程,请查看此处:
这些是我在 Web Api 设置中的 AutoFac-DI 定义:
builder.RegisterType<MyContext>().As<MyContext>().InstancePerRequest();
builder.RegisterType<TestRepository>().InstancePerRequest();
builder.RegisterType<SchoolclassCodeRepository>().InstancePerRequest();
builder.RegisterType<TestService>().InstancePerRequest();
TestService 构造函数接受 TestRepository 和 SchoolclassCodeRepository。两个存储库都接受相同的 MyContext 实例。
我同意:Is it wise to use same DbContext with multiple repositories?
尽管如此,共享上下文还有其他很好的理由,其中之一(恕我直言)是上下文必须跟踪实体的状态,如果你得到一个实体,处置context,对实体进行一些修改,然后附加到一个新的上下文,这个新的上下文必须访问数据库,这样它才能弄清楚实体的状态。同样,如果您正在处理实体图(发票及其所有 InvoiceItems),那么新上下文将必须获取图中的所有实体以确定它们的状态。
但现在我用这种架构撞到了单行道!
如果我必须进行跨越多个存储库的事务怎么办?
使用 EF6,您可以在没有存储库的情况下这样做:
using(NorthwindEntities db = new NorthwindEntities())
{
DbContextTransaction transaction = db.Database.BeginTransaction();
try
{
//insert record 1
Customer obj1 = new Customer();
obj1.CustomerID = "ABCDE";
db.Customers.Add(obj1);
db.SaveChanges();
//insert record 2
Customer obj2 = new Customer();
obj2.CustomerID = "PQRST";
db.Customers.Add(obj2);
db.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
}
当我现在使用上面的示例并尝试对服务中的 2 个存储库执行相同的操作时,我遇到了一个严重的问题。
- 我的服务中没有可用的 DbContext。
- DbContext 是一个 DataProvider/Layer 问题,应该保留在存储库中。
如何在不更改我的存储库的情况下在多个存储库上创建事务?
我想要的样例:
在我的 TestService 中我想做的大致是:
public void Save()
{
// Open Transaction
// testRepo.Insert();
// schoolclassCodeRepo.Delete();
// Commit Transaction
}
更新
在我的 TestService 中,我将存储库中的所有实体映射到 DTO 对象,然后在我的网络 api 控制器中通过数据 + 链接(Rest)丰富这些对象。
更新 2
- 存储库模式使数据访问方法可重用,这很好。
- 但它使得共享相同 DbContext 的多个存储库上的事务不可能。
将所有 Repository 方法实现为 DbContext 的扩展方法不是更好吗,这样我可以直接在注入到我的 TestService 中的一个 DbContext 上调用我的服务中的 "Repo extension methods"?
更新 3 解决方案代码
public async Task<bool> DeleteSchoolyearAsync(int id)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
try
{
await testRepository.DeleteTestTypes(id);
await schoolyearRepository.DeleteAsync(id);
scope.Complete(); // Rollback is done due to using statement...
return true;
}
catch (System.Exception)
{
return false;
}
}
}
此代码运行良好!
您可以使用事务范围而不是 dbcontexttransaction 来执行此操作:
using (var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
// Your code
scope.Complete();
}
请注意,当您跨数据库使用它时,它将使用 MSDTC。
您没有更改存储库,但您的体系结构中肯定缺少 工作单元。这是您为多个存储库共享单个上下文的地方。
将 UoW 视为 DbContext,其中存储库是 DbSet。
UoW uow = new UoW( context );
uow.BeginTransaction();
uow.Repository1.... // query, insert, update, delete
uow.Repository2....
uow.Commit();
一个典型的实现只是公开多个存储库:
public class UoW {
public UoW( DbContext ctx ) {
this._ctx = ctx;
}
private Repository1 _repo1;
public Repository1 Repo1
{
get
{
if ( _repo1 == null )
_repo1 = new Repository1( this._ctx );
return _repo1;
}
...
如果您需要有关这方面的完整教程,请查看此处: