无法在一个事务中使用两个 EF 存储库插入两个 one-to-one 实体(方法挂起)
Cannot insert two one-to-one entities using two EF repositories in one transaction (method hangs)
我有两个实体通过外键绑定为 one-to-one:CreateTenantDto 和 SaasTenantCreateDto。
我需要使用两个存储库(_abpTenantRepository
是来自 ABP Framework 的第 3 方存储库的实例)将这些实体插入到数据库中。我正在尝试为此使用 ABP UnitOfWork 实现。插入 SaasTenantCreateDto 实体后,我试图插入依赖于它的 CreateTenantDto 条目。如果我使用 OnCompleted
事件插入 CreateTenantDto 记录 - 该方法不会在 returning newTenantDto
和后者之前输入 OnCompleted
被 return 编辑为 null
(最后插入记录,但如果插入成功,我想 return 插入的实体)。如果我根本不使用 OnCompleted
- 方法挂起(看起来像数据库锁)。如果我使用两个嵌套的 UnitOfWork objects - 该方法也会挂起。如果我使用范围来处理两个存储库 -
using (var scope = ServiceProvider.CreateScope())
{
var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using (var tenantUow = unitOfWorkManager.Begin(new AbpUnitOfWorkOptions { IsTransactional = true }))
{ ... }
}
它也挂了...这绝对是锁,它与从新创建的 newAbpTenant 访问 id 有关:我可以在 SQL 开发人员会议
中看到
enq: TX - row lock contention
有罪 session 是我的另一个 HttpApi 主机 session。原因可能正如 Oracle 文档所说:“child table 上的 INSERT、UPDATE 和 DELETE 语句不会获取 parent table 上的任何锁,尽管 INSERT和 UPDATE 语句等待 parent table 的索引上的 row-lock 清除。” - SaveChangesAsync 导致新记录行锁定?
如何解决这个问题?
//OnModelCreatingBinding
builder.Entity<Tenant>()
.HasOne(x => x.AbpTenant)
.WithOne()
.HasPrincipalKey<Volo.Saas.Tenant>(x => x.Id)
.HasForeignKey<Tenant>(x => x.AbpId);
...
b.Property(x => x.AbpId).HasColumnName("C_ABP_TENANT").IsRequired();
//Mapping ignoration to avoid problems with 'bound' entities, since using separate repositories for Insert / Update
CreateMap<CreateTenantDto, Tenant>().ForMember(x => x.AbpTenant, opt => opt.Ignore());
CreateMap<UpdateTenantDto, Tenant>().ForMember(x => x.AbpTenant, opt => opt.Ignore());
public class CreateTenantDto
{
[Required]
public int Id { get; set; }
...
public Guid? AbpId { get; set; }
public SaasTenantCreateDto AbpTenant { get; set; }
}
public async Task<TenantDto> CreateAsync(CreateTenantDto input)
{
try
{
TenantDto newTenantDto = null;
using (var uow = _unitOfWorkManager.Begin(new AbpUnitOfWorkOptions { IsTransactional = true, IsolationLevel = System.Data.IsolationLevel.Serializable }))
{
var abpTenant = await _abpTenantManager.CreateAsync(input.AbpTenant.Name, input.AbpTenant.EditionId);
input.AbpTenant.MapExtraPropertiesTo(abpTenant);
var newAbpTenant = await _abpTenantRepository.InsertAsync(abpTenant);
await uow.SaveChangesAsync();
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
tenant.AbpId = newAbpTenant.Id;
var newTenant = await _tenantRepository.InsertAsync(tenant);
newTenantDto = ObjectMapper.Map<Tenant, TenantDto>(newTenant);
await uow.CompleteAsync();
}
return newTenantDto;
}
//Implementation by ABP Framework
public virtual async Task CompleteAsync(CancellationToken cancellationToken = default)
{
if (_isRolledback)
{
return;
}
PreventMultipleComplete();
try
{
_isCompleting = true;
await SaveChangesAsync(cancellationToken);
await CommitTransactionsAsync();
IsCompleted = true;
await OnCompletedAsync();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}
我终于使用以下方法解决了问题(但它没有使用两个似乎无法实现的存储库,因为我们需要直接操作 DbContext):
应用服务层:
//requiresNew: true - to be able to use TransactionScope
//isTransactional: false, otherwise it won't be possible to use TransactionScope, since we would have active ambient transaction
using var uow = _unitOfWorkManager.Begin(requiresNew: true);
var abpTenant = await _abpTenantManager.CreateAsync(input.AbpTenant.Name, input.AbpTenant.EditionId);
input.AbpTenant.MapExtraPropertiesTo(abpTenant);
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
var newTenant = await _tenantRepository.InsertAsync(tenant, abpTenant);
await uow.CompleteAsync();
return ObjectMapper.Map<Tenant, TenantDto>(newTenant);
Repository (EntityFrameworkCore) 层上的手工 InsertAsync 方法:
using (new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
{
var newAbpTenant = DbContext.AbpTenants.Add(abpTenant).Entity;
tenant.AbpId = newAbpTenant.Id;
var newTenant = DbContext.Tenants.Add(tenant).Entity;
if (autoSave)
{
await DbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
}
return newTenant;
}
我有两个实体通过外键绑定为 one-to-one:CreateTenantDto 和 SaasTenantCreateDto。
我需要使用两个存储库(_abpTenantRepository
是来自 ABP Framework 的第 3 方存储库的实例)将这些实体插入到数据库中。我正在尝试为此使用 ABP UnitOfWork 实现。插入 SaasTenantCreateDto 实体后,我试图插入依赖于它的 CreateTenantDto 条目。如果我使用 OnCompleted
事件插入 CreateTenantDto 记录 - 该方法不会在 returning newTenantDto
和后者之前输入 OnCompleted
被 return 编辑为 null
(最后插入记录,但如果插入成功,我想 return 插入的实体)。如果我根本不使用 OnCompleted
- 方法挂起(看起来像数据库锁)。如果我使用两个嵌套的 UnitOfWork objects - 该方法也会挂起。如果我使用范围来处理两个存储库 -
using (var scope = ServiceProvider.CreateScope())
{
var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
using (var tenantUow = unitOfWorkManager.Begin(new AbpUnitOfWorkOptions { IsTransactional = true }))
{ ... }
}
它也挂了...这绝对是锁,它与从新创建的 newAbpTenant 访问 id 有关:我可以在 SQL 开发人员会议
中看到enq: TX - row lock contention
有罪 session 是我的另一个 HttpApi 主机 session。原因可能正如 Oracle 文档所说:“child table 上的 INSERT、UPDATE 和 DELETE 语句不会获取 parent table 上的任何锁,尽管 INSERT和 UPDATE 语句等待 parent table 的索引上的 row-lock 清除。” - SaveChangesAsync 导致新记录行锁定?
如何解决这个问题?
//OnModelCreatingBinding
builder.Entity<Tenant>()
.HasOne(x => x.AbpTenant)
.WithOne()
.HasPrincipalKey<Volo.Saas.Tenant>(x => x.Id)
.HasForeignKey<Tenant>(x => x.AbpId);
...
b.Property(x => x.AbpId).HasColumnName("C_ABP_TENANT").IsRequired();
//Mapping ignoration to avoid problems with 'bound' entities, since using separate repositories for Insert / Update
CreateMap<CreateTenantDto, Tenant>().ForMember(x => x.AbpTenant, opt => opt.Ignore());
CreateMap<UpdateTenantDto, Tenant>().ForMember(x => x.AbpTenant, opt => opt.Ignore());
public class CreateTenantDto
{
[Required]
public int Id { get; set; }
...
public Guid? AbpId { get; set; }
public SaasTenantCreateDto AbpTenant { get; set; }
}
public async Task<TenantDto> CreateAsync(CreateTenantDto input)
{
try
{
TenantDto newTenantDto = null;
using (var uow = _unitOfWorkManager.Begin(new AbpUnitOfWorkOptions { IsTransactional = true, IsolationLevel = System.Data.IsolationLevel.Serializable }))
{
var abpTenant = await _abpTenantManager.CreateAsync(input.AbpTenant.Name, input.AbpTenant.EditionId);
input.AbpTenant.MapExtraPropertiesTo(abpTenant);
var newAbpTenant = await _abpTenantRepository.InsertAsync(abpTenant);
await uow.SaveChangesAsync();
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
tenant.AbpId = newAbpTenant.Id;
var newTenant = await _tenantRepository.InsertAsync(tenant);
newTenantDto = ObjectMapper.Map<Tenant, TenantDto>(newTenant);
await uow.CompleteAsync();
}
return newTenantDto;
}
//Implementation by ABP Framework
public virtual async Task CompleteAsync(CancellationToken cancellationToken = default)
{
if (_isRolledback)
{
return;
}
PreventMultipleComplete();
try
{
_isCompleting = true;
await SaveChangesAsync(cancellationToken);
await CommitTransactionsAsync();
IsCompleted = true;
await OnCompletedAsync();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}
我终于使用以下方法解决了问题(但它没有使用两个似乎无法实现的存储库,因为我们需要直接操作 DbContext):
应用服务层:
//requiresNew: true - to be able to use TransactionScope
//isTransactional: false, otherwise it won't be possible to use TransactionScope, since we would have active ambient transaction
using var uow = _unitOfWorkManager.Begin(requiresNew: true);
var abpTenant = await _abpTenantManager.CreateAsync(input.AbpTenant.Name, input.AbpTenant.EditionId);
input.AbpTenant.MapExtraPropertiesTo(abpTenant);
var tenant = ObjectMapper.Map<CreateTenantDto, Tenant>(input);
var newTenant = await _tenantRepository.InsertAsync(tenant, abpTenant);
await uow.CompleteAsync();
return ObjectMapper.Map<Tenant, TenantDto>(newTenant);
Repository (EntityFrameworkCore) 层上的手工 InsertAsync 方法:
using (new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
{
var newAbpTenant = DbContext.AbpTenants.Add(abpTenant).Entity;
tenant.AbpId = newAbpTenant.Id;
var newTenant = DbContext.Tenants.Add(tenant).Entity;
if (autoSave)
{
await DbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
}
return newTenant;
}