diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs index 348ffdd4..8684db2d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs @@ -13,7 +13,7 @@ namespace Snap.Hutao.Model.Entity; /// [HighQuality] [Table("cultivate_entries")] -internal sealed class CultivateEntry : IDbMappingForeignKeyFrom +internal sealed class CultivateEntry : IDbMappingForeignKeyFrom, IAppDbEntity { /// /// 内部Id diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs index d9cef50d..e0cc33cb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Core.Database; +using Snap.Hutao.Model.Entity.Abstraction; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -13,7 +14,7 @@ namespace Snap.Hutao.Model.Entity; /// [HighQuality] [Table("cultivate_projects")] -internal sealed class CultivateProject : ISelectable, IMappingFrom +internal sealed class CultivateProject : ISelectable, IMappingFrom, IAppDbEntity { /// /// 内部Id diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs index 404b8580..8b97ee63 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceAppDbEntityExtension.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using Microsoft.EntityFrameworkCore; -using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity.Abstraction; namespace Snap.Hutao.Service.Abstraction; @@ -12,13 +11,25 @@ internal static class AppDbServiceAppDbEntityExtension public static int DeleteByInnerId(this IAppDbService service, TEntity entity) where TEntity : class, IAppDbEntity { - return service.Execute(dbset => dbset.ExecuteDeleteWhere(e => e.InnerId == entity.InnerId)); + return service.DeleteByInnerId(entity.InnerId); + } + + public static int DeleteByInnerId(this IAppDbService service, Guid innerId) + where TEntity : class, IAppDbEntity + { + return service.Delete(e => e.InnerId == innerId); } public static ValueTask DeleteByInnerIdAsync(this IAppDbService service, TEntity entity, CancellationToken token = default) where TEntity : class, IAppDbEntity { - return service.ExecuteAsync((dbset, token) => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId, token), token); + return service.DeleteByInnerIdAsync(entity.InnerId, token); + } + + public static ValueTask DeleteByInnerIdAsync(this IAppDbService service, Guid innerId, CancellationToken token = default) + where TEntity : class, IAppDbEntity + { + return service.DeleteAsync(e => e.InnerId == innerId, token); } public static List ListByArchiveId(this IAppDbService service, Guid archiveId) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs index 1965ba4d..4ce1dac9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceCollectionExtension.cs @@ -30,7 +30,13 @@ internal static class AppDbServiceCollectionExtension public static ValueTask> ListAsync(this IAppDbService service, Expression> predicate, CancellationToken token = default) where TEntity : class { - return service.QueryAsync((query, token) => query.Where(predicate).ToListAsync(token), token); + return service.ListAsync(query => query.Where(predicate), token); + } + + public static ValueTask> ListAsync(this IAppDbService service, Func, IQueryable> query, CancellationToken token = default) + where TEntity : class + { + return service.QueryAsync((query1, token) => query(query1).ToListAsync(token), token); } public static ObservableCollection ObservableCollection(this IAppDbService service) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs index 798da431..7d0bd691 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Abstraction/AppDbServiceExtension.cs @@ -126,6 +126,18 @@ internal static class AppDbServiceExtension return service.QueryAsync((query, token) => query.SingleAsync(predicate, token), token); } + public static TEntity? SingleOrDefault(this IAppDbService service, Expression> predicate) + where TEntity : class + { + return service.Query(query => query.SingleOrDefault(predicate)); + } + + public static ValueTask SingleOrDefaultAsync(this IAppDbService service, Expression> predicate, CancellationToken token = default) + where TEntity : class + { + return service.QueryAsync((query, token) => query.SingleOrDefaultAsync(predicate, token), token); + } + public static int Update(this IAppDbService service, TEntity entity) where TEntity : class { @@ -144,9 +156,21 @@ internal static class AppDbServiceExtension 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 ValueTask DeleteAsync(this IAppDbService service, TEntity entity, CancellationToken token = default) where TEntity : class { return service.ExecuteAsync((dbset, token) => dbset.RemoveAndSaveAsync(entity, token), token); } + + public static ValueTask DeleteAsync(this IAppDbService service, Expression> predicate, CancellationToken token = default) + where TEntity : class + { + return service.ExecuteAsync((dbset, token) => dbset.Where(predicate).ExecuteDeleteAsync(token), token); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs index 683c7dc4..e67fa126 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementDbService.cs @@ -46,13 +46,12 @@ internal sealed partial class AchievementDbService : IAchievementDbService [SuppressMessage("", "CA1305")] public ValueTask> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take, CancellationToken token = default) { - return this.QueryAsync>( - (query, token) => query + return this.ListAsync( + query => query .Where(a => a.ArchiveId == archiveId) .Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED) .OrderByDescending(a => a.Time.ToString()) - .Take(take) - .ToListAsync(token), + .Take(take), token); } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Announcement/IAnnouncementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Announcement/IAnnouncementService.cs index 9dc59681..a6386111 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Announcement/IAnnouncementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Announcement/IAnnouncementService.cs @@ -12,12 +12,5 @@ namespace Snap.Hutao.Service.Announcement; [HighQuality] internal interface IAnnouncementService { - /// - /// 异步获取游戏公告与活动,通常会进行缓存 - /// - /// 语言代码 - /// 服务器 - /// 取消令牌 - /// 公告包装器 - ValueTask GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken cancellationToken = default); + ValueTask GetAnnouncementWrapperAsync(string languageCode, Region region, CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs index 31014a30..d93e048b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoDbService.cs @@ -29,11 +29,11 @@ internal sealed partial class AvatarInfoDbService : IAvatarInfoDbService public void RemoveAvatarInfoRangeByUid(string uid) { - this.Execute(dbset => dbset.Where(i => i.Uid == uid).ExecuteDelete()); + this.Delete(i => i.Uid == uid); } public async ValueTask RemoveAvatarInfoRangeByUidAsync(string uid, CancellationToken token = default) { - await this.ExecuteAsync((dbset, token) => dbset.Where(i => i.Uid == uid).ExecuteDeleteAsync(token), token).ConfigureAwait(false); + await this.DeleteAsync(i => i.Uid == uid, token).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs index bbed4250..7116fdbe 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/AvatarInfoService.cs @@ -25,11 +25,11 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService private readonly IMetadataService metadataService; private readonly ISummaryFactory summaryFactory; - public async ValueTask> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default) + public async ValueTask> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default) { if (!await metadataService.InitializeAsync().ConfigureAwait(false)) { - return new(RefreshResult.MetadataNotInitialized, null); + return new(RefreshResultKind.MetadataNotInitialized, null); } switch (refreshOption) @@ -40,43 +40,43 @@ internal sealed partial class AvatarInfoService : IAvatarInfoService if (resp is null) { - return new(RefreshResult.APIUnavailable, default); + return new(RefreshResultKind.APIUnavailable, default); } if (!string.IsNullOrEmpty(resp.Message)) { - return new(RefreshResult.StatusCodeNotSucceed, new Summary { Message = resp.Message }); + return new(RefreshResultKind.StatusCodeNotSucceed, new Summary { Message = resp.Message }); } if (!resp.IsValid) { - return new(RefreshResult.ShowcaseNotOpen, default); + return new(RefreshResultKind.ShowcaseNotOpen, default); } List list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByShowcaseAsync(userAndUid.Uid.Value, resp.AvatarInfoList, token).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false); - return new(RefreshResult.Ok, summary); + return new(RefreshResultKind.Ok, summary); } case RefreshOption.RequestFromHoyolabGameRecord: { List list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByGameRecordCharacterAsync(userAndUid, token).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false); - return new(RefreshResult.Ok, summary); + return new(RefreshResultKind.Ok, summary); } case RefreshOption.RequestFromHoyolabCalculate: { List list = await avatarInfoDbBulkOperation.UpdateDbAvatarInfosByCalculateAvatarDetailAsync(userAndUid, token).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false); - return new(RefreshResult.Ok, summary); + return new(RefreshResultKind.Ok, summary); } default: { List list = await avatarInfoDbService.GetAvatarInfoListByUidAsync(userAndUid.Uid.Value, token).ConfigureAwait(false); Summary summary = await GetSummaryCoreAsync(list, token).ConfigureAwait(false); - return new(RefreshResult.Ok, summary.Avatars.Count == 0 ? null : summary); + return new(RefreshResultKind.Ok, summary.Avatars.Count == 0 ? null : summary); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs index b6c623b6..f931f946 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/IAvatarInfoService.cs @@ -19,5 +19,5 @@ internal interface IAvatarInfoService /// 刷新选项 /// 取消令牌 /// 总览数据 - ValueTask> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default); + ValueTask> GetSummaryAsync(UserAndUid userAndUid, RefreshOption refreshOption, CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/RefreshResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/RefreshResultKind.cs similarity index 95% rename from src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/RefreshResult.cs rename to src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/RefreshResultKind.cs index f68f6449..986d1128 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/RefreshResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/RefreshResultKind.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.AvatarInfo; /// 刷新结果 /// [HighQuality] -internal enum RefreshResult +internal enum RefreshResultKind { /// /// 正常 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs index cf68e715..c7fa3190 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs @@ -26,11 +26,11 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService private readonly ITaskContext taskContext; private readonly AppOptions appOptions; - private HashSet currentBackgroundPathSet; + private HashSet? currentBackgroundPathSet; - public async ValueTask> GetNextBackgroundImageAsync(BackgroundImage? previous) + public async ValueTask> GetNextBackgroundImageAsync(BackgroundImage? previous, CancellationToken token = default) { - HashSet backgroundSet = await SkipOrInitBackgroundAsync().ConfigureAwait(false); + HashSet backgroundSet = await SkipOrInitBackgroundAsync(token).ConfigureAwait(false); if (backgroundSet.Count <= 0) { @@ -79,7 +79,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService } } - private async ValueTask> SkipOrInitBackgroundAsync() + private async ValueTask> SkipOrInitBackgroundAsync(CancellationToken token = default) { switch (appOptions.BackgroundImageType) { @@ -90,7 +90,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder(); currentBackgroundPathSet = Directory - .GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories) + .EnumerateFiles(backgroundFolder, "*", SearchOption.AllDirectories) .Where(path => AllowedFormats.Contains(Path.GetExtension(path))) .ToHashSet(); } @@ -100,13 +100,13 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService } case BackgroundImageType.HutaoBing: - await SetCurrentBackgroundPathSetAsync(client => client.GetBingWallpaperAsync()).ConfigureAwait(false); + await SetCurrentBackgroundPathSetAsync((client, token) => client.GetBingWallpaperAsync(token), token).ConfigureAwait(false); break; case BackgroundImageType.HutaoDaily: - await SetCurrentBackgroundPathSetAsync(client => client.GetTodayWallpaperAsync()).ConfigureAwait(false); + await SetCurrentBackgroundPathSetAsync((client, token) => client.GetTodayWallpaperAsync(token), token).ConfigureAwait(false); break; case BackgroundImageType.HutaoOfficialLauncher: - await SetCurrentBackgroundPathSetAsync(client => client.GetLauncherWallpaperAsync()).ConfigureAwait(false); + await SetCurrentBackgroundPathSetAsync((client, token) => client.GetLauncherWallpaperAsync(token), token).ConfigureAwait(false); break; default: currentBackgroundPathSet = []; @@ -116,10 +116,10 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService currentBackgroundPathSet ??= []; return currentBackgroundPathSet; - async Task SetCurrentBackgroundPathSetAsync(Func>> responseFactory) + async Task SetCurrentBackgroundPathSetAsync(Func>> responseFactory, CancellationToken token = default) { HutaoWallpaperClient wallpaperClient = serviceProvider.GetRequiredService(); - Response response = await responseFactory(wallpaperClient).ConfigureAwait(false); + Response response = await responseFactory(wallpaperClient, token).ConfigureAwait(false); if (response is { Data: Wallpaper wallpaper }) { await taskContext.SwitchToMainThreadAsync(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs index 86ae854f..56d13635 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs @@ -5,5 +5,4 @@ namespace Snap.Hutao.Service.BackgroundImage; internal interface IBackgroundImageService { - ValueTask> GetNextBackgroundImageAsync(BackgroundImage? previous); -} \ No newline at end of file + ValueTask> GetNextBackgroundImageAsync(BackgroundImage? previous, CancellationToken token = default); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs index 164971a4..c7d5934e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs @@ -2,9 +2,8 @@ // Licensed under the MIT license. using Microsoft.EntityFrameworkCore; -using Snap.Hutao.Core.Database; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Database; +using Snap.Hutao.Service.Abstraction; using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Cultivation; @@ -15,182 +14,90 @@ internal sealed partial class CultivationDbService : ICultivationDbService { private readonly IServiceProvider serviceProvider; + public IServiceProvider ServiceProvider { get => serviceProvider; } + public List GetInventoryItemListByProjectId(Guid projectId) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - IQueryable result = appDbContext.InventoryItems.AsNoTracking().Where(a => a.ProjectId == projectId); - return [.. result]; - } + return this.List(i => i.ProjectId == projectId); } - public async ValueTask> GetInventoryItemListByProjectIdAsync(Guid projectId) + public ValueTask> GetInventoryItemListByProjectIdAsync(Guid projectId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return await appDbContext.InventoryItems - .AsNoTracking() - .Where(a => a.ProjectId == projectId) - .ToListAsync() - .ConfigureAwait(false); - } + return this.ListAsync(i => i.ProjectId == projectId, token); } - public async ValueTask> GetCultivateEntryListByProjectIdAsync(Guid projectId) + public ValueTask> GetCultivateEntryListByProjectIdAsync(Guid projectId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return await appDbContext.CultivateEntries - .AsNoTracking() - .Where(e => e.ProjectId == projectId) - .ToListAsync() - .ConfigureAwait(false); - } + return this.ListAsync(e => e.ProjectId == projectId, token); } - public async ValueTask> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId) + public ValueTask> GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(Guid projectId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return await appDbContext.CultivateEntries - .AsNoTracking() - .Where(e => e.ProjectId == projectId) - .Include(e => e.LevelInformation) - .ToListAsync() - .ConfigureAwait(false); - } + return this.ListAsync(query => query.Where(e => e.ProjectId == projectId).Include(e => e.LevelInformation), token); } - public async ValueTask> GetCultivateItemListByEntryIdAsync(Guid entryId) + public ValueTask> GetCultivateItemListByEntryIdAsync(Guid entryId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return await appDbContext.CultivateItems - .Where(i => i.EntryId == entryId) - .OrderBy(i => i.ItemId) - .ToListAsync() - .ConfigureAwait(false); - } + return this.ListAsync(query => query.Where(i => i.EntryId == entryId).OrderBy(i => i.ItemId), token); } - public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId) + public async ValueTask RemoveCultivateEntryByIdAsync(Guid entryId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateEntries - .ExecuteDeleteWhereAsync(i => i.InnerId == entryId) - .ConfigureAwait(false); - } + await this.DeleteByInnerIdAsync(entryId, token).ConfigureAwait(false); } public void UpdateCultivateItem(CultivateItem item) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - appDbContext.CultivateItems.UpdateAndSave(item); - } + this.Update(item); } - public async ValueTask UpdateCultivateItemAsync(CultivateItem item) + public async ValueTask UpdateCultivateItemAsync(CultivateItem item, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateItems.UpdateAndSaveAsync(item).ConfigureAwait(false); - } + await this.UpdateAsync(item, token).ConfigureAwait(false); } - public async ValueTask GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId) + public async ValueTask GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return await appDbContext.CultivateEntries - .SingleOrDefaultAsync(e => e.ProjectId == projectId && e.Id == itemId) - .ConfigureAwait(false); - } + return await this.SingleOrDefaultAsync(e => e.ProjectId == projectId && e.Id == itemId, token).ConfigureAwait(false); } - public async ValueTask AddCultivateEntryAsync(CultivateEntry entry) + public async ValueTask AddCultivateEntryAsync(CultivateEntry entry, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false); - } + await this.AddAsync(entry, token).ConfigureAwait(false); } - public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId) + public async ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateItems - .ExecuteDeleteWhereAsync(i => i.EntryId == entryId) - .ConfigureAwait(false); - } + await this.DeleteAsync(i => i.EntryId == entryId, token).ConfigureAwait(false); } - public async ValueTask AddCultivateItemRangeAsync(IEnumerable toAdd) + public async ValueTask AddCultivateItemRangeAsync(IEnumerable toAdd, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false); - } + await this.AddRangeAsync(toAdd, token).ConfigureAwait(false); } - public async ValueTask AddCultivateProjectAsync(CultivateProject project) + public async ValueTask AddCultivateProjectAsync(CultivateProject project, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateProjects.AddAndSaveAsync(project).ConfigureAwait(false); - } + await this.AddAsync(project, token).ConfigureAwait(false); } - public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId) + public async ValueTask RemoveCultivateProjectByIdAsync(Guid projectId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.CultivateProjects - .ExecuteDeleteWhereAsync(p => p.InnerId == projectId) - .ConfigureAwait(false); - } + await this.DeleteByInnerIdAsync(projectId, token).ConfigureAwait(false); } public ObservableCollection GetCultivateProjectCollection() { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - return appDbContext.CultivateProjects.AsNoTracking().ToObservableCollection(); - } + return this.ObservableCollection(); } - public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId) + public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.LevelInformations.ExecuteDeleteWhereAsync(l => l.EntryId == entryId).ConfigureAwait(false); - } + await this.DeleteAsync(l => l.EntryId == entryId, token).ConfigureAwait(false); } - public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation) + public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation, CancellationToken token = default) { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - await appDbContext.LevelInformations.AddAndSaveAsync(levelInformation).ConfigureAwait(false); - } + await this.AddAsync(levelInformation, token).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.Collection.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.Collection.cs deleted file mode 100644 index 9c128f4c..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.Collection.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Core.Database; -using Snap.Hutao.Model.Entity; -using System.Collections.ObjectModel; - -namespace Snap.Hutao.Service.Cultivation; - -/// -/// 集合部分 -/// -internal sealed partial class CultivationService -{ - private ObservableCollection? projects; - - /// - public CultivateProject? Current - { - get => dbCurrent.Current; - set => dbCurrent.Current = value; - } - - /// - public ObservableCollection ProjectCollection - { - get - { - if (projects is null) - { - projects = cultivationDbService.GetCultivateProjectCollection(); - Current ??= projects.SelectedOrDefault(); - } - - return projects; - } - } - - /// - public async ValueTask TryAddProjectAsync(CultivateProject project) - { - if (string.IsNullOrWhiteSpace(project.Name)) - { - return ProjectAddResult.InvalidName; - } - - ArgumentNullException.ThrowIfNull(projects); - - if (projects.Any(a => a.Name == project.Name)) - { - return ProjectAddResult.AlreadyExists; - } - - // Sync cache - await taskContext.SwitchToMainThreadAsync(); - projects.Add(project); - - // Sync database - await taskContext.SwitchToBackgroundAsync(); - await cultivationDbService.AddCultivateProjectAsync(project).ConfigureAwait(false); - - return ProjectAddResult.Added; - } - - /// - public async ValueTask RemoveProjectAsync(CultivateProject project) - { - ArgumentNullException.ThrowIfNull(projects); - - // Sync cache - // Keep this on main thread. - await taskContext.SwitchToMainThreadAsync(); - projects.Remove(project); - - // Sync database - await taskContext.SwitchToBackgroundAsync(); - await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index 93cd54e5..d1fddb5e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -4,13 +4,13 @@ using Snap.Hutao.Core.Database; using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Metadata.Item; using Snap.Hutao.Service.Inventory; using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.ViewModel.Cultivation; using System.Collections.ObjectModel; +using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; namespace Snap.Hutao.Service.Cultivation; @@ -25,28 +25,46 @@ internal sealed partial class CultivationService : ICultivationService private readonly ScopedDbCurrent dbCurrent; private readonly ICultivationDbService cultivationDbService; private readonly IInventoryDbService inventoryDbService; - private readonly IServiceProvider serviceProvider; private readonly ITaskContext taskContext; + private ObservableCollection? projects; + + /// + public CultivateProject? Current + { + get => dbCurrent.Current; + set => dbCurrent.Current = value; + } + + /// + public ObservableCollection ProjectCollection + { + get + { + if (projects is null) + { + projects = cultivationDbService.GetCultivateProjectCollection(); + Current ??= projects.SelectedOrDefault(); + } + + return projects; + } + } + /// public List GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand) { - using (IServiceScope scope = serviceProvider.CreateScope()) + Guid projectId = cultivateProject.InnerId; + List entities = cultivationDbService.GetInventoryItemListByProjectId(projectId); + + List results = []; + foreach (Material meta in context.EnumerateInventoryMaterial()) { - AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); - - Guid projectId = cultivateProject.InnerId; - List entities = cultivationDbService.GetInventoryItemListByProjectId(projectId); - - List results = []; - foreach (Material meta in context.EnumerateInventoryMaterial()) - { - InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id); - results.Add(new(entity, meta, saveCommand)); - } - - return results; + InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id); + results.Add(new(entity, meta, saveCommand)); } + + return results; } /// @@ -54,13 +72,15 @@ internal sealed partial class CultivationService : ICultivationService { await taskContext.SwitchToBackgroundAsync(); List entries = await cultivationDbService - .GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(cultivateProject.InnerId) + .GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(cultivateProject.InnerId) .ConfigureAwait(false); List resultEntries = new(entries.Count); foreach (CultivateEntry entry in entries) { List entryItems = []; + + // Async operation here, thus we can't use the Span trick. foreach (CultivateItem cultivateItem in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false)) { entryItems.Add(new(cultivateItem, context.GetMaterial(cultivateItem.ItemId))); @@ -78,9 +98,7 @@ internal sealed partial class CultivationService : ICultivationService resultEntries.Add(new(entry, item, entryItems)); } - return resultEntries - .OrderByDescending(e => e.IsToday) - .ToObservableCollection(); + return resultEntries.SortByDescending(e => e.IsToday).ToObservableCollection(); } /// @@ -92,11 +110,9 @@ internal sealed partial class CultivationService : ICultivationService Guid projectId = cultivateProject.InnerId; - token.ThrowIfCancellationRequested(); - - foreach (CultivateEntry entry in await cultivationDbService.GetCultivateEntryListByProjectIdAsync(projectId).ConfigureAwait(false)) + foreach (CultivateEntry entry in await cultivationDbService.GetCultivateEntryListByProjectIdAsync(projectId, token).ConfigureAwait(false)) { - foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false)) + foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId, token).ConfigureAwait(false)) { if (item.IsFinished) { @@ -114,9 +130,7 @@ internal sealed partial class CultivationService : ICultivationService } } - token.ThrowIfCancellationRequested(); - - foreach (InventoryItem inventoryItem in await cultivationDbService.GetInventoryItemListByProjectIdAsync(projectId).ConfigureAwait(false)) + foreach (InventoryItem inventoryItem in await cultivationDbService.GetInventoryItemListByProjectIdAsync(projectId, token).ConfigureAwait(false)) { if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is { } existedItem) { @@ -124,15 +138,12 @@ internal sealed partial class CultivationService : ICultivationService } } - token.ThrowIfCancellationRequested(); - return resultItems.SortBy(item => item.Inner.Id, MaterialIdComparer.Shared).ToObservableCollection(); } /// public async ValueTask RemoveCultivateEntryAsync(Guid entryId) { - await taskContext.SwitchToBackgroundAsync(); await cultivationDbService.RemoveCultivateEntryByIdAsync(entryId).ConfigureAwait(false); } @@ -149,7 +160,7 @@ internal sealed partial class CultivationService : ICultivationService } /// - public async ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items, LevelInformation levelInformation) + public async ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items, LevelInformation levelInformation) { if (items.Count == 0) { @@ -190,4 +201,45 @@ internal sealed partial class CultivationService : ICultivationService return true; } + + /// + public async ValueTask TryAddProjectAsync(CultivateProject project) + { + if (string.IsNullOrWhiteSpace(project.Name)) + { + return ProjectAddResultKind.InvalidName; + } + + ArgumentNullException.ThrowIfNull(projects); + + if (projects.Any(a => a.Name == project.Name)) + { + return ProjectAddResultKind.AlreadyExists; + } + + // Sync cache + await taskContext.SwitchToMainThreadAsync(); + projects.Add(project); + + // Sync database + await taskContext.SwitchToBackgroundAsync(); + await cultivationDbService.AddCultivateProjectAsync(project).ConfigureAwait(false); + + return ProjectAddResultKind.Added; + } + + /// + public async ValueTask RemoveProjectAsync(CultivateProject project) + { + ArgumentNullException.ThrowIfNull(projects); + + // Sync cache + // Keep this on main thread. + await taskContext.SwitchToMainThreadAsync(); + projects.Remove(project); + + // Sync database + await taskContext.SwitchToBackgroundAsync(); + await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs index 45c90413..c8173b50 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs @@ -2,43 +2,48 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Entity; +using Snap.Hutao.Service.Abstraction; using System.Collections.ObjectModel; namespace Snap.Hutao.Service.Cultivation; -internal interface ICultivationDbService +internal interface ICultivationDbService : IAppDbService, + IAppDbService, + IAppDbService, + IAppDbService, + IAppDbService { - ValueTask AddCultivateProjectAsync(CultivateProject project); + ValueTask AddCultivateProjectAsync(CultivateProject project, CancellationToken token = default); - ValueTask RemoveCultivateEntryByIdAsync(Guid entryId); + ValueTask RemoveCultivateEntryByIdAsync(Guid entryId, CancellationToken token = default); - ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId); + ValueTask RemoveCultivateItemRangeByEntryIdAsync(Guid entryId, CancellationToken token = default); - ValueTask RemoveCultivateProjectByIdAsync(Guid projectId); + ValueTask RemoveCultivateProjectByIdAsync(Guid projectId, CancellationToken token = default); - ValueTask GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId); + ValueTask GetCultivateEntryByProjectIdAndItemIdAsync(Guid projectId, uint itemId, CancellationToken token = default); - ValueTask> GetCultivateEntryListByProjectIdAsync(Guid projectId); + ValueTask> GetCultivateEntryListByProjectIdAsync(Guid projectId, CancellationToken token = default); - ValueTask> GetCultivateItemListByEntryIdAsync(Guid entryId); + ValueTask> GetCultivateItemListByEntryIdAsync(Guid entryId, CancellationToken token = default); ObservableCollection GetCultivateProjectCollection(); List GetInventoryItemListByProjectId(Guid projectId); - ValueTask> GetInventoryItemListByProjectIdAsync(Guid projectId); + ValueTask> GetInventoryItemListByProjectIdAsync(Guid projectId, CancellationToken token = default); - ValueTask AddCultivateEntryAsync(CultivateEntry entry); + ValueTask AddCultivateEntryAsync(CultivateEntry entry, CancellationToken token = default); - ValueTask AddCultivateItemRangeAsync(IEnumerable toAdd); + ValueTask AddCultivateItemRangeAsync(IEnumerable toAdd, CancellationToken token = default); void UpdateCultivateItem(CultivateItem item); - ValueTask UpdateCultivateItemAsync(CultivateItem item); + ValueTask UpdateCultivateItemAsync(CultivateItem item, CancellationToken token = default); - ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId); + ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId, CancellationToken token = default); - ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation); + ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation, CancellationToken token = default); - ValueTask> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId); + ValueTask> GetCultivateEntryListIncludingLevelInformationByProjectIdAsync(Guid projectId, CancellationToken token = default); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs index 2beb88ab..0a83a3e0 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -65,5 +65,5 @@ internal interface ICultivationService /// /// 项目 /// 添加操作的结果 - ValueTask TryAddProjectAsync(CultivateProject project); + ValueTask TryAddProjectAsync(CultivateProject project); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/MaterialIdComparer.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/MaterialIdComparer.cs index 236e1d2a..0d910c93 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/MaterialIdComparer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/MaterialIdComparer.cs @@ -8,9 +8,10 @@ namespace Snap.Hutao.Service.Cultivation; internal sealed class MaterialIdComparer : IComparer { - private static readonly Lazy LazyShared = new(() => new()); + private static MaterialIdComparer? shared; + private static object? syncRoot; - public static MaterialIdComparer Shared { get => LazyShared.Value; } + public static MaterialIdComparer Shared { get => LazyInitializer.EnsureInitialized(ref shared, ref syncRoot, () => new()); } public int Compare(MaterialId x, MaterialId y) { diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResultKind.cs similarity index 92% rename from src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResult.cs rename to src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResultKind.cs index fa23f036..e541d0c2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResultKind.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Cultivation; /// 项目添加结果 /// [HighQuality] -internal enum ProjectAddResult +internal enum ProjectAddResultKind { /// /// 添加成功 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs index c758b59f..50227764 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs @@ -120,7 +120,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I { try { - ValueResult summaryResult; + ValueResult summaryResult; using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) { ContentDialog dialog = await contentDialogFactory @@ -135,8 +135,8 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I } } - (RefreshResult result, Summary? summary) = summaryResult; - if (result == RefreshResult.Ok) + (RefreshResultKind result, Summary? summary) = summaryResult; + if (result == RefreshResultKind.Ok) { await taskContext.SwitchToMainThreadAsync(); Summary = summary; @@ -146,16 +146,16 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I { switch (result) { - case RefreshResult.APIUnavailable: + case RefreshResultKind.APIUnavailable: infoBarService.Warning(SH.ViewModelAvatarPropertyEnkaApiUnavailable); break; - case RefreshResult.StatusCodeNotSucceed: + case RefreshResultKind.StatusCodeNotSucceed: ArgumentNullException.ThrowIfNull(summary); infoBarService.Warning(summary.Message); break; - case RefreshResult.ShowcaseNotOpen: + case RefreshResultKind.ShowcaseNotOpen: infoBarService.Warning(SH.ViewModelAvatarPropertyShowcaseNotOpen); break; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs index fa7424cd..f50fa9ac 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs @@ -86,15 +86,15 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel switch (await cultivationService.TryAddProjectAsync(project).ConfigureAwait(false)) { - case ProjectAddResult.Added: + case ProjectAddResultKind.Added: infoBarService.Success(SH.ViewModelCultivationProjectAdded); await taskContext.SwitchToMainThreadAsync(); SelectedProject = project; break; - case ProjectAddResult.InvalidName: + case ProjectAddResultKind.InvalidName: infoBarService.Information(SH.ViewModelCultivationProjectInvalidName); break; - case ProjectAddResult.AlreadyExists: + case ProjectAddResultKind.AlreadyExists: infoBarService.Information(SH.ViewModelCultivationProjectAlreadyExists); break; default: