Muf*_*lix 4 sql-server transactionscope distributed-transactions dbcontext entity-framework-core
我无法弄清楚为什么 TransactionScope 正在启动分布式事务(未在 SQL Server 上配置)。我想改用本地事务,当两个数据库位于同一个 SQL Server 实例中时可以使用本地事务。我的代码有什么问题,我该如何修复它?我可以强制 Transaction Scope 首先尝试本地事务吗?
数据库
应用程序设置.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=DESKTOP;Initial Catalog=test;Integrated Security=True",
"Test2Connection": "Data Source=DESKTOP;Initial Catalog=test2;Integrated Security=True"
}
}
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
使用 TestContext 的CustomerRepository
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();
}
}
Run Code Online (Sandbox Code Playgroud)
使用 Test2Context 的MaterialRepository
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();
}
}
Run Code Online (Sandbox Code Playgroud)
客户服务
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
从第二个上下文中读取抛出的This platform does not support distributed transactions异常。
当两个数据库上下文使用相同的连接字符串时,分布式事务也会被触发
启动.cs
services.AddDbContext<TestContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<TestReadOnlyContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
Run Code Online (Sandbox Code Playgroud)
客户只读存储库
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;
}
}
Run Code Online (Sandbox Code Playgroud)
客户服务
var customer = _customerRepository.Retrieve(1);
var customerReadOnly = _customerReadOnlyRepository.Retrieve(1); // Throw's the same error.
Run Code Online (Sandbox Code Playgroud)
为什么TransactionScope要启动分布式事务?
因为您有两个不同的 SQL Server 会话。客户端无法在不将事务提升为分布式事务的情况下协调单独会话上的事务。
我可以强制 Transaction Scope 首先尝试本地事务吗?
如果您对两个 DbContext 实例使用单个 Sql Server 会话,则不需要将其提升为分布式事务。
您应该能够简单地对两个 DbContext 使用相同的查询字符串,并且 SqlClient 将自动缓存并为两者重用单个连接。当事务中登记的 SqlConnection 为 Close() 或 Dispose() 时,它实际上被搁置以等待事务的结果。任何使用相同连接字符串打开新 SqlConnection 的后续尝试都将返回相同的连接。默认情况下,DbContext 将为每个操作打开和关闭 SqlConnection,因此它应该从此行为中受益。
如果相同的连接字符串不起作用,您可能必须打开 SqlConnection 并使用它来构造两个 DbContext 实例。
但是等等,这些表位于不同的数据库中。是的,如果有充分的理由,您可以将它们留在那里。您需要做一些工作才能使单个 SqlConnection 能够访问两个数据库中的对象。执行此操作的最佳方法是CREATE SYNONYM,以便您的应用程序可以连接到单个数据库并使用本地名称访问远程对象。这还使您能够在单个实例上拥有应用程序的多个实例(对于开发/测试来说很方便)。