同一台服务器上的两个 DbContext 抛出:此平台不支持分布式事务

Two DbContext's on same server throws: This platform does not support distributed transactions

我无法弄清楚为什么 TransactionScope 正在启动分布式事务(未在 SQL 服务器上配置)。我想改用本地事务,当两个数据库位于同一个 SQL 服务器实例中时可以使用它。我的代码有什么问题,我该如何解决?我可以强制事务范围先尝试本地事务吗?

数据库

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=DESKTOP;Initial Catalog=test;Integrated Security=True",
    "Test2Connection": "Data Source=DESKTOP;Initial Catalog=test2;Integrated Security=True"
  }
}

startup.cs 注册 TestContext 和 Test2Context

services.AddDbContext<TestContext>(options =>
 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddDbContext<Test2Context>(options =>
 options.UseSqlServer(Configuration.GetConnectionString("Test2Connection")));

services.AddTransient<ICustomerRepository, CustomerRepository>();
services.AddTransient<IMaterialRepository, MaterialRepository>();

// This service inject TestContext and Test2Context
services.AddTransient<ICustomerService, CustomerService>();

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

CustomerRepository 使用 TestContext

public class CustomerRepository : ICustomerRepository
    {
        private readonly TestContext _context;
        public CustomerRepository(TestContext context)
        {
            _context = context;
        }
        public Customer Retrieve(int id)
        {
            return _context.Customers.Where(x => x.Id == id).FirstOrDefault();
        }
    }

MaterialRepository 使用 Test2Context

public class MaterialRepository : IMaterialRepository
    {
        private readonly Test2Context _context;
        public MaterialRepository(Test2Context context)
        {
            _context = context;
        }
        public Material Retrieve(int id)
        {
            return _context.Materials.Where(x => x.Id == id).FirstOrDefault();
        }
    }

客户服务

public class CustomerService : ICustomerService
    {
        private readonly ICustomerRepository _customerRepository;
        private readonly IMaterialRepository _materialRepository;
        public CustomerService(
            ICustomerRepository customerRepository, 
            IMaterialRepository materialRepository)
        {
            _customerRepository = customerRepository;
            _materialRepository = materialRepository;
        }
        public void DoSomething()
        {
            using (var transaction = new TransactionScope(TransactionScopeOption.Required
               //,new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }
                ))
            {
                var customer = _customerRepository.Retrieve(1);
                var material = _materialRepository.Retrieve(1); // The exception is thrown here !
                // _customerRepository.Save(customer);
                transaction.Complete();
            }
        }
    }

从第二个上下文中读取抛出的 This platform does not support distributed transactions 异常。

当对两个数据库上下文使用相同的连接字符串时,分布式事务也会触发

startup.cs

services.AddDbContext<TestContext>(options =>
                 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

services.AddDbContext<TestReadOnlyContext>(options =>  
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

CustomerReadOnlyRepository

public class CustomerReadOnlyRepository : ICustomerReadOnlyRepository
    {
        private readonly TestReadOnlyContext _context;
        public CustomerReadOnlyRepository(TestReadOnlyContext context)
        {
            _context = context;
        }
        public Customer Retrieve(int customerId)
        {
            Customer customer = _context.Customers.Where(x => x.Id == customerId).Include("Offices").FirstOrDefault();

            return customer;
        }
    }

客户服务

var customer = _customerRepository.Retrieve(1);
var customerReadOnly = _customerReadOnlyRepository.Retrieve(1); // Throw's the same error.

why TransactionScope is starting distributed transaction?

因为您有两个不同的 SQL 服务器会话。如果不将事务提升为分布式事务,客户端将无法在单独的会话上协调事务。

Can I force Transaction Scope to try local transaction first?

如果您对两个 DbContext 实例使用单个 Sql 服务器会话,则不需要将其提升为分布式事务。

您应该能够简单地为两个 DbContext 使用相同的查询字符串,并且 SqlClient 将自动缓存并为两者重用一个连接。当事务中登记的 SqlConnection 是 Close() 或 Disposed() 时,它实际上被搁置以等待事务的结果。任何使用相同连接字符串打开新 SqlConnection 的后续尝试都将 return 此相同连接。默认情况下,DbContext 将为每个操作打开和关闭 Sql连接,因此它应该受益于此行为。

如果相同的连接字符串不起作用,您可能必须打开 SqlConnection 并使用它来构造两个 DbContext 实例。

等等,这些表在不同的数据库中。是的,如果有充分的理由,您可以将它们留在那里。您需要做一些工作来启用单个 SqlConnection 来访问两个数据库中的对象。执行此操作的最佳方法是 CREATE SYNONYMs,这样您的应用程序就可以连接到单个数据库并使用本地名称访问远程对象。这也使您能够在单个实例上拥有应用程序的多个实例(方便dev/test)。