2

背景:
从 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:请查看此处。但我真的更喜欢采用其他方式而不是 TransactionScope ...

4

4 回答 4

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();
    }
}

上下文构造函数定义为:

public Context(DbConnection connection) : base(connection,false) { }

样本本身对我有用,但它有多个问题:

  • 上下文的首次使用必须在关闭连接的情况下完成。这就是我在打开连接之前添加实体的原因。
  • 我宁愿在事务之外手动打开连接,但也许不需要。
  • 两个保存更改都成功运行并且Transaction.Current具有空的分布式事务 ID,因此它应该仍然是本地的。
  • 保存要复杂得多,您必须使用ObjectContext,因为DbContext没有所有必要的方法。
  • 它不必在每种情况下都有效。甚至 MSDN 也声称这一点:

当在单个事务中关闭并重新打开连接时,可能会向 DTC 提升事务。因为实体框架会自动打开和关闭连接,所以您应该考虑手动打开和关闭连接以避免事务提升。

DbContext API 的问题在于,即使您手动打开它,它也会关闭并重新打开连接,因此如果 API 始终正确识别它是否在事务上下文中运行并且不关闭连接,这是一个开放的问题。

于 2012-01-06T22:42:46.093 回答
0

@Ladislav Mrnka 你从一开始就是对的:我必须使用 MSDTC。

我在这里尝试了多种方法,包括我提供的示例代码。我已经用改变过的野兔在那里测试了很多次,但它不起作用。该错误深入探讨了 EF 和 DbContext 的工作原理,为了改变这一点,我最终发现自己使用了自己的 ORM 工具。事实并非如此。

我还与一位对 EF 也很了解的朋友 (MVP) 进行了交谈。我们在这里测试了其他一些东西,但它不会按照我想要的方式工作。我最终会得到多个独立的事务(我试图将它们与我的示例代码放在一起)并且使用这种方法我没有任何方法可以自动强制执行完全回滚,我将不得不创建很多通用的/custom 代码来手动回滚更改,这里又出现了另一个问题:如果这种回滚失败(这不是回滚,只是更新)怎么办?

因此,我们在这里找到的唯一方法是使用 MSDTC 并构建一些工具来帮助调试/测试是否启用了 DTC、客户端/服务器防火墙是否正常等等。

不管怎么说,还是要谢谢你。=)

于 2012-01-17T14:38:48.577 回答
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();

这对于跨存储库实现单个工作单元类是一个严重的拖累。有什么新方法可以解决这个问题吗?

于 2012-10-19T19:32:29.377 回答
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

于 2014-06-23T16:48:08.157 回答