【问题标题】:Two DbContext's on same server throws: This platform does not support distributed transactions同一服务器上的两个 DbContext 抛出:此平台不支持分布式事务
【发布时间】:2019-10-16 15:12:25
【问题描述】:

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

数据库

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

读取第二个上下文 throw 的 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.

【问题讨论】:

    标签: sql-server entity-framework-core dbcontext transactionscope distributed-transactions


    【解决方案1】:

    为什么 TransactionScope 开始分布式事务?

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

    我可以强制 Transaction Scope 先尝试本地事务吗?

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

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

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

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

    【讨论】:

    • 我不想相信,但 transactionScope 似乎总是使用 DTC,即使它通过两个 DbContext 访问具有相同连接字符串的同一个数据库(我添加了问题示例)。在一个 SqlConnection 中创建两个 DbContext 的其他变体可能会起作用,但我认为在我的应用程序服务实现中包含基础设施细节是不正确的,因此我使用更通用的 TransactionScope。
    • 所以我必须引用第二个数据库中的所有对象(或创建视图)才能不运行 DTC。当应用程序使用来自多个数据库的数据时,是否建议从单个数据库(在单个 SQL 服务器实例的情况下)访问它们?
    • “推荐的做法是从单个数据库访问它们吗?”如果您打算使用跨数据库查询或事务,是的。它简化了您的应用程序,因此它不必了解两个数据库,并使 SQL Server 能够使用轻量级跨数据库事务。
    猜你喜欢
    • 1970-01-01
    • 2020-03-26
    • 2017-02-07
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多