如何在单个事务下获得两个工作单元
How can I get two units of work under a single transaction
我有一台包含两个数据库的 MS Sql 服务器。在 C# 控制台应用程序中,我创建了两个不同的实体数据模型 (edmx) (EF6);一个用于 DatabaseA,一个用于 DatabaseB;将存储库和工作单元模式应用于两者。分开来说,它们运作良好。没问题。我无法弄清楚的是如何将两者放在一个 'transaction'.
下
在 EF 之前,我会创建 SqlConnection 和 SqlTransaction,修改该事务中任一数据库中的相关表,然后根据需要提交或回滚。但这在 EF 中似乎没有类似物。
UnitOfWorkForDatabaseA.Commit();
UnitOfWorkForDatabaseB.Commit(); //If this fails, both should rollback
但是对于两个独立的工作单元,每个工作单元都有自己的 ObjectContext,这似乎是不可能的。
我是否需要将它们都包含在 TransactionScope 中?或者设计一个 SuperUnitOfWork?
要么使用 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 登记,并且不支持嵌套事务(称为 "parallel transactions")。它从 .NET 1.0 开始的任何方式,并且不能真正更改。不过,当它出现在 .NET 2.0 中时,它可以与 System.Transactions 一起正常工作。
我有一台包含两个数据库的 MS Sql 服务器。在 C# 控制台应用程序中,我创建了两个不同的实体数据模型 (edmx) (EF6);一个用于 DatabaseA,一个用于 DatabaseB;将存储库和工作单元模式应用于两者。分开来说,它们运作良好。没问题。我无法弄清楚的是如何将两者放在一个 'transaction'.
下在 EF 之前,我会创建 SqlConnection 和 SqlTransaction,修改该事务中任一数据库中的相关表,然后根据需要提交或回滚。但这在 EF 中似乎没有类似物。
UnitOfWorkForDatabaseA.Commit();
UnitOfWorkForDatabaseB.Commit(); //If this fails, both should rollback
但是对于两个独立的工作单元,每个工作单元都有自己的 ObjectContext,这似乎是不可能的。
我是否需要将它们都包含在 TransactionScope 中?或者设计一个 SuperUnitOfWork?
要么使用 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 登记,并且不支持嵌套事务(称为 "parallel transactions")。它从 .NET 1.0 开始的任何方式,并且不能真正更改。不过,当它出现在 .NET 2.0 中时,它可以与 System.Transactions 一起正常工作。