背景
从SO的另一个问题我有一个Winforms解决方案(财务)与许多项目(解决方案的固定项目)。
现在,我的一位客户要求我“升级”解决方案,并添加来自另一个Winforms解决方案(HR)的项目/模块。
我真的不想将这些项目作为现有财务解决方案的固定项目。为此,我正在尝试使用MEF创建将加载GUI,业务逻辑和数据层的插件。
问题:
我有一个上下文(DbContext构建用于实现通用存储库模式)和一个外部上下文列表(使用MEF加载 - 这些上下文代表每个插件的上下文,也使用通用存储库模式)。
假设我有这个:
public class MainContext : DbContext
{
public List<IPluginContext> ExternalContexts { get; set; }
// other stuff here
}
和
public class PluginContext_A : DbContext, IPluginContext
{ /* Items from this context */ }
public class PluginContext_B : DbContext, IPluginContext
{ /* Items from this context */ }
并且在已经加载的MainContext类中,我有两个外部上下文(来自插件)。
考虑到这一点,假设我的事务会影响两者 MainContext和PluginContext_B。
如何在一个事务中的两个上下文中执行更新/插入/删除(统一工作)?
使用IUnityOfWork我可以为最后一项设置SaveChanges()但据我所知,我必须有一个上下文才能作为单个事务工作。
有一种使用MSDTC(TransactionScope)的方法,但这种方法很糟糕,我根本不会使用它(也因为我需要在客户端和服务器上启用MSDTC而且我一直都有崩溃和泄漏)。
更新
系统正在使用SQL 2008 R2。永远不要吼叫。
如果可以以不能扩展到MSDTC的方式使用TransactionScope,那很好,但我从未实现过。我一直使用TransactionScope进入MSDTC。根据关于SO的另一篇文章,有些情况下TS不会进入MSDTC:check here。但我真的更愿意采用其他方式而不是TransactionScope ......
答案 0 :(得分:3)
如果您使用多个上下文,每个上下文使用单独的连接,并且您希望在单个事务中将数据保存到这些上下文,则必须使用TransactionScope
和分布式事务(MSDTC)。
您的链接问题不是这种情况,因为在该场景中,第一个连接不会修改数据,因此可以在开始修改数据的连接之前将其关闭。在您的情况下,数据在多个连接上同时修改,这需要两阶段提交和MSDTC。
您可以尝试通过在多个上下文之间共享单个连接来解决它,但这可能非常棘手。我不确定以下样本的可靠性,但您可以尝试一下:
using (var connection = new SqlConnection(connnectionString))
{
var c1 = new Context(connection);
var c2 = new Context(connection);
c1.MyEntities.Add(new MyEntity() { Name = "A" });
c2.MyEntities.Add(new MyEntity() { Name = "B" });
connection.Open();
using (var scope = new TransactionScope())
{
// This is necessary because DbContext doesnt't contain necessary methods
ObjectContext obj1 = ((IObjectContextAdapter)c1).ObjectContext;
obj1.SaveChanges(SaveOptions.DetectChangesBeforeSave);
ObjectContext obj2 = ((IObjectContextAdapter)c2).ObjectContext;
obj2.SaveChanges(SaveOptions.DetectChangesBeforeSave);
scope.Complete();
// Only after successful commit of both save operations we can accept changes
// otherwise in rollback caused by second context the changes from the first
// context will be already accepted = lost
obj1.AcceptAllChanges();
obj2.AcceptAllChanges();
}
}
Context构造函数定义为:
public Context(DbConnection connection) : base(connection,false) { }
样本本身对我有用,但它有多个问题:
Transaction.Current
已分配空的分布式事务ID,因此它应该仍然是本地的。ObjectContext
因为DbContext
没有所有必要的方法。连接时可能会向DTC提升交易 在一次交易中关闭并重新开启。因为实体 框架会自动打开和关闭连接 考虑手动打开和关闭连接以避免 交易促销。
DbContext API的问题在于,即使您手动打开它也会关闭并重新打开连接,因此如果API始终正确识别它是否在事务上下文中运行且不关闭连接,则它是一个打开的问题。
答案 1 :(得分:0)
@Ladislav Mrnka 你从一开始就是对的:我必须使用MSDTC。
我在这里尝试了很多东西,包括我提供的示例代码。 我已经用变化的野兔测试了很多次但是它不会起作用。错误深入到EF和DbContext的工作方式,并为此改变我终于找到了自己的ORM工具。事实并非如此。
我也和一位对EF有很多了解的朋友(MVP)进行了交谈。 我们在这里测试了一些其他的东西,但它不会按照我想要的方式工作。我最终会遇到多个孤立的事务(我试图将它们与我的示例代码一起使用)并且使用这种方法我没有任何方法可以自动执行完全回滚,我将不得不创建大量的通用/自定义代码手动回滚更改,这里有另一个问题:如果这种回滚失败(这不是回滚,只是更新)会怎么样?
所以,我们在这里找到的唯一方法是使用MSDTC并构建一些工具来帮助调试/测试是否启用了DTC,如果客户端/服务器防火墙没问题就可以了。
非常感谢。 =)
答案 2 :(得分:0)
那么,这种情况在10月19日之前有变化吗?在整个intertubes中,人们建议使用以下代码,但它不起作用:
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Open();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Open();
using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = IsolationLevel.ReadUncommitted, Timeout = TimeSpan.MaxValue}))
{
_contextA.SaveChanges();
_contextB.SaveChanges();
// Commit the transaction
transaction.Complete();
}
// Close open connections
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Close();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Close();
这对于跨存储库实现单个工作单元类是一个严重的拖累。有什么新方法吗?
答案 3 :(得分:0)
为避免使用MSDTC(分布式事务):
这会强制您在事务中使用一个连接以及只使用一个事务。否则应抛出异常。
注意:至少需要EF6
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
}
}
来源: http://msdn.microsoft.com/en-us/data/dn456843.aspx#existing