diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs index d8eda709..db0266da 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Json/Converter/DateTimeOffsetConverter.cs @@ -6,7 +6,7 @@ using System.Globalization; namespace Snap.Hutao.Core.Json.Converter; // 此转换器无法实现无损往返 必须在反序列化后调整 Offset -internal class DateTimeOffsetConverter : JsonConverter +internal sealed class DateTimeOffsetConverter : JsonConverter { private const string Format = "yyyy-MM-dd HH:mm:ss"; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs index 2041df8d..6b3d4e33 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity.Database; using System.Linq.Expressions; @@ -32,6 +33,24 @@ internal static class AppDbServiceExtension return service.Execute(dbset => dbset.AddRangeAndSave(entities)); } + public static int Delete(this IAppDbService service) + where TEntity : class + { + return service.Execute(dbset => dbset.ExecuteDelete()); + } + + public static int Delete(this IAppDbService service, TEntity entity) + where TEntity : class + { + return service.Execute(dbset => dbset.RemoveAndSave(entity)); + } + + public static int Delete(this IAppDbService service, Expression> predicate) + where TEntity : class + { + return service.Execute(dbset => dbset.Where(predicate).ExecuteDelete()); + } + public static TResult Query(this IAppDbService service, Func, TResult> func) where TEntity : class { @@ -50,27 +69,38 @@ internal static class AppDbServiceExtension return service.Query(query => query.SingleOrDefault(predicate)); } + public static void TransactionalExecute(this IAppDbService service, Action> action) + where TEntity : class + { + using (IServiceScope scope = service.ServiceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.GetAppDbContext(); + using (IDbContextTransaction transaction = appDbContext.Database.BeginTransaction()) + { + action(appDbContext.Set()); + transaction.Commit(); + } + } + } + + public static TResult TransactionalExecute(this IAppDbService service, Func, TResult> func) + where TEntity : class + { + using (IServiceScope scope = service.ServiceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.GetAppDbContext(); + using (IDbContextTransaction transaction = appDbContext.Database.BeginTransaction()) + { + TResult result = func(appDbContext.Set()); + transaction.Commit(); + return result; + } + } + } + public static int Update(this IAppDbService service, TEntity entity) where TEntity : class { return service.Execute(dbset => dbset.UpdateAndSave(entity)); } - - public static int Delete(this IAppDbService service) - where TEntity : class - { - return service.Execute(dbset => dbset.ExecuteDelete()); - } - - public static int Delete(this IAppDbService service, TEntity entity) - where TEntity : class - { - return service.Execute(dbset => dbset.RemoveAndSave(entity)); - } - - public static int Delete(this IAppDbService service, Expression> predicate) - where TEntity : class - { - return service.Execute(dbset => dbset.Where(predicate).ExecuteDelete()); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs index c43e6f78..08d5bb6d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/ProfilePictureService.cs @@ -20,6 +20,8 @@ internal sealed partial class ProfilePictureService : IProfilePictureService private readonly IServiceProvider serviceProvider; private readonly ITaskContext taskContext; + private readonly object syncRoot = new(); + public async ValueTask TryInitializeAsync(ViewModel.User.User user, CancellationToken token = default) { foreach (UserGameRole userGameRole in user.UserGameRoles) @@ -53,8 +55,13 @@ internal sealed partial class ProfilePictureService : IProfilePictureService { UidProfilePicture profilePicture = UidProfilePicture.From(userGameRole, playerInfo.ProfilePicture); - uidProfilePictureDbService.DeleteUidProfilePictureByUid(userGameRole.GameUid); - uidProfilePictureDbService.UpdateUidProfilePicture(profilePicture); + // We don't use DbTransaction here because it's rather complicated + // to handle transaction over multiple DbContext + lock (syncRoot) + { + uidProfilePictureDbService.DeleteUidProfilePictureByUid(userGameRole.GameUid); + uidProfilePictureDbService.UpdateUidProfilePicture(profilePicture); + } await TryUpdateUserGameRoleAsync(userGameRole, profilePicture, token).ConfigureAwait(false); }