【问题标题】:How can I get two units of work under a single transaction如何在单个事务下获得两个工作单元
【发布时间】:2017-09-10 18:55:31
【问题描述】:

我有一个包含两个数据库的 MS Sql Server。在 C# 控制台应用程序中,我创建了两个不同的实体数据模型 (edmx) (EF6);一个用于数据库A,一个用于数据库B;将存储库和工作单元模式应用于两者。分开来说,它们工作得很好。没问题。我无法弄清楚的是如何将两者都置于一个“交易”之下。

在 EF 之前,我将创建 SqlConnection 和 SqlTransaction,修改该事务中任一数据库中的相关表,然后根据需要提交或回滚。但这似乎在 EF 中没有类似物。

UnitOfWorkForDatabaseA.Commit(); 
UnitOfWorkForDatabaseB.Commit(); //If this fails, both should rollback

但这对于两个独立的工作单元来说似乎是不可能的,每个工作单元都有自己的 ObjectContext。

我需要将它们都包含在 TransactionScope 中吗?或者也许设计一个 SuperUnitOfWork?

【问题讨论】:

    标签: c# entity-framework transactions objectcontext


    【解决方案1】:

    要么使用 TransactionScope 和分布式事务(警告需要 MSDTC),要么对两个 DbContext 实例使用单个 SqlConnection。您必须通过调用手动将数据库上下文从第一个数据库切换到第二个数据库

    USE OtherDatabaseName
    

    要使这项工作最简单的方法是使用 TransactionScope(它不会被提升为 DTC 事务,因为您使用的是单个 SqlConnection)。

    例如

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Common;
    using System.Data.Entity;
    using System.Data.Entity.ModelConfiguration;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Transactions;
    
    namespace ConsoleApp8
    {
    
        public class A
        {
            public int AID { get; set; }
            public string Name { get; set; }
        }
    
        public class B
        {
    
            public int BId { get; set; }
            public string Name { get; set; }
        }
        class DbA : DbContext
        {
            public DbA(): base()
            {
    
            }
            public DbA(DbConnection con) : base(con,false)
            {
    
            }
            public DbSet<A> A { get; set; }
    
    
        }
        class DbB : DbContext
        {
            public DbB() : base()
            {
    
            }
            public DbB(DbConnection con) : base(con, false)
            {
    
            }
            public DbSet<B> B { get; set; }
    
    
        }
    
    
    
        class Program
        {      
    
            static void Main(string[] args)
            {
    
                Database.SetInitializer(new CreateDatabaseIfNotExists<DbA>());
                Database.SetInitializer(new CreateDatabaseIfNotExists<DbB>());
                string DatabaseNameA, DatabaseNameB;
                using (var db = new DbA())
                {
                    db.Database.Initialize(false);
                    DatabaseNameA = db.Database.Connection.Database;
                }
    
                using (var db = new DbB())
                {
                    db.Database.Initialize(false);
                    DatabaseNameB = db.Database.Connection.Database;
                }
    
                var opts = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted };
    
                using (var dbA = new DbA())
                using (var tran = new TransactionScope(TransactionScopeOption.Required, opts))
                {
    
    
                    var a = dbA.A.Create();
                    a.Name = "someA";
                    dbA.A.Add(a);
                    dbA.SaveChanges();
    
                    dbA.Database.ExecuteSqlCommand($"use [{DatabaseNameB}]");
                    using (var dbB = new DbB(dbA.Database.Connection))
                    {
    
                        var b = dbB.B.Create();
                        b.Name = "someB";
                        dbB.B.Add(b);
                        dbB.SaveChanges();
    
                    }
    
                    tran.Dispose();
    
                }
    
    
    
                using (var dbA = new DbA())
                {
                    dbA.Database.Connection.Open(); //lock the connection open if not using a transaction
                    Console.WriteLine($"Count of A: {dbA.A.Count()}");
    
                    dbA.Database.ExecuteSqlCommand($"use [{DatabaseNameB}]");
                    using (var dbB = new DbB(dbA.Database.Connection))
                    {
                        Console.WriteLine($"Count of B: {dbB.B.Count()}");
    
                    }
    
    
    
                }
    
                Console.WriteLine("Hit any key to exit");
                Console.ReadKey();
    
    
    
    
    
    
            }
        }
    }
    

    输出

    Count of A: 0
    Count of B: 0
    Hit any key to exit
    

    您必须在此处使用 TransactionScope 的原因是 SaveChanges 否则将在内部使用 SqlTransaction。有趣的事实:SqlTransaction 被严重破坏了。它需要手动 SqlCommand 登记,并且不支持嵌套事务(它称为“并行事务”)。无论如何,它可以追溯到 .NET 1.0,并且无法真正更改。不过,当它出现在 .NET 2.0 中时,它可以与 System.Transactions 一起正常工作。

    【讨论】:

    • 我不明白你说的手动开关,大卫。我在 sql 调用中使用了“USE”,但我没有编写 EF 生成的任何 SQL。
    • 谢谢。我花了一段时间仔细研究它并确信这是最好的方法,但我现在就在那里。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-31
    • 2011-11-02
    • 2013-11-30
    • 2014-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多