Skip to content

Commit

Permalink
Prevent racy updates (#1756)
Browse files Browse the repository at this point in the history
  • Loading branch information
gunndabad authored Dec 19, 2024
1 parent ee51de7 commit b3659a7
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 29 deletions.
4 changes: 3 additions & 1 deletion TeachingRecordSystem/src/TeachingRecordSystem.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using TeachingRecordSystem.Core.Services.GetAnIdentityApi;
using TeachingRecordSystem.Core.Services.NameSynonyms;
using TeachingRecordSystem.Core.Services.TrnGenerationApi;
using TeachingRecordSystem.Core.Services.TrsDataSync;
using TeachingRecordSystem.Core.Services.Webhooks;
using TeachingRecordSystem.ServiceDefaults;
using TeachingRecordSystem.ServiceDefaults.Infrastructure.Logging;
Expand Down Expand Up @@ -226,7 +227,8 @@ public static void Main(string[] args)
.AddIdentityApi()
.AddNameSynonyms()
.AddDqtOutboxMessageSerializer()
.AddWebhookOptions();
.AddWebhookOptions()
.AddTrsSyncHelper();

services.AddAccessYourTeachingQualificationsOptions(configuration, env);
services.AddCertificateGeneration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.PowerPlatform.Dataverse.Client;
using TeachingRecordSystem.Core.Dqt;

Expand All @@ -12,6 +11,8 @@ public static class HostApplicationBuilderExtensions
{
public static IHostApplicationBuilder AddTrsSyncService(this IHostApplicationBuilder builder)
{
builder.AddTrsSyncHelper();

var runService = builder.Configuration.GetValue<bool>("TrsSyncService:RunService");

builder.Services.AddOptions<TrsDataSyncServiceOptions>()
Expand All @@ -24,12 +25,21 @@ public static IHostApplicationBuilder AddTrsSyncService(this IHostApplicationBui
builder.Services.AddSingleton<IHostedService, TrsDataSyncService>();
}

builder.Services.AddNamedServiceClient(
TrsDataSyncService.CrmClientName,
ServiceLifetime.Singleton,
sp => new ServiceClient(sp.GetRequiredService<IOptions<TrsDataSyncServiceOptions>>().Value.CrmConnectionString));
return builder;
}

builder.Services.AddCrmEntityChangesService(name: TrsDataSyncService.CrmClientName);
public static IHostApplicationBuilder AddTrsSyncHelper(this IHostApplicationBuilder builder)
{
if (!builder.Environment.IsUnitTests() && !builder.Environment.IsEndToEndTests())
{
builder.Services.AddNamedServiceClient(
TrsDataSyncService.CrmClientName,
ServiceLifetime.Singleton,
sp => new ServiceClient(sp.GetRequiredService<IConfiguration>()
.GetRequiredValue("TrsSyncService:CrmConnectionString")));

builder.Services.AddCrmEntityChangesService(name: TrsDataSyncService.CrmClientName);
}

builder.Services.TryAddSingleton<TrsDataSyncHelper>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
}

var currentAlert = await dbContext.Alerts
.FromSql($"select * from alerts where alert_id = {alertId} for update") // https://github.com/dotnet/efcore/issues/26042
.Include(a => a.AlertType)
.ThenInclude(at => at.AlertCategory)
.Include(a => a.Person)
.SingleOrDefaultAsync(a => a.AlertId == alertId);
.SingleOrDefaultAsync();

if (currentAlert is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
}

var currentMq = await dbContext.MandatoryQualifications
.FromSql($"select * from qualifications where qualification_id = {qualificationId} for update") // https://github.com/dotnet/efcore/issues/26042
.Include(mq => mq.Provider)
.Include(mq => mq.Person)
.SingleOrDefaultAsync(mq => mq.QualificationId == qualificationId);
.SingleOrDefaultAsync();

if (currentMq is null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using TeachingRecordSystem.Core.DataStore.Postgres;
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;
using TeachingRecordSystem.Core.Jobs.Scheduling;
using TeachingRecordSystem.Core.Services.TrsDataSync;

namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;
Expand All @@ -22,7 +23,7 @@ namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;
public class CheckPersonExistsFilter(
TrsDbContext dbContext,
ICrmQueryDispatcher crmQueryDispatcher,
IBackgroundJobScheduler backgroundJobScheduler) : IAsyncResourceFilter
TrsDataSyncHelper syncHelper) : IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
Expand All @@ -33,7 +34,7 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
return;
}

var person = await dbContext.Persons.SingleOrDefaultAsync(p => p.PersonId == personId && p.DqtState == 0);
var person = await GetPersonAsync();

if (person is not null)
{
Expand All @@ -58,9 +59,16 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res

if (dqtContact is not null)
{
context.HttpContext.SetCurrentPersonFeature(dqtContact);
var synced = await syncHelper.SyncPersonAsync(personId, /*ignoreInvalid: */ false, /*dryRun:*/ false, CancellationToken.None);
if (!synced)
{
throw new Exception($"Could not sync Person with contact ID: '{personId}'.");
}

await backgroundJobScheduler.EnqueueAsync<TrsDataSyncHelper>(helper => helper.SyncPersonAsync(personId, /*ignoreInvalid: */ false, /*dryRun:*/ false, CancellationToken.None));
person = await GetPersonAsync();
Debug.Assert(person is not null);

context.HttpContext.SetCurrentPersonFeature(person);
}
else
{
Expand All @@ -70,6 +78,10 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
}

await next();

Task<Person?> GetPersonAsync() => dbContext.Persons
.FromSql($"select * from persons where person_id = {personId} and dqt_state = 0 for update") // https://github.com/dotnet/efcore/issues/26042
.SingleOrDefaultAsync();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
return;
}

var currentSupportTask = await dbContext.SupportTasks.SingleOrDefaultAsync(t => t.SupportTaskReference == supportTaskReference);
var currentSupportTask = await dbContext.SupportTasks
.FromSql($"select * from support_tasks where support_task_reference = {supportTaskReference} for update") // https://github.com/dotnet/efcore/issues/26042
.SingleOrDefaultAsync();

if (currentSupportTask is null ||
(supportTaskType is SupportTaskType type && currentSupportTask.SupportTaskType != type) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Http.Features;
using TeachingRecordSystem.Core.DataStore.Postgres.Models;
using TeachingRecordSystem.Core.Dqt.Models;

namespace TeachingRecordSystem.SupportUi;

Expand All @@ -21,15 +20,6 @@ public static void SetCurrentPersonFeature(this HttpContext context, Person pers
person.MiddleName,
person.LastName));

public static void SetCurrentPersonFeature(this HttpContext context, ContactDetail contactDetail) =>
SetCurrentPersonFeature(
context,
new CurrentPersonFeature(
contactDetail.Contact.Id,
contactDetail.Contact.FirstName,
contactDetail.Contact.MiddleName ?? "",
contactDetail.Contact.LastName));

public static CurrentMandatoryQualificationFeature GetCurrentMandatoryQualificationFeature(this HttpContext context) =>
context.Features.GetRequiredFeature<CurrentMandatoryQualificationFeature>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ public async Task<IActionResult> OnPostAsync()
{
var now = clock.UtcNow;

var alert = await dbContext.Alerts
.SingleAsync(a => a.AlertId == AlertId);
var alert = HttpContext.GetCurrentAlertFeature().Alert;

var oldAlertEventModel = EventModels.Alert.FromModel(alert);
alert.EndDate = EndDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public void OnGet()

public async Task<IActionResult> OnPostAsync()
{
dbContext.SupportTasks.Attach(_supportTask!);
var data = (ConnectOneLoginUserData)_supportTask!.Data;
_supportTask.Data = data with
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using TeachingRecordSystem.Core.Infrastructure;
using TeachingRecordSystem.Core.Services.Files;
using TeachingRecordSystem.Core.Services.PersonMatching;
using TeachingRecordSystem.Core.Services.TrsDataSync;
using TeachingRecordSystem.ServiceDefaults;
using TeachingRecordSystem.ServiceDefaults.Infrastructure.Logging;
using TeachingRecordSystem.SupportUi;
Expand Down Expand Up @@ -152,7 +153,9 @@
}
}

builder.AddBlobStorage();
builder
.AddBlobStorage()
.AddTrsSyncHelper();

builder.Services
.AddTrsBaseServices()
Expand Down

0 comments on commit b3659a7

Please sign in to comment.