Add transaction support between services

This commit is contained in:
2025-10-27 18:09:09 +09:00
parent 6b3733001a
commit 9e526c54fa
11 changed files with 75 additions and 44 deletions

View File

@ -20,24 +20,23 @@ namespace Hcs.WebApp.BackgroundServices
{ {
await InitializeStateAsync(); await InitializeStateAsync();
using var scope = scopeFactory.CreateScope();
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
while (campaignManagementState.TryDequeueCampaign(out var campaign)) while (campaignManagementState.TryDequeueCampaign(out var campaign))
{ {
if (stoppingToken.IsCancellationRequested) return; if (stoppingToken.IsCancellationRequested) return;
using var scope = scopeFactory.CreateScope();
try try
{ {
var manager = managerFactory.CreateManager(campaign); var manager = managerFactory.CreateManager(scope, campaign);
await manager.StartAsync(stoppingToken); await manager.StartAsync(stoppingToken);
managers.Add(manager); managers.Add(manager);
} }
catch (Exception e) catch (Exception e)
{ {
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
await headquartersService.SetCampaignEndedWithFail(campaign.Id, e.Message); await headquartersService.SetCampaignEndedWithFail(campaign.Id, e.Message);
} }
} }

View File

@ -1,12 +1,24 @@
using Hcs.WebApp.Data.Hcs; using Hcs.WebApp.Data.Hcs;
using Hcs.WebApp.Services;
namespace Hcs.WebApp.BackgroundServices.CampaignManagers namespace Hcs.WebApp.BackgroundServices.CampaignManagers
{ {
public class ExportRequiredRegistryElementsManager_15_7_0_1(OperationExecutionState state, IServiceScopeFactory scopeFactory, Campaign campaign) : ManagerBase(state, scopeFactory, campaign) public class ExportRequiredRegistryElementsManager_15_7_0_1(IServiceScope scope, OperationExecutionState state, Campaign campaign) : ManagerBase(scope, state, campaign)
{ {
public override async Task StartAsync(CancellationToken cancellationToken) public override async Task StartAsync(CancellationToken cancellationToken)
{ {
// TODO var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
var registryService = scope.ServiceProvider.GetRequiredService<RegistryService>();
using var context = headquartersService.GetNewContext();
using var transaction = await context.Database.BeginTransactionAsync(cancellationToken);
try
{
// TODO
}
catch
{
throw;
}
} }
} }
} }

View File

@ -2,10 +2,10 @@
namespace Hcs.WebApp.BackgroundServices.CampaignManagers namespace Hcs.WebApp.BackgroundServices.CampaignManagers
{ {
public abstract class ManagerBase(OperationExecutionState state, IServiceScopeFactory scopeFactory, Campaign campaign) : IManager public abstract class ManagerBase(IServiceScope scope, OperationExecutionState state, Campaign campaign) : IManager
{ {
protected readonly IServiceScope scope = scope;
protected readonly OperationExecutionState state = state; protected readonly OperationExecutionState state = state;
protected readonly IServiceScopeFactory scopeFactory = scopeFactory;
protected readonly Campaign campaign = campaign; protected readonly Campaign campaign = campaign;
public IManager.ManagerState State { get; } = IManager.ManagerState.Created; public IManager.ManagerState State { get; } = IManager.ManagerState.Created;

View File

@ -2,17 +2,16 @@
namespace Hcs.WebApp.BackgroundServices.CampaignManagers namespace Hcs.WebApp.BackgroundServices.CampaignManagers
{ {
public class ManagerFactory(OperationExecutionState state, IServiceScopeFactory scopeFactory) public class ManagerFactory(OperationExecutionState state)
{ {
protected readonly OperationExecutionState state = state; protected readonly OperationExecutionState state = state;
protected readonly IServiceScopeFactory scopeFactory = scopeFactory;
public IManager CreateManager(Campaign campaign) public IManager CreateManager(IServiceScope scope, Campaign campaign)
{ {
switch (campaign.Type) switch (campaign.Type)
{ {
case Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1: case Campaign.CampaignType.ExportRequiredRegistryElements_15_7_0_1:
return new ExportRequiredRegistryElementsManager_15_7_0_1(state, scopeFactory, campaign); return new ExportRequiredRegistryElementsManager_15_7_0_1(scope, state, campaign);
} }
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -25,19 +25,18 @@ namespace Hcs.WebApp.BackgroundServices
InitializeClient(); InitializeClient();
var scope = scopeFactory.CreateScope();
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
while (state.TryDequeueOperation(out var operation)) while (state.TryDequeueOperation(out var operation))
{ {
if (stoppingToken.IsCancellationRequested) return; if (stoppingToken.IsCancellationRequested) return;
var scope = scopeFactory.CreateScope();
var headquartersService = scope.ServiceProvider.GetRequiredService<HeadquartersService>();
var messageGuid = string.Empty; var messageGuid = string.Empty;
try try
{ {
var executor = executorFactory.CreateExecutor(client, operation); var executor = executorFactory.CreateExecutor(scope, client, operation);
await headquartersService.SetOperationStarted(operation.Id); await headquartersService.SetOperationStarted(operation.Id);
messageGuid = await executor.ExecuteAsync(stoppingToken); messageGuid = await executor.ExecuteAsync(stoppingToken);
} }

View File

@ -3,10 +3,10 @@ using Hcs.WebApp.Data.Hcs;
namespace Hcs.WebApp.BackgroundServices.OperationExecutors namespace Hcs.WebApp.BackgroundServices.OperationExecutors
{ {
public abstract class ExecutorBase(IClient client, IServiceScopeFactory scopeFactory, Operation operation) : IExecutor public abstract class ExecutorBase(IClient client, IServiceScope scope, Operation operation) : IExecutor
{ {
protected readonly IClient client = client; protected readonly IClient client = client;
protected readonly IServiceScopeFactory scopeFactory = scopeFactory; protected readonly IServiceScope scope = scope;
protected readonly Operation operation = operation; protected readonly Operation operation = operation;
public abstract Task<string> ExecuteAsync(CancellationToken cancellationToken); public abstract Task<string> ExecuteAsync(CancellationToken cancellationToken);

View File

@ -4,16 +4,14 @@ using Hcs.WebApp.Data.Hcs;
namespace Hcs.WebApp.BackgroundServices.OperationExecutors namespace Hcs.WebApp.BackgroundServices.OperationExecutors
{ {
public class ExecutorFactory(IServiceScopeFactory scopeFactory) public class ExecutorFactory
{ {
protected readonly IServiceScopeFactory scopeFactory = scopeFactory; public IExecutor CreateExecutor(IServiceScope scope, IClient client, Operation operation)
public IExecutor CreateExecutor(IClient client, Operation operation)
{ {
switch (operation.Type) switch (operation.Type)
{ {
case Operation.OperationType.NsiCommon_ExportNsiItem_15_7_0_1: case Operation.OperationType.NsiCommon_ExportNsiItem_15_7_0_1:
return new ExportNsiItemExecutor_15_7_0_1(client, scopeFactory, operation); return new ExportNsiItemExecutor_15_7_0_1(client, scope, operation);
} }
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -5,13 +5,12 @@ using Hcs.WebApp.Services;
namespace Hcs.WebApp.BackgroundServices.OperationExecutors.NsiCommon namespace Hcs.WebApp.BackgroundServices.OperationExecutors.NsiCommon
{ {
public class ExportNsiItemExecutor_15_7_0_1(IClient client, IServiceScopeFactory scopeFactory, Operation operation) : ExecutorBase(client, scopeFactory, operation) public class ExportNsiItemExecutor_15_7_0_1(IClient client, IServiceScope scope, Operation operation) : ExecutorBase(client, scope, operation)
{ {
public override async Task<string> ExecuteAsync(CancellationToken cancellationToken) public override async Task<string> ExecuteAsync(CancellationToken cancellationToken)
{ {
using var scope = scopeFactory.CreateScope();
var registryService = scope.ServiceProvider.GetRequiredService<RegistryService>(); var registryService = scope.ServiceProvider.GetRequiredService<RegistryService>();
var registry = await registryService.GetRegistryByOperationId(operation.Id); var registry = await registryService.GetRegistryByOperationIdAsync(operation.Id);
return await client.NsiCommon.RequestExportNsiItemAsync(registry.Number, ListGroup.NSI, cancellationToken); return await client.NsiCommon.RequestExportNsiItemAsync(registry.Number, ListGroup.NSI, cancellationToken);
} }
} }

View File

@ -0,0 +1,15 @@
using Hcs.WebApp.Data.Hcs;
using Microsoft.EntityFrameworkCore;
namespace Hcs.WebApp.Services
{
public abstract class HcsServiceBase(IDbContextFactory<HcsDbContext> factory)
{
private readonly IDbContextFactory<HcsDbContext> factory = factory;
public HcsDbContext GetNewContext()
{
return factory.CreateDbContext();
}
}
}

View File

@ -3,19 +3,17 @@ using Microsoft.EntityFrameworkCore;
namespace Hcs.WebApp.Services namespace Hcs.WebApp.Services
{ {
public class HeadquartersService(IDbContextFactory<HcsDbContext> factory) public class HeadquartersService(IDbContextFactory<HcsDbContext> factory) : HcsServiceBase(factory)
{ {
private readonly IDbContextFactory<HcsDbContext> factory = factory;
public async Task<bool> HasActiveCampaignAsync(Campaign.CampaignType type) public async Task<bool> HasActiveCampaignAsync(Campaign.CampaignType type)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
return await context.Campaigns.AnyAsync(x => x.Type == type && !x.EndedAt.HasValue); return await context.Campaigns.AnyAsync(x => x.Type == type && !x.EndedAt.HasValue);
} }
public async Task<IEnumerable<Campaign>> GetInitiatedCampaignAsync() public async Task<IEnumerable<Campaign>> GetInitiatedCampaignAsync()
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
return await (from campaign in context.Campaigns return await (from campaign in context.Campaigns
where !campaign.EndedAt.HasValue where !campaign.EndedAt.HasValue
select campaign).ToListAsync(); select campaign).ToListAsync();
@ -23,7 +21,7 @@ namespace Hcs.WebApp.Services
public async Task<IEnumerable<Operation>> GetInitiatedOperationsAsync() public async Task<IEnumerable<Operation>> GetInitiatedOperationsAsync()
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
return await (from operation in context.Operations return await (from operation in context.Operations
where !operation.EndedAt.HasValue && string.IsNullOrEmpty(operation.MessageGuid) where !operation.EndedAt.HasValue && string.IsNullOrEmpty(operation.MessageGuid)
select operation).ToListAsync(); select operation).ToListAsync();
@ -31,7 +29,7 @@ namespace Hcs.WebApp.Services
public async Task<Campaign> InitiateCampaignAsync(Campaign.CampaignType type, string initiatorId) public async Task<Campaign> InitiateCampaignAsync(Campaign.CampaignType type, string initiatorId)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
var campaign = new Campaign() var campaign = new Campaign()
{ {
Type = type, Type = type,
@ -45,7 +43,7 @@ namespace Hcs.WebApp.Services
public async Task<Operation> InitiateOperationAsync(int campaignId, Operation.OperationType type) public async Task<Operation> InitiateOperationAsync(int campaignId, Operation.OperationType type)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
var operation = new Operation() var operation = new Operation()
{ {
CampaignId = campaignId, CampaignId = campaignId,
@ -59,7 +57,7 @@ namespace Hcs.WebApp.Services
public async Task SetCampaignEndedWithFail(int campaignId, string failureReason) public async Task SetCampaignEndedWithFail(int campaignId, string failureReason)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
var campaign = await context.Campaigns.FirstOrDefaultAsync(x => x.Id == campaignId); var campaign = await context.Campaigns.FirstOrDefaultAsync(x => x.Id == campaignId);
if (campaign != null) if (campaign != null)
{ {
@ -72,7 +70,7 @@ namespace Hcs.WebApp.Services
public async Task SetOperationStarted(int operationId) public async Task SetOperationStarted(int operationId)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
var operation = await context.Operations.FirstOrDefaultAsync(x => x.Id == operationId); var operation = await context.Operations.FirstOrDefaultAsync(x => x.Id == operationId);
if (operation != null) if (operation != null)
{ {
@ -84,7 +82,7 @@ namespace Hcs.WebApp.Services
public async Task SetOperationEndedWithFail(int operationId, string failureReason) public async Task SetOperationEndedWithFail(int operationId, string failureReason)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
var operation = await context.Operations.FirstOrDefaultAsync(x => x.Id == operationId); var operation = await context.Operations.FirstOrDefaultAsync(x => x.Id == operationId);
if (operation != null) if (operation != null)
{ {
@ -97,7 +95,7 @@ namespace Hcs.WebApp.Services
public async Task SetOperationMessageGuidAsync(int operationId, string messageGuid) public async Task SetOperationMessageGuidAsync(int operationId, string messageGuid)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
var operation = await context.Operations.FirstOrDefaultAsync(x => x.Id == operationId); var operation = await context.Operations.FirstOrDefaultAsync(x => x.Id == operationId);
if (operation != null) if (operation != null)
{ {

View File

@ -3,22 +3,34 @@ using Microsoft.EntityFrameworkCore;
namespace Hcs.WebApp.Services namespace Hcs.WebApp.Services
{ {
public class RegistryService(IDbContextFactory<HcsDbContext> factory) public class RegistryService(IDbContextFactory<HcsDbContext> factory) : HcsServiceBase(factory)
{ {
private readonly IDbContextFactory<HcsDbContext> factory = factory;
public async Task<IEnumerable<Registry>> GetAllRegistriesAsync(bool isCommon) public async Task<IEnumerable<Registry>> GetAllRegistriesAsync(bool isCommon)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
return await (from registry in context.Registries return await (from registry in context.Registries
where registry.IsCommon == isCommon where registry.IsCommon == isCommon
select registry).ToListAsync(); select registry).ToListAsync();
} }
public async Task<Registry> GetRegistryByOperationId(int operationId) public async Task<Registry> GetRegistryByOperationIdAsync(int operationId)
{ {
using var context = factory.CreateDbContext(); using var context = GetNewContext();
return await context.Registries.SingleAsync(x => x.LastSyncOperationId == operationId); return await context.Registries.SingleAsync(x => x.LastSyncOperationId == operationId);
} }
public async Task<IEnumerable<Registry>> GetRegistriesByOperationId(int operationId)
{
using var context = GetNewContext();
return await (from registry in context.Registries
where registry.LastSyncOperationId == operationId
select registry).ToListAsync();
}
public async Task SetOperationIdToAllRegistries(HcsDbContext context, int operationId)
{
await context.Registries.ForEachAsync(x => x.LastSyncOperationId = operationId);
await context.SaveChangesAsync();
}
} }
} }