mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Introducing IAppInfrastructureService
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Collection;
|
||||
|
||||
internal sealed class TwoEnumerbleEnumerator<TFirst, TSecond> : IDisposable
|
||||
{
|
||||
private readonly IEnumerator<TFirst> firstEnumerator;
|
||||
private readonly IEnumerator<TSecond> secondEnumerator;
|
||||
|
||||
public TwoEnumerbleEnumerator(IEnumerable<TFirst> firstEnumerable, IEnumerable<TSecond> secondEnumerable)
|
||||
{
|
||||
firstEnumerator = firstEnumerable.GetEnumerator();
|
||||
secondEnumerator = secondEnumerable.GetEnumerator();
|
||||
}
|
||||
|
||||
public (TFirst First, TSecond Second) Current { get => (firstEnumerator.Current, secondEnumerator.Current); }
|
||||
|
||||
public bool MoveNext(ref bool moveFirst, ref bool moveSecond)
|
||||
{
|
||||
moveFirst = moveFirst && firstEnumerator.MoveNext();
|
||||
moveSecond = moveSecond && secondEnumerator.MoveNext();
|
||||
|
||||
return moveFirst || moveSecond;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
firstEnumerator.Dispose();
|
||||
secondEnumerator.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ internal enum HutaoExceptionKind
|
||||
|
||||
// Foundation
|
||||
ImageCacheInvalidUri,
|
||||
DatabaseCorrupted,
|
||||
|
||||
// IO
|
||||
FileSystemCreateFileInsufficientPermissions,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
internal interface IAppDbEntity
|
||||
{
|
||||
Guid InnerId { get; set; }
|
||||
}
|
||||
@@ -16,8 +16,8 @@ namespace Snap.Hutao.Model.Entity;
|
||||
[HighQuality]
|
||||
[Table("achievements")]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class Achievement
|
||||
: IEquatable<Achievement>,
|
||||
internal sealed class Achievement : IAppDbEntity,
|
||||
IEquatable<Achievement>,
|
||||
IDbMappingForeignKeyFrom<Achievement, AchievementId>,
|
||||
IDbMappingForeignKeyFrom<Achievement, UIAFItem>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal static class AppDbServiceAppDbEntityExtension
|
||||
{
|
||||
public static int DeleteByInnerId<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class, IAppDbEntity
|
||||
{
|
||||
return service.Execute(dbset => dbset.ExecuteDeleteWhere(e => e.InnerId == entity.InnerId));
|
||||
}
|
||||
|
||||
public static ValueTask<int> DeleteByInnerIdAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class, IAppDbEntity
|
||||
{
|
||||
return service.ExecuteAsync(dbset => dbset.ExecuteDeleteWhereAsync(e => e.InnerId == entity.InnerId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal static class AppDbServiceExtension
|
||||
{
|
||||
public static TResult Execute<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, TResult> func)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return func(appDbContext.Set<TEntity>());
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, ValueTask<TResult>> asyncFunc)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return await asyncFunc(appDbContext.Set<TEntity>()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<TResult> ExecuteAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<DbSet<TEntity>, Task<TResult>> asyncFunc)
|
||||
where TEntity : class
|
||||
{
|
||||
using (IServiceScope scope = service.ServiceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
return await asyncFunc(appDbContext.Set<TEntity>()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Add<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AddAndSave(entity));
|
||||
}
|
||||
|
||||
public static ValueTask<int> AddAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => dbset.AddAndSaveAsync(entity));
|
||||
}
|
||||
|
||||
public static int AddRange<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AddRangeAndSave(entities));
|
||||
}
|
||||
|
||||
public static ValueTask<int> AddRangeAsync<TEntity>(this IAppDbService<TEntity> service, IEnumerable<TEntity> entities)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => dbset.AddRangeAndSaveAsync(entities));
|
||||
}
|
||||
|
||||
public static TEntity Single<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.AsNoTracking().Single(predicate));
|
||||
}
|
||||
|
||||
public static ValueTask<TEntity> SingleAsync<TEntity>(this IAppDbService<TEntity> service, Expression<Func<TEntity, bool>> predicate)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => dbset.AsNoTracking().SingleAsync(predicate, default));
|
||||
}
|
||||
|
||||
public static TResult Query<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, TResult> func)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => func(dbset.AsNoTracking()));
|
||||
}
|
||||
|
||||
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, ValueTask<TResult>> func)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => func(dbset.AsNoTracking()));
|
||||
}
|
||||
|
||||
public static ValueTask<TResult> QueryAsync<TEntity, TResult>(this IAppDbService<TEntity> service, Func<IQueryable<TEntity>, Task<TResult>> func)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => func(dbset.AsNoTracking()));
|
||||
}
|
||||
|
||||
public static int Update<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.UpdateAndSave(entity));
|
||||
}
|
||||
|
||||
public static ValueTask<int> UpdateAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => dbset.UpdateAndSaveAsync(entity));
|
||||
}
|
||||
|
||||
public static int Delete<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.Execute(dbset => dbset.RemoveAndSave(entity));
|
||||
}
|
||||
|
||||
public static ValueTask<int> DeleteAsync<TEntity>(this IAppDbService<TEntity> service, TEntity entity)
|
||||
where TEntity : class
|
||||
{
|
||||
return service.ExecuteAsync(dbset => dbset.RemoveAndSaveAsync(entity));
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,10 @@ namespace Snap.Hutao.Service.Abstraction;
|
||||
/// 数据库存储选项的设置
|
||||
/// </summary>
|
||||
[ConstructorGenerated]
|
||||
internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbStoreOptions>
|
||||
internal abstract partial class DbStoreOptions : ObservableObject
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DbStoreOptions Value { get => this; }
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取字符串数据
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
protected string GetOption(ref string? storage, string key, string defaultValue = "")
|
||||
{
|
||||
return GetOption(ref storage, key, () => defaultValue);
|
||||
@@ -49,13 +39,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取bool数据
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
protected bool GetOption(ref bool? storage, string key, bool defaultValue = false)
|
||||
{
|
||||
return GetOption(ref storage, key, () => defaultValue);
|
||||
@@ -78,13 +61,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
return storage.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取int数据
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
protected int GetOption(ref int? storage, string key, int defaultValue = 0)
|
||||
{
|
||||
return GetOption(ref storage, key, () => defaultValue);
|
||||
@@ -107,15 +83,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
return storage.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中获取任何类型的数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据的类型</typeparam>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="deserializer">反序列化器</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>值</returns>
|
||||
[return: NotNull]
|
||||
protected T GetOption<T>(ref T? storage, string key, Func<string, T> deserializer, [DisallowNull] T defaultValue)
|
||||
{
|
||||
@@ -160,13 +127,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void SetOption(ref string? storage, string key, string? value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!SetProperty(ref storage, value, propertyName))
|
||||
@@ -182,14 +142,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
/// <returns>是否设置了值</returns>
|
||||
protected bool SetOption(ref bool? storage, string key, bool value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
bool set = SetProperty(ref storage, value, propertyName);
|
||||
@@ -208,13 +160,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void SetOption(ref int? storage, string key, int value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!SetProperty(ref storage, value, propertyName))
|
||||
@@ -230,15 +175,6 @@ internal abstract partial class DbStoreOptions : ObservableObject, IOptions<DbSt
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将值存入数据库
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据的类型</typeparam>
|
||||
/// <param name="storage">存储字段</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="serializer">序列化器</param>
|
||||
/// <param name="propertyName">属性名称</param>
|
||||
protected void SetOption<T>(ref T? storage, string key, T value, Func<T, string> serializer, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!SetProperty(ref storage, value, propertyName))
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IAppDbService<TEntity> : IAppInfrastructureService
|
||||
where TEntity : class
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IAppInfrastructureService
|
||||
{
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IAppService;
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal static class ServiceScopeExtension
|
||||
{
|
||||
public static TService GetRequiredService<TService>(this IServiceScope scope)
|
||||
where TService : class
|
||||
{
|
||||
return scope.ServiceProvider.GetRequiredService<TService>();
|
||||
}
|
||||
|
||||
public static TDbContext GetDbContext<TDbContext>(this IServiceScope scope)
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
return scope.GetRequiredService<TDbContext>();
|
||||
}
|
||||
|
||||
public static AppDbContext GetAppDbContext(this IServiceScope scope)
|
||||
{
|
||||
return scope.GetDbContext<AppDbContext>();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Snap.Hutao.Core.Collection;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
@@ -21,197 +23,155 @@ internal sealed partial class AchievementDbBulkOperation
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<AchievementDbBulkOperation> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 合并
|
||||
/// </summary>
|
||||
/// <param name="archiveId">成就id</param>
|
||||
/// <param name="items">待合并的项</param>
|
||||
/// <param name="aggressive">是否贪婪</param>
|
||||
/// <returns>导入结果</returns>
|
||||
public ImportResult Merge(Guid archiveId, IEnumerable<UIAFItem> items, bool aggressive)
|
||||
{
|
||||
logger.LogInformation("Perform {Method} Operation for archive: {Id}, Aggressive: {Aggressive}", nameof(Merge), archiveId, aggressive);
|
||||
logger.LogInformation("Perform merge operation for [Archive: {Id}], [Aggressive: {Aggressive}]", archiveId, aggressive);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
|
||||
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.OrderBy(a => a.Id);
|
||||
|
||||
int add = 0;
|
||||
int update = 0;
|
||||
(int add, int update) = (0, 0);
|
||||
|
||||
using (IEnumerator<EntityAchievement> entityEnumerator = oldData.GetEnumerator())
|
||||
using (TwoEnumerbleEnumerator<EntityAchievement, UIAFItem> enumerator = new(oldData, items))
|
||||
{
|
||||
using (IEnumerator<UIAFItem> uiafEnumerator = items.GetEnumerator())
|
||||
(bool moveEntity, bool moveUIAF) = (true, true);
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool moveEntity = true;
|
||||
bool moveUIAF = true;
|
||||
|
||||
while (true)
|
||||
if (!enumerator.MoveNext(ref moveEntity, ref moveUIAF))
|
||||
{
|
||||
bool moveEntityResult = moveEntity && entityEnumerator.MoveNext();
|
||||
bool moveUIAFResult = moveUIAF && uiafEnumerator.MoveNext();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(moveEntityResult || moveUIAFResult))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityAchievement? entity = entityEnumerator.Current;
|
||||
UIAFItem? uiaf = uiafEnumerator.Current;
|
||||
|
||||
if (entity is null && uiaf is not null)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
else if (entity is not null && uiaf is null)
|
||||
{
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
(EntityAchievement? entity, UIAFItem? uiaf) = enumerator.Current;
|
||||
|
||||
switch (entity, uiaf)
|
||||
{
|
||||
case (null, not null):
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
continue;
|
||||
case (not null, null):
|
||||
continue; // Skipped
|
||||
default:
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
ArgumentNullException.ThrowIfNull(uiaf);
|
||||
|
||||
if (entity.Id < uiaf.Id)
|
||||
switch (entity.Id.CompareTo(uiaf.Id))
|
||||
{
|
||||
moveEntity = true;
|
||||
moveUIAF = false;
|
||||
}
|
||||
else if (entity.Id == uiaf.Id)
|
||||
{
|
||||
moveEntity = true;
|
||||
moveUIAF = true;
|
||||
case < 0:
|
||||
(moveEntity, moveUIAF) = (true, false);
|
||||
break;
|
||||
case 0:
|
||||
(moveEntity, moveUIAF) = (true, true);
|
||||
|
||||
if (aggressive)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(entity);
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
update++;
|
||||
}
|
||||
|
||||
break;
|
||||
case > 0:
|
||||
(moveEntity, moveUIAF) = (false, true);
|
||||
|
||||
if (aggressive)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(entity);
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
update++;
|
||||
}
|
||||
add++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// entity.Id > uiaf.Id
|
||||
moveEntity = false;
|
||||
moveUIAF = true;
|
||||
|
||||
appDbContext.Achievements.AddAndSave(EntityAchievement.From(archiveId, uiaf));
|
||||
add++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("{Method} Operation Complete, Add: {Add}, Update: {Update}", nameof(Merge), add, update);
|
||||
logger.LogInformation("Merge operation complete, [Add: {Add}], [Update: {Update}]", add, update);
|
||||
return new(add, update, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖
|
||||
/// </summary>
|
||||
/// <param name="archiveId">成就id</param>
|
||||
/// <param name="items">待覆盖的项</param>
|
||||
/// <returns>导入结果</returns>
|
||||
public ImportResult Overwrite(Guid archiveId, IEnumerable<EntityAchievement> items)
|
||||
{
|
||||
logger.LogInformation("Perform {Method} Operation for archive: {Id}", nameof(Overwrite), archiveId);
|
||||
logger.LogInformation("Perform Overwrite Operation for [Archive: {Id}]", archiveId);
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
AppDbContext appDbContext = scope.GetAppDbContext();
|
||||
|
||||
IOrderedQueryable<EntityAchievement> oldData = appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.OrderBy(a => a.Id);
|
||||
|
||||
int add = 0;
|
||||
int update = 0;
|
||||
int remove = 0;
|
||||
(int add, int update, int remove) = (0, 0, 0);
|
||||
|
||||
using (IEnumerator<EntityAchievement> oldDataEnumerator = oldData.GetEnumerator())
|
||||
using (TwoEnumerbleEnumerator<EntityAchievement, EntityAchievement> enumerator = new(oldData, items))
|
||||
{
|
||||
using (IEnumerator<EntityAchievement> newDataEnumerator = items.GetEnumerator())
|
||||
(bool moveOld, bool moveNew) = (true, true);
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool moveOld = true;
|
||||
bool moveNew = true;
|
||||
|
||||
while (true)
|
||||
if (!enumerator.MoveNext(ref moveOld, ref moveNew))
|
||||
{
|
||||
bool moveOldResult = moveOld && oldDataEnumerator.MoveNext();
|
||||
bool moveNewResult = moveNew && newDataEnumerator.MoveNext();
|
||||
break;
|
||||
}
|
||||
|
||||
if (moveOldResult || moveNewResult)
|
||||
{
|
||||
EntityAchievement? oldEntity = oldDataEnumerator.Current;
|
||||
EntityAchievement? newEntity = newDataEnumerator.Current;
|
||||
|
||||
if (oldEntity is null && newEntity is not null)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
continue;
|
||||
}
|
||||
else if (oldEntity is not null && newEntity is null)
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
continue;
|
||||
}
|
||||
(EntityAchievement? oldEntity, EntityAchievement? newEntity) = enumerator.Current;
|
||||
|
||||
switch (oldEntity, newEntity)
|
||||
{
|
||||
case (null, not null):
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
continue;
|
||||
case (not null, null):
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
continue;
|
||||
default:
|
||||
ArgumentNullException.ThrowIfNull(oldEntity);
|
||||
ArgumentNullException.ThrowIfNull(newEntity);
|
||||
|
||||
if (oldEntity.Id < newEntity.Id)
|
||||
switch (oldEntity.Id.CompareTo(newEntity.Id))
|
||||
{
|
||||
moveOld = true;
|
||||
moveNew = false;
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
remove++;
|
||||
}
|
||||
else if (oldEntity.Id == newEntity.Id)
|
||||
{
|
||||
moveOld = true;
|
||||
moveNew = true;
|
||||
case < 0:
|
||||
(moveOld, moveNew) = (true, false);
|
||||
break;
|
||||
case 0:
|
||||
(moveOld, moveNew) = (true, true);
|
||||
|
||||
if (oldEntity.Equals(newEntity))
|
||||
{
|
||||
// Skip same entry, reduce write operation.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
update++;
|
||||
}
|
||||
|
||||
break;
|
||||
case > 0:
|
||||
(moveOld, moveNew) = (false, true);
|
||||
|
||||
if (oldEntity.Equals(newEntity))
|
||||
{
|
||||
// skip same entry.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
appDbContext.Achievements.RemoveAndSave(oldEntity);
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
update++;
|
||||
}
|
||||
add++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// entity.Id > uiaf.Id
|
||||
moveOld = false;
|
||||
moveNew = true;
|
||||
appDbContext.Achievements.AddAndSave(newEntity);
|
||||
add++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("{Method} Operation Complete, Add: {Add}, Update: {Update}, Remove: {Remove}", nameof(Overwrite), add, update, remove);
|
||||
logger.LogInformation("Overwrite Operation Complete, Add: {Add}, Update: {Update}, Remove: {Remove}", add, update, remove);
|
||||
return new(add, update, remove);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using System.Collections.ObjectModel;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
@@ -21,148 +23,92 @@ internal sealed partial class AchievementDbService : IAchievementDbService
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public IServiceProvider ServiceProvider { get => serviceProvider; }
|
||||
|
||||
public Dictionary<AchievementId, EntityAchievement> GetAchievementMapByArchiveId(Guid archiveId)
|
||||
{
|
||||
Dictionary<AchievementId, EntityAchievement> entities;
|
||||
try
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
entities = appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToDictionary(a => (AchievementId)a.Id);
|
||||
}
|
||||
return this.Query<EntityAchievement, Dictionary<AchievementId, EntityAchievement>>(query => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToDictionary(a => (AchievementId)a.Id));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
throw ThrowHelper.DatabaseCorrupted(SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
|
||||
throw HutaoException.Throw(HutaoExceptionKind.DatabaseCorrupted, SH.ServiceAchievementUserdataCorruptedInnerIdNotUnique, ex);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public async ValueTask<int> GetFinishedAchievementCountByArchiveIdAsync(Guid archiveId)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
return await this.QueryAsync<EntityAchievement, int>(query => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.CountAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
.CountAsync())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA1305")]
|
||||
public async ValueTask<List<EntityAchievement>> GetLatestFinishedAchievementListByArchiveIdAsync(Guid archiveId, int take)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
return await this.QueryAsync<EntityAchievement, List<EntityAchievement>>(query => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.Where(a => a.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
.OrderByDescending(a => a.Time.ToString())
|
||||
.Take(take)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
.ToListAsync())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void OverwriteAchievement(EntityAchievement achievement)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
this.DeleteByInnerId(achievement);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// Delete exists one.
|
||||
appDbContext.Achievements.ExecuteDeleteWhere(e => e.InnerId == achievement.InnerId);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
{
|
||||
appDbContext.Achievements.AddAndSave(achievement);
|
||||
}
|
||||
this.Add(achievement);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask OverwriteAchievementAsync(EntityAchievement achievement)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
await this.DeleteByInnerIdAsync(achievement).ConfigureAwait(false);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// Delete exists one.
|
||||
await appDbContext.Achievements.ExecuteDeleteWhereAsync(e => e.InnerId == achievement.InnerId).ConfigureAwait(false);
|
||||
if (achievement.Status >= Model.Intrinsic.AchievementStatus.STATUS_FINISHED)
|
||||
{
|
||||
await appDbContext.Achievements.AddAndSaveAsync(achievement).ConfigureAwait(false);
|
||||
}
|
||||
await this.AddAsync(achievement).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<AchievementArchive> GetAchievementArchiveCollection()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return appDbContext.AchievementArchives.AsNoTracking().ToObservableCollection();
|
||||
}
|
||||
return this.Query<AchievementArchive, ObservableCollection<AchievementArchive>>(query => query.ToObservableCollection());
|
||||
}
|
||||
|
||||
public async ValueTask RemoveAchievementArchiveAsync(AchievementArchive archive)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
|
||||
// It will cascade deleted the achievements.
|
||||
await appDbContext.AchievementArchives.RemoveAndSaveAsync(archive).ConfigureAwait(false);
|
||||
}
|
||||
// It will cascade deleted the achievements.
|
||||
await this.DeleteAsync(archive).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public List<EntityAchievement> GetAchievementListByArchiveId(Guid archiveId)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
IQueryable<EntityAchievement> result = appDbContext.Achievements.AsNoTracking().Where(i => i.ArchiveId == archiveId);
|
||||
return [.. result];
|
||||
}
|
||||
return this.Query<EntityAchievement, List<EntityAchievement>>(query => [.. query.Where(a => a.ArchiveId == archiveId)]);
|
||||
}
|
||||
|
||||
public async ValueTask<List<EntityAchievement>> GetAchievementListByArchiveIdAsync(Guid archiveId)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.Achievements
|
||||
.AsNoTracking()
|
||||
.Where(i => i.ArchiveId == archiveId)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
return await this.QueryAsync<EntityAchievement, List<EntityAchievement>>(query => query
|
||||
.Where(a => a.ArchiveId == archiveId)
|
||||
.ToListAsync())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public List<AchievementArchive> GetAchievementArchiveList()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
IQueryable<AchievementArchive> result = appDbContext.AchievementArchives.AsNoTracking();
|
||||
return [.. result];
|
||||
}
|
||||
return this.Query<AchievementArchive, List<AchievementArchive>>(query => [.. query]);
|
||||
}
|
||||
|
||||
public async ValueTask<List<AchievementArchive>> GetAchievementArchiveListAsync()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.AchievementArchives.AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
}
|
||||
return await this.QueryAsync<AchievementArchive, List<AchievementArchive>>(query => query.ToListAsync()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,13 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using System.Collections.ObjectModel;
|
||||
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
|
||||
|
||||
namespace Snap.Hutao.Service.Achievement;
|
||||
|
||||
internal interface IAchievementDbService
|
||||
internal interface IAchievementDbService : IAppDbService<Model.Entity.AchievementArchive>, IAppDbService<EntityAchievement>
|
||||
{
|
||||
ValueTask RemoveAchievementArchiveAsync(Model.Entity.AchievementArchive archive);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Announcement;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
using Snap.Hutao.Web.Response;
|
||||
@@ -4,7 +4,7 @@
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||
|
||||
namespace Snap.Hutao.Service.Abstraction;
|
||||
namespace Snap.Hutao.Service.Announcement;
|
||||
|
||||
/// <summary>
|
||||
/// 公告服务
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Announcement;
|
||||
using Snap.Hutao.Service.Hutao;
|
||||
using Snap.Hutao.View.Card;
|
||||
using Snap.Hutao.View.Card.Primitive;
|
||||
|
||||
Reference in New Issue
Block a user