From 4db67113b4218b7421bb88f2db78acdeddfecc61 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 6 Jun 2022 15:43:47 +0500 Subject: [PATCH] =?UTF-8?q?CrudService=20=D0=BE=D1=87=D0=B8=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BE=D1=82=20=D0=BD=D0=B5=D0=B8=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D0=BC=D0=BE=D0=B3=D0=BE=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0.=20CrudCacheService=20=D0=90=D0=B4=D0=B0?= =?UTF-8?q?=D0=BF=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=81=D1=85?= =?UTF-8?q?=D0=B5=D0=BC=D1=8B=20=D0=BA=D0=B5=D1=88=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F.=20=D0=A3=D0=B1=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D1=8B=20extention=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20mapster.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudApp/Data/IWellRelated.cs | 2 +- AsbCloudApp/Services/ICrudService.cs | 55 +++-- AsbCloudApp/Services/IWellService.cs | 2 +- AsbCloudInfrastructure/DependencyInjection.cs | 27 ++- .../EfCache/EfCacheDictionaryExtensions.cs | 94 +++++--- .../EfCache/EfCacheExtensions.cs | 94 +++++--- AsbCloudInfrastructure/MapsterExtension.cs | 32 +-- .../Services/ClusterService.cs | 4 +- .../Services/CrudCacheServiceBase.cs | 154 ++++++------ .../Services/CrudServiceBase.cs | 151 ++++++------ .../Services/DrillParamsService.cs | 4 +- .../DrillingProgram/DrillingProgramService.cs | 2 +- .../Services/MeasureService.cs | 31 ++- .../Services/SAUB/TelemetryService.cs | 2 +- .../Services/ScheduleService.cs | 12 +- .../Services/UserRoleService.cs | 15 +- .../Services/UserService.cs | 8 + .../Services/WellCompositeService.cs | 16 +- .../WellOperationService.cs | 3 +- .../Services/WellService.cs | 223 +++++++++--------- AsbCloudInfrastructure/Startup.cs | 2 +- AsbCloudWebApi/AsbCloudWebApi.csproj | 1 + .../Controllers/AdminClusterController.cs | 5 +- .../Controllers/AdminCompanyController.cs | 1 - .../Controllers/AdminDepositController.cs | 1 - .../Controllers/AdminTelemetryController.cs | 1 - .../Controllers/AdminWellController.cs | 10 +- AsbCloudWebApi/Controllers/CrudController.cs | 2 +- AsbCloudWebApi/wwwroot/asset-manifest.json | 74 +++--- AsbCloudWebApi/wwwroot/index.html | 2 +- 30 files changed, 550 insertions(+), 480 deletions(-) diff --git a/AsbCloudApp/Data/IWellRelated.cs b/AsbCloudApp/Data/IWellRelated.cs index 28c42911..f79a03a9 100644 --- a/AsbCloudApp/Data/IWellRelated.cs +++ b/AsbCloudApp/Data/IWellRelated.cs @@ -8,6 +8,6 @@ /// /// Well id in db /// - public int IdWell { get; set; } + int IdWell { get; set; } } } diff --git a/AsbCloudApp/Services/ICrudService.cs b/AsbCloudApp/Services/ICrudService.cs index 5e0919b7..8d5edb8d 100644 --- a/AsbCloudApp/Services/ICrudService.cs +++ b/AsbCloudApp/Services/ICrudService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; namespace AsbCloudApp.Services { #nullable enable + /// /// Сервис получения, добавления, изменения, удаления данных /// @@ -14,9 +15,31 @@ namespace AsbCloudApp.Services where Tdto : Data.IId { /// - /// Включение связных данных + /// Код возврата ошибки: Id не найден в БД. /// - ISet Includes { get; } + public const int ErrorIdNotFound = -1; + + /// + /// Получение всех записей + /// + /// + /// emptyList if nothing found + Task> GetAllAsync(CancellationToken token); + + /// + /// Получить запись по id + /// + /// + /// + /// null if not found + Task GetAsync(int id, CancellationToken token); + + /// + /// Получить запись по id + /// + /// + /// null if not found + Tdto? Get(int id); /// /// Добавление новой записи @@ -24,7 +47,7 @@ namespace AsbCloudApp.Services /// /// /// Id новой записи - Task InsertAsync(Tdto newItem, CancellationToken token = default); + Task InsertAsync(Tdto newItem, CancellationToken token); /// /// Добавление нескольких записей @@ -32,23 +55,7 @@ namespace AsbCloudApp.Services /// /// /// количество добавленных - Task InsertRangeAsync(IEnumerable newItems, CancellationToken token = default); - - /// - /// Получение всех записей - /// - /// - /// - [Obsolete("Небезопасный метод, может выполняться бесконечно долго")] - Task> GetAllAsync(CancellationToken token = default); - - /// - /// Получить запись по id - /// - /// - /// - /// - Task GetAsync(int id, CancellationToken token = default); + Task InsertRangeAsync(IEnumerable newItems, CancellationToken token); /// /// Отредактировать запись @@ -56,16 +63,16 @@ namespace AsbCloudApp.Services /// /// /// - /// - Task UpdateAsync(int id, Tdto item, CancellationToken token = default); + /// если больше 0 - Id записи, если меньше 0 - код ошибки + Task UpdateAsync(int id, Tdto item, CancellationToken token); /// /// Удалить запись /// /// /// - /// - Task DeleteAsync(int id, CancellationToken token = default); + /// если больше 0 - Id записи, если меньше 0 - код ошибки + Task DeleteAsync(int id, CancellationToken token); } #nullable disable } \ No newline at end of file diff --git a/AsbCloudApp/Services/IWellService.cs b/AsbCloudApp/Services/IWellService.cs index 76cbf2a9..b655566a 100644 --- a/AsbCloudApp/Services/IWellService.cs +++ b/AsbCloudApp/Services/IWellService.cs @@ -21,6 +21,6 @@ namespace AsbCloudApp.Services Task> GetClusterWellsIdsAsync(int idWell, CancellationToken token); SimpleTimezoneDto GetTimezone(int idWell); DatesRangeDto GetDatesRange(int idWell); - void EnshureTimezonesIsSet(); + Task EnshureTimezonesIsSetAsync(CancellationToken token); } } diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 48c3cb30..435e6418 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -47,6 +47,10 @@ namespace AsbCloudInfrastructure TypeAdapterConfig.GlobalSettings.Default.Config .ForType() .MapWith((source) => new(source)); + + TypeAdapterConfig.GlobalSettings.Default.Config + .ForType() + .MapWith((source) => new(source)); } public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) @@ -98,12 +102,27 @@ namespace AsbCloudInfrastructure services.AddTransient(); // admin crud services: - services.AddTransient, CrudServiceBase>(); // может быть включен в сервис TelemetryService + services.AddTransient, CrudServiceBase>(s => + new CrudCacheServiceBase( + s.GetService(), + dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService services.AddTransient, DrillParamsService>(); - services.AddTransient, CrudCacheServiceBase>(); - services.AddTransient, CrudCacheServiceBase>(); + services.AddTransient, CrudCacheServiceBase>(s => + new CrudCacheServiceBase( + s.GetService(), + dbSet => dbSet.Include(d => d.Clusters))); + services.AddTransient, CrudCacheServiceBase>(s => + new CrudCacheServiceBase( + s.GetService(), + dbSet => dbSet.Include(c=>c.CompanyType))); services.AddTransient, CrudCacheServiceBase>(); - services.AddTransient, CrudCacheServiceBase>(); // может быть включен в сервис ClusterService + services.AddTransient, CrudCacheServiceBase>(s => + new CrudCacheServiceBase( + s.GetService(), + dbSet => dbSet + .Include(c => c.Wells) + .Include(c => c.Deposit))); // может быть включен в сервис ClusterService + services.AddTransient, CrudCacheServiceBase>(); // TelemetryData services diff --git a/AsbCloudInfrastructure/EfCache/EfCacheDictionaryExtensions.cs b/AsbCloudInfrastructure/EfCache/EfCacheDictionaryExtensions.cs index 347b9e17..9846f872 100644 --- a/AsbCloudInfrastructure/EfCache/EfCacheDictionaryExtensions.cs +++ b/AsbCloudInfrastructure/EfCache/EfCacheDictionaryExtensions.cs @@ -26,6 +26,57 @@ namespace AsbCloudInfrastructure.EfCache internal DateTime DateObsolete; internal DateTime DateObsoleteTotal; internal readonly SemaphoreSlim semaphore = new(1); + + + internal Dictionary GetData() + where TKey : notnull + { + if (Data is Dictionary typedData) + return typedData; + throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + } + + internal Dictionary GetData(Func convert, int attempt = 1) + where TKey : notnull + { + if (Data is Dictionary typedData) + return typedData; + if (Data is Dictionary typedEntityData) + { + if (semaphore.Wait(0)) + { + try + { + var convertedData = typedEntityData.ToDictionary(i => i.Key, i => convert(i.Value)); + Data = convertedData; + return convertedData; + } + catch + { + throw; + } + finally + { + semaphore.Release(); + } + } + else + { + if (semaphore.Wait(semaphoreTimeout)) + { + semaphore.Release(); + } + else + { + semaphore.Release(); + throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while converting cache data"); + } + } + } + if (attempt > 0) + return GetData(convert, --attempt); + throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + } } private static CacheItem GetOrAddCache(string tag, Func valueFactory, TimeSpan obsolete) @@ -233,15 +284,9 @@ namespace AsbCloudInfrastructure.EfCache where TKey : notnull { IDictionary factory() - { - var queryData = query.AsNoTracking() - .ToDictionary(keySelector); - return queryData; - } + => query.AsNoTracking().ToDictionary(keySelector); var cache = GetOrAddCache(tag, factory, obsolescence); - if (cache.Data is Dictionary typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(); } /// @@ -267,17 +312,9 @@ namespace AsbCloudInfrastructure.EfCache where TKey : notnull { IDictionary factory() - { - var queryData = query.AsNoTracking() - .ToList(); - var data = queryData - .ToDictionary(keySelector, convert); - return data; - } + => query.AsNoTracking().ToDictionary(keySelector); var cache = GetOrAddCache(tag, factory, obsolescence); - if (cache.Data is Dictionary typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(convert); } /// @@ -301,15 +338,9 @@ namespace AsbCloudInfrastructure.EfCache where TKey : notnull { async Task factory(CancellationToken token) - { - var queryData = await query.AsNoTracking().ToDictionaryAsync(keySelector, token); - return queryData; - } + => await query.AsNoTracking().ToDictionaryAsync(keySelector, token); var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token); - - if (cache.Data is Dictionary typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(); } /// @@ -331,16 +362,9 @@ namespace AsbCloudInfrastructure.EfCache where TKey : notnull { async Task factory(CancellationToken token) - { - var queryData = await query.AsNoTracking().ToListAsync(token); - var data = queryData.ToDictionary(keySelector, convert); - return data; - } + => await query.AsNoTracking().ToDictionaryAsync(keySelector, token); var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token); - - if (cache.Data is Dictionary typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(convert); } /// diff --git a/AsbCloudInfrastructure/EfCache/EfCacheExtensions.cs b/AsbCloudInfrastructure/EfCache/EfCacheExtensions.cs index beaf1001..e8c8a6e2 100644 --- a/AsbCloudInfrastructure/EfCache/EfCacheExtensions.cs +++ b/AsbCloudInfrastructure/EfCache/EfCacheExtensions.cs @@ -26,6 +26,54 @@ namespace AsbCloudInfrastructure.EfCache internal DateTime DateObsolete; internal DateTime DateObsoleteTotal; internal readonly SemaphoreSlim semaphore = new(1); + + internal IEnumerable GetData() + { + if (Data is IEnumerable typedData) + return typedData; + throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + } + + internal IEnumerable GetData(Func convert, int attempt = 1) + { + if (Data is IEnumerable typedData) + return typedData; + if (Data is IEnumerable typedEntityData) + { + if (semaphore.Wait(0)) + { + try + { + var convertedData = typedEntityData.Select(convert).ToList(); + Data = convertedData; + return convertedData; + } + catch + { + throw; + } + finally + { + semaphore.Release(); + } + } + else + { + if (semaphore.Wait(semaphoreTimeout)) + { + semaphore.Release(); + } + else + { + semaphore.Release(); + throw new TimeoutException("EfCacheL2.GetOrAddCache. Can't wait too long while converting cache data"); + } + } + } + if(attempt > 0) + return GetData(convert, --attempt); + throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + } } private static CacheItem GetOrAddCache(string tag, Func valueFactory, TimeSpan obsolete) @@ -224,16 +272,9 @@ namespace AsbCloudInfrastructure.EfCache public static IEnumerable FromCache(this IQueryable query, string tag, TimeSpan obsolescence) where TEntity : class { - IEnumerable factory() - { - var queryData = query.AsNoTracking() - .ToList(); - return queryData; - } + IEnumerable factory() => query.AsNoTracking().ToList(); var cache = GetOrAddCache(tag, factory, obsolescence); - if(cache.Data is IEnumerable typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(); } /// @@ -250,19 +291,9 @@ namespace AsbCloudInfrastructure.EfCache public static IEnumerable FromCache(this IQueryable query, string tag, TimeSpan obsolescence, Func convert) where TEntity : class { - IEnumerable factory () - { - var queryData = query.AsNoTracking() - .ToList(); - var data = queryData - .Select(convert) - .ToList(); - return data; - } + IEnumerable factory () => query.AsNoTracking().ToList(); var cache = GetOrAddCache(tag, factory, obsolescence); - if (cache.Data is IEnumerable typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(convert); } /// @@ -278,14 +309,9 @@ namespace AsbCloudInfrastructure.EfCache where TEntity : class { async Task factory(CancellationToken token) - { - var queryData = await query.AsNoTracking().ToListAsync(token); - return queryData; - } + => await query.AsNoTracking().ToListAsync(token); var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token); - if (cache.Data is IEnumerable typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(); } /// @@ -304,17 +330,9 @@ namespace AsbCloudInfrastructure.EfCache where TEntity : class { async Task factory(CancellationToken token) - { - var queryData = await query.AsNoTracking().ToListAsync(token); - var data = queryData - .Select(convert) - .ToList(); - return data; - } + => await query.AsNoTracking().ToListAsync(token); var cache = await GetOrAddCacheAsync(tag, factory, obsolescence, token); - if (cache.Data is IEnumerable typedData) - return typedData; - throw new TypeAccessException("Cache data has wrong type. Possible 'tag' is not unique."); + return cache.GetData(convert); } /// diff --git a/AsbCloudInfrastructure/MapsterExtension.cs b/AsbCloudInfrastructure/MapsterExtension.cs index 9f678a80..d86af842 100644 --- a/AsbCloudInfrastructure/MapsterExtension.cs +++ b/AsbCloudInfrastructure/MapsterExtension.cs @@ -6,35 +6,11 @@ namespace Mapster { public static class MapsterExtension { - public static IEnumerable Adapt(this IEnumerable sourceList) - { - return sourceList.Select(item => item.Adapt()); - } + //public static IEnumerable Adapt(this IEnumerable sourceList) + //{ + // return sourceList.Select(item => item.Adapt()); + //} - public static TDestination Adapt(this object source, Action afterMapAction = default) - { - var dest = source.Adapt(); - afterMapAction?.Invoke(dest); - return dest; - } - - public static TDestination Adapt(this TSource source, Action afterMapAction = default) - { - var dest = source.Adapt(); - afterMapAction?.Invoke(dest, source); - return dest; - } - - public static IEnumerable Adapt(this IEnumerable sourceList, Action eachAfterMapAction = default) - { - foreach (var item in sourceList) - { - var dest = item.Adapt(); - eachAfterMapAction?.Invoke(dest, item); - yield return dest; - } - - } } } diff --git a/AsbCloudInfrastructure/Services/ClusterService.cs b/AsbCloudInfrastructure/Services/ClusterService.cs index 79f1ea46..0a361021 100644 --- a/AsbCloudInfrastructure/Services/ClusterService.cs +++ b/AsbCloudInfrastructure/Services/ClusterService.cs @@ -71,7 +71,7 @@ namespace AsbCloudInfrastructure.Services .ToListAsync(token) .ConfigureAwait(false); - var dtos = entities.Adapt(); + var dtos = entities.Adapt>(); return dtos; } @@ -87,7 +87,7 @@ namespace AsbCloudInfrastructure.Services .ToListAsync(token) .ConfigureAwait(false); - var dtos = entities.Adapt(); + var dtos = entities.Adapt>(); return dtos; } diff --git a/AsbCloudInfrastructure/Services/CrudCacheServiceBase.cs b/AsbCloudInfrastructure/Services/CrudCacheServiceBase.cs index 104aa156..d49100c1 100644 --- a/AsbCloudInfrastructure/Services/CrudCacheServiceBase.cs +++ b/AsbCloudInfrastructure/Services/CrudCacheServiceBase.cs @@ -1,7 +1,6 @@ -using AsbCloudApp.Services; using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services.Cache; -using Mapster; +using AsbCloudInfrastructure.EfCache; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; @@ -10,111 +9,104 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { - public class CrudCacheServiceBase : ICrudService +#nullable enable + /// + /// CRUD + /// + /// + /// + public class CrudCacheServiceBase: CrudServiceBase where TDto : AsbCloudApp.Data.IId - where TModel : class, AsbCloudDb.Model.IId + where TEntity : class, AsbCloudDb.Model.IId { - private CacheTable cache = null; - private readonly IAsbCloudDbContext db; - private readonly CacheDb cacheDb; + protected string CacheTag = typeof(TDto).Name; + protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5); + protected int KeySelector(TEntity entity) => entity.Id; - public ISet Includes { get; } = new SortedSet(); + public CrudCacheServiceBase(IAsbCloudDbContext dbContext) + : base(dbContext) { } - protected CacheTable Cache + public CrudCacheServiceBase(IAsbCloudDbContext dbContext, ISet includes) + : base(dbContext, includes) { } + + public CrudCacheServiceBase(IAsbCloudDbContext dbContext, Func, IQueryable> makeQuery) + : base(dbContext, makeQuery) { } + + /// + public override async Task InsertAsync(TDto newItem, CancellationToken token) { - get - { - if (cache is null) - cache = cacheDb.GetCachedTable((AsbCloudDbContext)db, Includes); - return cache; - } + var result = await base.InsertAsync(newItem, token); + if (result > 0) + DropCache(); + return result; } - public CrudCacheServiceBase(IAsbCloudDbContext db, CacheDb cacheDb) + /// + public override async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) { - this.db = db; - this.cacheDb = cacheDb; + var result = await base.InsertRangeAsync(dtos, token); + if (result > 0) + DropCache(); + return result; } - public virtual async Task InsertAsync(TDto newItem, CancellationToken token = default) + /// + public override async Task> GetAllAsync(CancellationToken token) { - var entity = Convert(newItem); - var insertedEntity = await Cache.InsertAsync(entity, token) - .ConfigureAwait(false); - return insertedEntity?.Id ?? -1; + var result = await GetQuery() + .FromCacheDictionaryAsync(CacheTag, CacheOlescence, KeySelector, Convert, token); + return result.Values; } - public virtual async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) + /// + /// + /// + /// + /// + public override TDto? Get(int id) { - var entities = dtos.Select(Convert); - var insertedEntities = await Cache.InsertAsync(entities, token) - .ConfigureAwait(false); - return insertedEntities?.Count() ?? 0; + var result = GetQuery() + .FromCacheDictionary(CacheTag, CacheOlescence, KeySelector, Convert); + return result.GetValueOrDefault(id); } - public virtual async Task> GetAllAsync(CancellationToken token) + /// + public override async Task GetAsync(int id, CancellationToken token) { - var entities = await Cache.WhereAsync(token) - .ConfigureAwait(false); - var dtos = entities?.Select(Convert); - return dtos; + var result = await GetQuery() + .FromCacheDictionaryAsync(CacheTag, CacheOlescence, KeySelector, Convert, token); + return result.GetValueOrDefault(id); } - public virtual async Task GetAsync(int id, CancellationToken token) + /// + public override async Task UpdateAsync(int id, TDto dto, CancellationToken token) { - var entity = await Cache - .FirstOrDefaultAsync(p => p.Id == id, token) - .ConfigureAwait(false); - if (entity is null) - return default; - var dto = Convert(entity); - return dto; + var result = await base.UpdateAsync(id, dto, token); + if (result > 0) + DropCache(); + return result; } - public virtual async Task UpdateAsync(int id, TDto dto, CancellationToken token) + /// + public override async Task DeleteAsync(int id, CancellationToken token) { - if (dto.Id != id) - { - var exist = await Cache.ContainsAsync(i => i.Id == dto.Id, token) - .ConfigureAwait(false); - - if (exist) - return -1; - - await Cache.RemoveAsync(i => i.Id == id, token) - .ConfigureAwait(false); - } - - var entity = Convert(dto); - await Cache.UpsertAsync(entity, token) - .ConfigureAwait(false); - return 1; + var result = await base.DeleteAsync(id, token); + if (result > 0) + DropCache(); + return result; } - public virtual async Task DeleteAsync(int id, CancellationToken token) - { - var affected = await Cache.RemoveAsync(p => p.Id == id, token) - .ConfigureAwait(false); - return affected; - } + protected virtual Task> GetCacheAsync(CancellationToken token) + => GetQuery() + .FromCacheDictionaryAsync(CacheTag, CacheOlescence, KeySelector, Convert, token); - public virtual async Task DeleteAsync(IEnumerable ids, CancellationToken token = default) - { - var affected = await Cache.RemoveAsync(p => ids.Contains(p.Id), token) - .ConfigureAwait(false); - return affected; - } - protected virtual TModel Convert(TDto src) - { - var entity = src.Adapt(); - return entity; - } + protected virtual Dictionary GetCache() + => GetQuery() + .FromCacheDictionary(CacheTag, CacheOlescence, KeySelector, Convert); - protected virtual TDto Convert(TModel src) - { - var dto = src.Adapt(); - return dto; - } + protected virtual void DropCache() + => dbSet.DropCacheDictionary(CacheTag); } +#nullable disable } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/CrudServiceBase.cs b/AsbCloudInfrastructure/Services/CrudServiceBase.cs index d64ba451..f5f6ff3f 100644 --- a/AsbCloudInfrastructure/Services/CrudServiceBase.cs +++ b/AsbCloudInfrastructure/Services/CrudServiceBase.cs @@ -1,5 +1,4 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Services; +using AsbCloudApp.Services; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; @@ -11,87 +10,99 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { - public class CrudServiceBase : ICrudService, IConverter +#nullable enable + /// + /// CRUD сервис для работы с БД + /// + /// + /// + public class CrudServiceBase : ICrudService where TDto : AsbCloudApp.Data.IId - where TModel : class, AsbCloudDb.Model.IId + where TEntity : class, AsbCloudDb.Model.IId { - protected readonly IAsbCloudDbContext context; - protected readonly DbSet dbSet; - - public ISet Includes { get; } = new SortedSet(); + protected readonly IAsbCloudDbContext dbContext; + protected readonly DbSet dbSet; + protected readonly Func> GetQuery; public CrudServiceBase(IAsbCloudDbContext context) { - this.context = context; - dbSet = context.Set(); + this.dbContext = context; + dbSet = context.Set(); + GetQuery = () => dbSet; } - public virtual async Task> GetPageAsync(int skip = 0, int take = 32, CancellationToken token = default) + public CrudServiceBase(IAsbCloudDbContext dbContext, ISet includes) { - var query = GetQueryWithIncludes(); - var count = await query - .CountAsync(token) - .ConfigureAwait(false); + this.dbContext = dbContext; + dbSet = dbContext.Set(); - var container = new PaginationContainer - { - Skip = skip, - Take = take, - Count = count, + GetQuery = () => { + IQueryable query = dbSet; + foreach (var include in includes) + query = query.Include(include); + return query; }; - - if (skip >= count) - return container; - - query = query - .OrderBy(e => e.Id); - - if (skip > 0) - query = query.Skip(skip); - - query = query.Take(take); - - var entities = await query - .ToListAsync(token) - .ConfigureAwait(false); - - container.Items = entities - .Select(entity => Convert(entity)) - .ToList(); - - return container; } + public CrudServiceBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery) + { + this.dbContext = context; + dbSet = context.Set(); + GetQuery = () => makeQuery(dbSet); + } + + /// public virtual async Task> GetAllAsync(CancellationToken token = default) { - var query = GetQueryWithIncludes(); - var entities = await query - .OrderBy(e => e.Id) - .ToListAsync(token).ConfigureAwait(false); - var dto = entities.Select(Convert).ToList(); - return dto; + var entities = await GetQuery() + //.OrderBy(e => e.Id) + .AsNoTracking() + .ToListAsync(token) + .ConfigureAwait(false); + var dtos = entities.Select(Convert).ToList(); + return dtos; } - public virtual async Task GetAsync(int id, CancellationToken token = default) + /// + public virtual async Task GetAsync(int id, CancellationToken token = default) { - var query = GetQueryWithIncludes(); - var entity = await query - .FirstOrDefaultAsync(e => e.Id == id, token).ConfigureAwait(false); + var entity = await GetQuery() + .AsNoTracking() + .FirstOrDefaultAsync(e => e.Id == id, token) + .ConfigureAwait(false); + if (entity == default) + return default; var dto = Convert(entity); return dto; } + /// + public virtual TDto? Get(int id) + { + var entity = GetQuery() + .AsNoTracking() + .FirstOrDefault(e => e.Id == id); + if (entity == default) + return default; + var dto = Convert(entity); + return dto; + } + + /// public virtual async Task InsertAsync(TDto item, CancellationToken token = default) { var entity = Convert(item); entity.Id = 0; dbSet.Add(entity); - await context.SaveChangesAsync(token); + await dbContext.SaveChangesAsync(token); return entity.Id; } + /// public virtual Task InsertRangeAsync(IEnumerable items, CancellationToken token = default) { + if (!items.Any()) + return Task.FromResult(0); var entities = items.Select(i => { var entity = Convert(i); @@ -100,40 +111,40 @@ namespace AsbCloudInfrastructure.Services }); dbSet.AddRange(entities); - return context.SaveChangesAsync(token); + return dbContext.SaveChangesAsync(token); } + /// public virtual async Task UpdateAsync(int id, TDto item, CancellationToken token = default) { - var existingEntity = await dbSet.AsNoTracking().FirstOrDefaultAsync(e => e.Id == id, token).ConfigureAwait(false); + var existingEntity = await dbSet + .AsNoTracking() + .FirstOrDefaultAsync(e => e.Id == id, token) + .ConfigureAwait(false); if (existingEntity is null) - return 0; + return ICrudService.ErrorIdNotFound; var entity = Convert(item); entity.Id = id; - dbSet.Update(entity); - return await context.SaveChangesAsync(token); + var entry = dbSet.Update(entity); + await dbContext.SaveChangesAsync(token); + return entry.Entity.Id; } + /// public virtual Task DeleteAsync(int id, CancellationToken token = default) { - var entity = dbSet.AsNoTracking() + var entity = dbSet + .AsNoTracking() .FirstOrDefault(e => e.Id == id); if (entity == default) - return Task.FromResult(0); + return Task.FromResult(ICrudService.ErrorIdNotFound); dbSet.Remove(entity); - return context.SaveChangesAsync(token); + return dbContext.SaveChangesAsync(token); } - public virtual TDto Convert(TModel src) => src.Adapt(); + protected virtual TDto Convert(TEntity src) => src.Adapt(); - public virtual TModel Convert(TDto src) => src.Adapt(); - - protected IQueryable GetQueryWithIncludes() - { - IQueryable query = dbSet; - foreach (var include in Includes) - query = query.Include(include); - return query; - } + protected virtual TEntity Convert(TDto src) => src.Adapt(); } +#nullable disable } diff --git a/AsbCloudInfrastructure/Services/DrillParamsService.cs b/AsbCloudInfrastructure/Services/DrillParamsService.cs index 385b3dea..00b45ca3 100644 --- a/AsbCloudInfrastructure/Services/DrillParamsService.cs +++ b/AsbCloudInfrastructure/Services/DrillParamsService.cs @@ -77,7 +77,7 @@ namespace AsbCloudInfrastructure.Services .ToListAsync(token) .ConfigureAwait(false); - var dto = entities.Adapt(); + var dto = entities.Adapt>(); return dto; } @@ -95,7 +95,7 @@ namespace AsbCloudInfrastructure.Services .ToListAsync(token) .ConfigureAwait(false); - var compositeDrillParamsDtos = compositeWellDrillParams.Adapt(); + var compositeDrillParamsDtos = compositeWellDrillParams.Adapt>(); return compositeDrillParamsDtos; } diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index aebb1657..7d5d79c3 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -75,7 +75,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram .SelectMany(r => r.Company.Users) .Where(u => u != null && !string.IsNullOrEmpty(u.Email)) .ToListAsync(token); - var usersDto = users.Adapt(); + var usersDto = users.Adapt>(); return usersDto; } diff --git a/AsbCloudInfrastructure/Services/MeasureService.cs b/AsbCloudInfrastructure/Services/MeasureService.cs index 2668cbe0..08e8b6b8 100644 --- a/AsbCloudInfrastructure/Services/MeasureService.cs +++ b/AsbCloudInfrastructure/Services/MeasureService.cs @@ -46,11 +46,7 @@ namespace AsbCloudInfrastructure.Services .ConfigureAwait(false); var timezone = wellService.GetTimezone(idWell); - var dto = entity?.Adapt((d, s) => - { - d.CategoryName = s.Category?.Name; - d.Timestamp = s.Timestamp.ToRemoteDateTime(timezone.Hours); - }); + var dto = Convert(entity, timezone.Hours); return dto; } @@ -69,11 +65,7 @@ namespace AsbCloudInfrastructure.Services .ConfigureAwait(false); var timezone = wellService.GetTimezone(idWell); - var dtos = entities.Adapt((d, s) => - { - d.CategoryName = s.Category?.Name; - d.Timestamp = s.Timestamp.ToRemoteDateTime(timezone.Hours); - }); + var dtos = entities.Select(e => Convert(e, timezone.Hours)); return dtos; } @@ -84,9 +76,8 @@ namespace AsbCloudInfrastructure.Services if (dto.Data is null) throw new ArgumentInvalidException("data.data is not optional", nameof(dto)); var timezone = wellService.GetTimezone(idWell); - var entity = dto.Adapt(); + var entity = Convert(dto, timezone.Hours); entity.IdWell = idWell; - entity.Timestamp = dto.Timestamp.ToUtcDateTimeOffset(timezone.Hours); db.Measures.Add(entity); return db.SaveChangesAsync(token); } @@ -109,7 +100,6 @@ namespace AsbCloudInfrastructure.Services throw new ArgumentInvalidException("id doesn't exist", nameof(dto)); var timezone = wellService.GetTimezone(idWell); - entity.IdWell = idWell; entity.Timestamp = dto.Timestamp.ToUtcDateTimeOffset(timezone.Hours); entity.Data = (RawData)dto.Data; @@ -138,5 +128,20 @@ namespace AsbCloudInfrastructure.Services db.Measures.RemoveRange(db.Measures.Where(m => m.IdWell == idWell && m.Id == idData)); return db.SaveChangesAsync(token); } + + private MeasureDto Convert(Measure entity, double hours) + { + var dto = entity.Adapt(); + dto.CategoryName = entity.Category?.Name; + dto.Timestamp = entity.Timestamp.ToRemoteDateTime(hours); + return dto; + } + private Measure Convert(MeasureDto dto, double hours) + { + var entity = dto.Adapt(); + entity.Category = null; + entity.Timestamp = dto.Timestamp.ToUtcDateTimeOffset(hours); + return entity; + } } } diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs index 30419050..96f5df65 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs @@ -51,7 +51,7 @@ namespace AsbCloudInfrastructure.Services.SAUB return telemetryDtos; var telemetries = cacheTelemetry .Where(t => activeTelemetriesUids.Contains(t.RemoteUid)); - telemetryDtos = telemetries.Adapt().ToList(); + telemetryDtos = telemetries.Adapt>().ToList(); return telemetryDtos; } diff --git a/AsbCloudInfrastructure/Services/ScheduleService.cs b/AsbCloudInfrastructure/Services/ScheduleService.cs index 88f035c2..7df6026e 100644 --- a/AsbCloudInfrastructure/Services/ScheduleService.cs +++ b/AsbCloudInfrastructure/Services/ScheduleService.cs @@ -16,15 +16,15 @@ namespace AsbCloudInfrastructure.Services { private readonly IWellService wellService; - public ScheduleService(IAsbCloudDbContext context, IWellService wellService) : base(context) + public ScheduleService(IAsbCloudDbContext context, IWellService wellService) + : base(context, dbSet => dbSet.Include(s => s.Driller)) { - Includes.Add(nameof(Schedule.Driller)); this.wellService = wellService; } public async Task> GetByIdWellAsync(int idWell, CancellationToken token = default) { - var entities = await GetQueryWithIncludes() + var entities = await GetQuery() .Where(s => s.IdWell == idWell) .ToListAsync(token); var dtos = entities.Select(Convert); @@ -36,7 +36,7 @@ namespace AsbCloudInfrastructure.Services var hoursOffset = wellService.GetTimezone(idWell).Hours; var date = workTime.ToUtcDateTimeOffset(hoursOffset); - var entities = await GetQueryWithIncludes() + var entities = await GetQuery() .Where(s => s.IdWell==idWell && s.DrillStart <= date && s.DrillEnd >= date) @@ -56,7 +56,7 @@ namespace AsbCloudInfrastructure.Services return entity?.Driller.Adapt(); } - public override Schedule Convert(ScheduleDto dto) + protected override Schedule Convert(ScheduleDto dto) { var hoursOffset = wellService.GetTimezone(dto.IdWell).Hours; var entity = base.Convert(dto); @@ -65,7 +65,7 @@ namespace AsbCloudInfrastructure.Services return entity; } - public override ScheduleDto Convert(Schedule entity) + protected override ScheduleDto Convert(Schedule entity) { var hoursOffset = wellService.GetTimezone(entity.IdWell).Hours; var dto = base.Convert(entity); diff --git a/AsbCloudInfrastructure/Services/UserRoleService.cs b/AsbCloudInfrastructure/Services/UserRoleService.cs index d6019def..12e501ad 100644 --- a/AsbCloudInfrastructure/Services/UserRoleService.cs +++ b/AsbCloudInfrastructure/Services/UserRoleService.cs @@ -56,6 +56,14 @@ namespace AsbCloudInfrastructure.Services var dtos = entities?.Select(Convert); return dtos; } + public UserRoleDto Get(int id) + { + var entity = cacheUserRoles.FirstOrDefault(r => r.Id == id); + if (entity is null) + return null; + var dto = Convert(entity); + return dto; + } public async Task GetAsync(int id, CancellationToken token = default) { @@ -97,7 +105,7 @@ namespace AsbCloudInfrastructure.Services .ConfigureAwait(false); if (exist) - return -1; + return ICrudService.ErrorIdNotFound; await cacheUserRoles.RemoveAsync(i => i.Id == id, token) .ConfigureAwait(false); @@ -107,10 +115,9 @@ namespace AsbCloudInfrastructure.Services await UpdatePermissionsAsync(dto, token); await UpdateIncludedRolesAsync(dto, token); - await cacheUserRoles.UpsertAsync(entity, token) + var result = await cacheUserRoles.UpsertAsync(entity, token) .ConfigureAwait(false); - - return dto.Id; + return result; } public IEnumerable GetNestedById(int id, int recursionLevel = 7) diff --git a/AsbCloudInfrastructure/Services/UserService.cs b/AsbCloudInfrastructure/Services/UserService.cs index 2fdcb94f..2984ebd0 100644 --- a/AsbCloudInfrastructure/Services/UserService.cs +++ b/AsbCloudInfrastructure/Services/UserService.cs @@ -81,6 +81,14 @@ namespace AsbCloudInfrastructure.Services return dtos; } + public UserExtendedDto Get(int id) + { + var entity = cacheUsers.FirstOrDefault(u => u.Id == id); + var dto = Convert(entity); + dto.RoleNames = GetRolesNamesByIdUser(dto.Id); + return dto; + } + public async Task GetAsync(int id, CancellationToken token = default) { var entity = await cacheUsers.FirstOrDefaultAsync(u => u.Id == id, token).ConfigureAwait(false); diff --git a/AsbCloudInfrastructure/Services/WellCompositeService.cs b/AsbCloudInfrastructure/Services/WellCompositeService.cs index 15a6b81e..da532e2c 100644 --- a/AsbCloudInfrastructure/Services/WellCompositeService.cs +++ b/AsbCloudInfrastructure/Services/WellCompositeService.cs @@ -26,7 +26,7 @@ namespace AsbCloudInfrastructure.Services .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); - return entities.Adapt(); + return entities.Select(Convert); } public Task SaveAsync(int idWell, IEnumerable wellComposites, CancellationToken token) @@ -35,10 +35,22 @@ namespace AsbCloudInfrastructure.Services .Where(c => c.IdWell == idWell)); var entities = wellComposites - .Adapt((s, _) => { s.IdWell = idWell; }); + .Select(w => Convert(idWell, w)); context.WellComposites.AddRange(entities); return context.SaveChangesAsync(token); } + + private WellComposite Convert(int idWell, WellCompositeDto dto) + { + var entity = dto.Adapt(); + entity.IdWell = idWell; + return entity; + } + private WellCompositeDto Convert(WellComposite entity) + { + var dto = entity.Adapt(); + return dto; + } } } diff --git a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs index 55979e96..2b4433f1 100644 --- a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs @@ -46,8 +46,7 @@ namespace AsbCloudInfrastructure.Services.WellOperationService { var operationTypes = cachedOperationCategories .Distinct().OrderBy(o => o.Name); - var result = operationTypes.Adapt(); - + var result = operationTypes.Adapt>(); return result; } diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs index 44085c81..a163ad6b 100644 --- a/AsbCloudInfrastructure/Services/WellService.cs +++ b/AsbCloudInfrastructure/Services/WellService.cs @@ -3,7 +3,9 @@ using AsbCloudApp.Exceptions; using AsbCloudApp.Services; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.Cache; +using AsbCloudInfrastructure.EfCache; using Mapster; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; @@ -25,31 +27,53 @@ namespace AsbCloudInfrastructure.Services dst => dst.WellType) .Config; + private const string relationCompaniesWellsCacheTag = "RelationCompaniesWells"; + private static readonly TimeSpan relationCompaniesWellsCacheObsolence = TimeSpan.FromMinutes(15); + private readonly ITelemetryService telemetryService; - private readonly CacheTable cacheRelationCompaniesWells; - private readonly CacheTable cacheCompanyWellTypes; + private readonly ICrudService companyTypesService; private readonly ITimezoneService timezoneService; - private readonly Lazy wellOperationService; + private readonly IWellOperationService wellOperationService; public ITelemetryService TelemetryService => telemetryService; + private static IQueryable MakeQueryWell(DbSet dbSet) + => dbSet + .Include(w => w.Cluster) + .ThenInclude(c => c.Deposit) + .Include(w => w.Telemetry) + .Include(w => w.WellType) + .Include(w => w.RelationCompaniesWells) + .ThenInclude(r => r.Company); + public WellService(IAsbCloudDbContext db, CacheDb cacheDb, ITelemetryService telemetryService, ITimezoneService timezoneService) - : base(db, cacheDb) + : base(db, MakeQueryWell) { this.telemetryService = telemetryService; this.timezoneService = timezoneService; - this.wellOperationService = new Lazy(() => new WellOperationService.WellOperationService(db, cacheDb, this)); - cacheRelationCompaniesWells = cacheDb.GetCachedTable((AsbCloudDbContext)db, nameof(RelationCompanyWell.Company), nameof(RelationCompanyWell.Well)); - cacheCompanyWellTypes = cacheDb.GetCachedTable((AsbCloudDbContext)db); - Includes.Add($"{nameof(Well.Cluster)}.{nameof(Cluster.Deposit)}"); - Includes.Add(nameof(Well.Telemetry)); - Includes.Add($"{nameof(Well.RelationCompaniesWells)}.{nameof(RelationCompanyWell.Company)}"); - Includes.Add(nameof(Well.WellType)); + + this.wellOperationService = new WellOperationService.WellOperationService(db, cacheDb, this); + companyTypesService = new CrudCacheServiceBase(dbContext); } + private IEnumerable GetCacheRelationCompanyWell() + => dbContext.RelationCompaniesWells + .Include(r => r.Company) + .Include(r => r.Well) + .FromCache(relationCompaniesWellsCacheTag, relationCompaniesWellsCacheObsolence); + + private Task> GetCacheRelationCompanyWellAsync(CancellationToken token) + => dbContext.RelationCompaniesWells + .Include(r => r.Company) + .Include(r => r.Well) + .FromCacheAsync(relationCompaniesWellsCacheTag, relationCompaniesWellsCacheObsolence, token); + + private void DropCacheRelationCompanyWell() + => dbContext.RelationCompaniesWells.DropCache(relationCompaniesWellsCacheTag); + public DateTimeOffset GetLastTelemetryDate(int idWell) { - var well = Cache.FirstOrDefault(w => w.Id == idWell); + var well = Get(idWell); if (well?.IdTelemetry is null) return DateTimeOffset.MinValue; @@ -60,14 +84,17 @@ namespace AsbCloudInfrastructure.Services public async Task> GetWellsByCompanyAsync(int idCompany, CancellationToken token) { - var relations = await cacheRelationCompaniesWells - .WhereAsync(r => r.IdCompany == idCompany, token); + var relationsCache = await GetCacheRelationCompanyWellAsync(token); - var wellsIds = relations.Select(r => r.IdWell); - var wells = await Cache.WhereAsync(w => wellsIds.Contains(w.Id), token); + var wellsIds = relationsCache + .Where(r => r.IdCompany == idCompany) + .Select(r => r.IdWell); + + var wellsDtos = (await GetCacheAsync(token)) + .Where(kv => wellsIds.Contains(kv.Key)) + .Select(kv =>kv.Value); - var dtos = wells.Select(Convert); - return dtos; + return wellsDtos.ToList(); } public override async Task InsertAsync(WellDto dto, CancellationToken token = default) @@ -78,20 +105,22 @@ namespace AsbCloudInfrastructure.Services if (dto.IdState is < 0 or > 2) throw new ArgumentInvalidException("Текущее состояние работы скважины указано неправильно.", nameof(dto)); - if (dto.Id != 0 && await Cache.ContainsAsync(w => w.Id == dto.Id, token)) + if (dto.Id != 0 && (await GetCacheAsync(token)).ContainsKey(dto.Id)) throw new ArgumentInvalidException($"Нельзя повторно добавить скважину с id: {dto.Id}", nameof(dto)); var entity = Convert(dto); - var result = await Cache.InsertAsync(entity, token); + var result = await base.InsertAsync(dto, token); if (dto.Companies.Any()) { - var newRelations = dto.Companies.Select(c => new RelationCompanyWell { IdWell = result.Id, IdCompany = c.Id }); - await cacheRelationCompaniesWells.InsertAsync(newRelations, token); + var newRelations = dto.Companies.Select(c => new RelationCompanyWell { IdWell = result, IdCompany = c.Id }); + dbContext.RelationCompaniesWells.AddRange(newRelations); + await dbContext.SaveChangesAsync(token); + DropCacheRelationCompanyWell(); } - return result.Id; + return result; } public override Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) @@ -111,47 +140,50 @@ namespace AsbCloudInfrastructure.Services if (dto.Id != idWell) throw new ArgumentInvalidException($"Нельзя поменять id для скважины: {idWell} => {dto.Id}.", nameof(dto)); - var entity = Convert(dto); - - var oldRelations = await cacheRelationCompaniesWells - .WhereAsync(r => r.IdWell == idWell, token); + var oldRelations = (await GetCacheRelationCompanyWellAsync(token)) + .Where(r => r.IdWell == idWell); if (dto.Companies.Count() != oldRelations.Count() || dto.Companies.Any(c => !oldRelations.Any(oldC => oldC.IdCompany == c.Id))) { - await cacheRelationCompaniesWells.RemoveAsync(r => r.IdWell == idWell, token); + dbContext.RelationCompaniesWells + .RemoveRange(dbContext.RelationCompaniesWells + .Where(r => r.IdWell == idWell)); + var newRelations = dto.Companies.Select(c => new RelationCompanyWell { IdWell = idWell, IdCompany = c.Id }); - await cacheRelationCompaniesWells.InsertAsync(newRelations, token); + dbContext.RelationCompaniesWells.AddRange(newRelations); } - var result = await Cache.UpsertAsync(entity, token); + var result = await base.UpdateAsync(idWell, dto, token); return result; } public bool IsCompanyInvolvedInWell(int idCompany, int idWell) - => cacheRelationCompaniesWells.Contains(r => r.IdWell == idWell && r.IdCompany == idCompany); + => GetCacheRelationCompanyWell() + .Any(r => r.IdWell == idWell && r.IdCompany == idCompany); public async Task IsCompanyInvolvedInWellAsync(int idCompany, int idWell, CancellationToken token) - => await cacheRelationCompaniesWells.ContainsAsync(r => r.IdWell == idWell && - r.IdCompany == idCompany, token).ConfigureAwait(false); + => (await GetCacheRelationCompanyWellAsync(token)) + .Any(r => r.IdWell == idWell && r.IdCompany == idCompany); public async Task GetWellCaptionByIdAsync(int idWell, CancellationToken token) { - var entity = await Cache.FirstOrDefaultAsync(w => w.Id == idWell, token).ConfigureAwait(false); + var entity = await GetAsync(idWell, token).ConfigureAwait(false); var dto = Convert(entity); return dto.Caption; } public async Task> GetCompaniesAsync(int idWell, CancellationToken token) { - var relations = await cacheRelationCompaniesWells.WhereAsync(r => r.IdWell == idWell, token); + var relations = (await GetCacheRelationCompanyWellAsync(token)) + .Where(r => r.IdWell == idWell); var dtos = relations.Select(r => Convert(r.Company)); return dtos; } private IEnumerable GetCompanies(int idWell) { - var relations = cacheRelationCompaniesWells.Where(r => r.IdWell == idWell); + var relations = GetCacheRelationCompanyWell().Where(r => r.IdWell == idWell); var dtos = relations.Select(r => Convert(r.Company)); return dtos; } @@ -168,16 +200,18 @@ namespace AsbCloudInfrastructure.Services public async Task> GetClusterWellsIdsAsync(int idWell, CancellationToken token) { - var well = await Cache.FirstOrDefaultAsync(w => w.Id == idWell, token) - .ConfigureAwait(false); + var well = await GetAsync(idWell, token); if (well is null) return null; - var clusterWells = await Cache.WhereAsync(w => w.IdCluster == well.IdCluster, token) - .ConfigureAwait(false); + var cache = await GetCacheAsync(token); - return clusterWells.Select(w => w.Id); + var clusterWellsIds = cache.Values + .Where((w) => w.IdCluster == well.IdCluster) + .Select(w => w.Id); + + return clusterWellsIds; } protected override Well Convert(WellDto dto) @@ -187,8 +221,8 @@ namespace AsbCloudInfrastructure.Services entity.IdTelemetry = entity.IdTelemetry ?? dto.IdTelemetry ?? dto.Telemetry?.Id; if (dto.Timezone is null) - if (TryGetTimezone(dto.Id, out var timezoneDto)) - entity.Timezone = timezoneDto.Adapt(); + entity.Timezone = GetTimezone(dto.Id) + .Adapt(); return entity; } @@ -201,15 +235,17 @@ namespace AsbCloudInfrastructure.Services var dto = base.Convert(entity); if (entity.Timezone is null) - if (TryGetTimezone(entity, out var timezone)) - dto.Timezone = timezone; + dto.Timezone = GetTimezone(entity.Id); - dto.StartDate = wellOperationService.Value.FirstOperationDate(entity.Id)?.ToRemoteDateTime(dto.Timezone.Hours); + dto.StartDate = wellOperationService.FirstOperationDate(entity.Id)?.ToRemoteDateTime(dto.Timezone.Hours); dto.WellType = entity.WellType?.Caption; dto.Cluster = entity.Cluster?.Caption; dto.Deposit = entity.Cluster?.Deposit?.Caption; - dto.LastTelemetryDate = GetLastTelemetryDate(entity.Id).DateTime; - dto.Companies = GetCompanies(entity.Id); + if(entity.IdTelemetry is not null) + dto.LastTelemetryDate = telemetryService.GetLastTelemetryDate((int)entity.IdTelemetry); + dto.Companies = entity.RelationCompaniesWells + .Select(r => Convert(r.Company)) + .ToList(); return dto; } @@ -217,94 +253,56 @@ namespace AsbCloudInfrastructure.Services { var dto = entity.Adapt(); dto.CompanyTypeCaption = entity.CompanyType?.Caption - ?? cacheCompanyWellTypes.FirstOrDefault(c => c.Id == entity.IdCompanyType).Caption; + ?? companyTypesService.Get(entity.IdCompanyType).Caption; return dto; } - public void EnshureTimezonesIsSet() + public async Task EnshureTimezonesIsSetAsync(CancellationToken token) { - var wells = Cache.Where(w => w.Timezone is null).ToList(); - foreach (var well in wells) - { - if (TryGetTimezone(well, out var timezone)) - well.Timezone = timezone.Adapt(); - else - well.Timezone = new SimpleTimezone - { - Hours = 5, - IsOverride = false, - TimeZoneId = "Assumed", - }; - } + var cache = await GetCacheAsync(token); + if (!cache.Values.Any(w => w.Timezone is null)) + return; - var wellsWithTz = wells.Where(w => w.Timezone is not null); - if (wellsWithTz.Any()) + var defaultTimeZone = new SimpleTimezone { - var adaptedWells = wellsWithTz.Adapt().Select(Convert); - Cache.Upsert(adaptedWells); - } - } + Hours = 5, + IsOverride = false, + TimeZoneId = "Assumed", + }; - private bool TryGetTimezone(int idWell, out SimpleTimezoneDto timezone) - { - timezone = null; - try - { - timezone = GetTimezone(idWell); - return timezone is not null; - } - catch - { - return false; - } + await dbSet.Where(w => w.Timezone == null) + .ForEachAsync(w => w.Timezone = defaultTimeZone, token); + + await dbContext.SaveChangesAsync(token); + DropCache(); } public SimpleTimezoneDto GetTimezone(int idWell) { - var well = Cache.FirstOrDefault(c => c.Id == idWell); + var well = Get(idWell); if (well == null) throw new ArgumentInvalidException($"idWell: {idWell} does not exist.", nameof(idWell)); return GetTimezone(well); } - private bool TryGetTimezone(Well well, out SimpleTimezoneDto timezone) + private SimpleTimezoneDto GetTimezone(WellDto wellDto) { - timezone = null; - try - { - timezone = GetTimezone(well); - return timezone is not null; - } - catch - { - return false; - } - } + if (wellDto.Timezone is not null) + return wellDto.Timezone; - private SimpleTimezoneDto GetTimezone(Well well) - { - if (well == null) - throw new ArgumentNullException(nameof(well)); - - if (well.Timezone is not null) - return well.Timezone.Adapt(); - - if (well.Telemetry is not null) + if (wellDto.Telemetry is not null) { - var timezone = telemetryService.GetTimezone(well.Telemetry.Id); + var timezone = telemetryService.GetTimezone(wellDto.Telemetry.Id); if (timezone is not null) - { - well.Timezone = timezone.Adapt(); return timezone; - } } + var well = GetQuery().FirstOrDefault(w => w.Id == wellDto.Id); var point = GetCoordinates(well); if (point is not null) { if (point.Timezone is not null) { - well.Timezone = point.Timezone; return point.Timezone.Adapt(); } @@ -313,13 +311,12 @@ namespace AsbCloudInfrastructure.Services var timezone = timezoneService.GetByCoordinates((double)point.Latitude, (double)point.Longitude); if (timezone is not null) { - well.Timezone = timezone.Adapt(); return timezone; } } } - throw new Exception($"Can't find timezone for well {well.Caption} id: {well.Id}"); + throw new Exception($"Can't find timezone for well {wellDto.Caption} id: {wellDto.Id}"); } private static AsbCloudDb.Model.IMapPoint GetCoordinates(Well well) @@ -330,9 +327,9 @@ namespace AsbCloudInfrastructure.Services if (well.Latitude is not null & well.Longitude is not null) return well; - if (well.Cluster is null) + if (well.IdCluster is null) throw new Exception($"Can't find coordinates of well {well.Caption} id: {well.Id}"); - + var cluster = well.Cluster; if (cluster.Latitude is not null & cluster.Longitude is not null) @@ -351,7 +348,7 @@ namespace AsbCloudInfrastructure.Services public DatesRangeDto GetDatesRange(int idWell) { - var well = Cache.FirstOrDefault(w => w.Id == idWell); + var well = Get(idWell); if (well is null) throw new Exception($"Well id: {idWell} does not exist."); diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 432bd68f..884a3383 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -19,7 +19,7 @@ namespace AsbCloudInfrastructure context.Database.Migrate(); var wellService = scope.ServiceProvider.GetService(); - wellService.EnshureTimezonesIsSet(); + wellService.EnshureTimezonesIsSetAsync(System.Threading.CancellationToken.None).Wait(); } } } diff --git a/AsbCloudWebApi/AsbCloudWebApi.csproj b/AsbCloudWebApi/AsbCloudWebApi.csproj index 492513b3..e317510e 100644 --- a/AsbCloudWebApi/AsbCloudWebApi.csproj +++ b/AsbCloudWebApi/AsbCloudWebApi.csproj @@ -3,6 +3,7 @@ net6.0 true + true $(NoWarn);1591 80899ceb-210f-4f19-ac56-aa90a5d666d4 diff --git a/AsbCloudWebApi/Controllers/AdminClusterController.cs b/AsbCloudWebApi/Controllers/AdminClusterController.cs index beced4cf..755f5c9b 100644 --- a/AsbCloudWebApi/Controllers/AdminClusterController.cs +++ b/AsbCloudWebApi/Controllers/AdminClusterController.cs @@ -12,9 +12,6 @@ namespace AsbCloudWebApi.Controllers { public AdminClusterController(ICrudService service) : base(service) - { - service.Includes.Add(nameof(ClusterDto.Wells)); - service.Includes.Add(nameof(ClusterDto.Deposit)); - } + {} } } diff --git a/AsbCloudWebApi/Controllers/AdminCompanyController.cs b/AsbCloudWebApi/Controllers/AdminCompanyController.cs index 0608d32a..09853a0e 100644 --- a/AsbCloudWebApi/Controllers/AdminCompanyController.cs +++ b/AsbCloudWebApi/Controllers/AdminCompanyController.cs @@ -13,7 +13,6 @@ namespace AsbCloudWebApi.Controllers public AdminCompanyController(ICrudService service) : base(service) { - service.Includes.Add("CompanyType"); } } } diff --git a/AsbCloudWebApi/Controllers/AdminDepositController.cs b/AsbCloudWebApi/Controllers/AdminDepositController.cs index e9275c06..571290de 100644 --- a/AsbCloudWebApi/Controllers/AdminDepositController.cs +++ b/AsbCloudWebApi/Controllers/AdminDepositController.cs @@ -13,7 +13,6 @@ namespace AsbCloudWebApi.Controllers public AdminDepositController(ICrudService service) : base(service) { - service.Includes.Add("Clusters"); } } } diff --git a/AsbCloudWebApi/Controllers/AdminTelemetryController.cs b/AsbCloudWebApi/Controllers/AdminTelemetryController.cs index 84ed7768..7e8c3d43 100644 --- a/AsbCloudWebApi/Controllers/AdminTelemetryController.cs +++ b/AsbCloudWebApi/Controllers/AdminTelemetryController.cs @@ -18,7 +18,6 @@ namespace AsbCloudWebApi.Controllers ITelemetryService telemetryService) : base(service) { - service.Includes.Add("Well"); this.telemetryService = telemetryService; } diff --git a/AsbCloudWebApi/Controllers/AdminWellController.cs b/AsbCloudWebApi/Controllers/AdminWellController.cs index 154217c8..0dd4457f 100644 --- a/AsbCloudWebApi/Controllers/AdminWellController.cs +++ b/AsbCloudWebApi/Controllers/AdminWellController.cs @@ -2,6 +2,8 @@ using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Threading; +using System.Threading.Tasks; namespace AsbCloudWebApi.Controllers { @@ -12,15 +14,13 @@ namespace AsbCloudWebApi.Controllers { public AdminWellController(IWellService service) : base(service) - { - service.Includes.Add("Telemetry"); - } + {} [HttpPost("EnshureTimezonesIsSet")] [Permission] - public IActionResult EnsureTimestamps() + public async Task EnsureTimestamps(CancellationToken token) { - ((IWellService)service).EnshureTimezonesIsSet(); + await ((IWellService)service).EnshureTimezonesIsSetAsync(token); return Ok(); } } diff --git a/AsbCloudWebApi/Controllers/CrudController.cs b/AsbCloudWebApi/Controllers/CrudController.cs index ce22fd29..e2e9d2e6 100644 --- a/AsbCloudWebApi/Controllers/CrudController.cs +++ b/AsbCloudWebApi/Controllers/CrudController.cs @@ -91,7 +91,7 @@ namespace AsbCloudWebApi.Controllers Forbid(); var result = await service.UpdateAsync(id, value, token).ConfigureAwait(false); - if (result == 0) + if (result == ICrudService.ErrorIdNotFound) return BadRequest($"id:{id} does not exist in the db"); return Ok(result); } diff --git a/AsbCloudWebApi/wwwroot/asset-manifest.json b/AsbCloudWebApi/wwwroot/asset-manifest.json index 14163925..62c408f8 100644 --- a/AsbCloudWebApi/wwwroot/asset-manifest.json +++ b/AsbCloudWebApi/wwwroot/asset-manifest.json @@ -1,50 +1,50 @@ { "files": { - "main.css": "/static/css/main.c2a82e71.chunk.css", - "main.js": "/static/js/main.1a56c0e0.chunk.js", - "main.js.map": "/static/js/main.1a56c0e0.chunk.js.map", - "runtime-main.js": "/static/js/runtime-main.8da12c69.js", - "runtime-main.js.map": "/static/js/runtime-main.8da12c69.js.map", - "static/js/2.f196b75b.chunk.js": "/static/js/2.f196b75b.chunk.js", - "static/js/2.f196b75b.chunk.js.map": "/static/js/2.f196b75b.chunk.js.map", + "main.css": "/static/css/main.a0664ea6.chunk.css", + "main.js": "/static/js/main.02d15bac.chunk.js", + "main.js.map": "/static/js/main.02d15bac.chunk.js.map", + "runtime-main.js": "/static/js/runtime-main.83ebcb38.js", + "runtime-main.js.map": "/static/js/runtime-main.83ebcb38.js.map", + "static/js/2.ebe1f792.chunk.js": "/static/js/2.ebe1f792.chunk.js", + "static/js/2.ebe1f792.chunk.js.map": "/static/js/2.ebe1f792.chunk.js.map", "static/css/3.f8ac3883.chunk.css": "/static/css/3.f8ac3883.chunk.css", - "static/js/3.31b66021.chunk.js": "/static/js/3.31b66021.chunk.js", - "static/js/3.31b66021.chunk.js.map": "/static/js/3.31b66021.chunk.js.map", + "static/js/3.a763380a.chunk.js": "/static/js/3.a763380a.chunk.js", + "static/js/3.a763380a.chunk.js.map": "/static/js/3.a763380a.chunk.js.map", "static/css/4.f8ac3883.chunk.css": "/static/css/4.f8ac3883.chunk.css", - "static/js/4.1f09e89e.chunk.js": "/static/js/4.1f09e89e.chunk.js", - "static/js/4.1f09e89e.chunk.js.map": "/static/js/4.1f09e89e.chunk.js.map", - "static/js/5.ef929bfe.chunk.js": "/static/js/5.ef929bfe.chunk.js", - "static/js/5.ef929bfe.chunk.js.map": "/static/js/5.ef929bfe.chunk.js.map", - "static/js/6.88051835.chunk.js": "/static/js/6.88051835.chunk.js", - "static/js/6.88051835.chunk.js.map": "/static/js/6.88051835.chunk.js.map", - "static/js/7.4f3c315a.chunk.js": "/static/js/7.4f3c315a.chunk.js", - "static/js/7.4f3c315a.chunk.js.map": "/static/js/7.4f3c315a.chunk.js.map", - "static/js/8.8e9a1dc7.chunk.js": "/static/js/8.8e9a1dc7.chunk.js", - "static/js/8.8e9a1dc7.chunk.js.map": "/static/js/8.8e9a1dc7.chunk.js.map", - "static/js/9.71667cac.chunk.js": "/static/js/9.71667cac.chunk.js", - "static/js/9.71667cac.chunk.js.map": "/static/js/9.71667cac.chunk.js.map", - "static/js/10.e5247b1b.chunk.js": "/static/js/10.e5247b1b.chunk.js", - "static/js/10.e5247b1b.chunk.js.map": "/static/js/10.e5247b1b.chunk.js.map", - "static/js/11.70112c8f.chunk.js": "/static/js/11.70112c8f.chunk.js", - "static/js/11.70112c8f.chunk.js.map": "/static/js/11.70112c8f.chunk.js.map", - "static/js/12.2265b74f.chunk.js": "/static/js/12.2265b74f.chunk.js", - "static/js/12.2265b74f.chunk.js.map": "/static/js/12.2265b74f.chunk.js.map", - "static/js/13.063a16c9.chunk.js": "/static/js/13.063a16c9.chunk.js", - "static/js/13.063a16c9.chunk.js.map": "/static/js/13.063a16c9.chunk.js.map", - "static/js/14.50a284b1.chunk.js": "/static/js/14.50a284b1.chunk.js", - "static/js/14.50a284b1.chunk.js.map": "/static/js/14.50a284b1.chunk.js.map", + "static/js/4.14deb3a9.chunk.js": "/static/js/4.14deb3a9.chunk.js", + "static/js/4.14deb3a9.chunk.js.map": "/static/js/4.14deb3a9.chunk.js.map", + "static/js/5.54daf1dd.chunk.js": "/static/js/5.54daf1dd.chunk.js", + "static/js/5.54daf1dd.chunk.js.map": "/static/js/5.54daf1dd.chunk.js.map", + "static/js/6.2f64a277.chunk.js": "/static/js/6.2f64a277.chunk.js", + "static/js/6.2f64a277.chunk.js.map": "/static/js/6.2f64a277.chunk.js.map", + "static/js/7.8c90cea1.chunk.js": "/static/js/7.8c90cea1.chunk.js", + "static/js/7.8c90cea1.chunk.js.map": "/static/js/7.8c90cea1.chunk.js.map", + "static/js/8.6e937634.chunk.js": "/static/js/8.6e937634.chunk.js", + "static/js/8.6e937634.chunk.js.map": "/static/js/8.6e937634.chunk.js.map", + "static/js/9.3b35991a.chunk.js": "/static/js/9.3b35991a.chunk.js", + "static/js/9.3b35991a.chunk.js.map": "/static/js/9.3b35991a.chunk.js.map", + "static/js/10.69527c71.chunk.js": "/static/js/10.69527c71.chunk.js", + "static/js/10.69527c71.chunk.js.map": "/static/js/10.69527c71.chunk.js.map", + "static/js/11.f8320c6a.chunk.js": "/static/js/11.f8320c6a.chunk.js", + "static/js/11.f8320c6a.chunk.js.map": "/static/js/11.f8320c6a.chunk.js.map", + "static/js/12.7a9654fd.chunk.js": "/static/js/12.7a9654fd.chunk.js", + "static/js/12.7a9654fd.chunk.js.map": "/static/js/12.7a9654fd.chunk.js.map", + "static/js/13.35247644.chunk.js": "/static/js/13.35247644.chunk.js", + "static/js/13.35247644.chunk.js.map": "/static/js/13.35247644.chunk.js.map", + "static/js/14.0f147158.chunk.js": "/static/js/14.0f147158.chunk.js", + "static/js/14.0f147158.chunk.js.map": "/static/js/14.0f147158.chunk.js.map", "index.html": "/index.html", "static/css/3.f8ac3883.chunk.css.map": "/static/css/3.f8ac3883.chunk.css.map", "static/css/4.f8ac3883.chunk.css.map": "/static/css/4.f8ac3883.chunk.css.map", - "static/css/main.c2a82e71.chunk.css.map": "/static/css/main.c2a82e71.chunk.css.map", - "static/js/2.f196b75b.chunk.js.LICENSE.txt": "/static/js/2.f196b75b.chunk.js.LICENSE.txt", + "static/css/main.a0664ea6.chunk.css.map": "/static/css/main.a0664ea6.chunk.css.map", + "static/js/2.ebe1f792.chunk.js.LICENSE.txt": "/static/js/2.ebe1f792.chunk.js.LICENSE.txt", "static/media/ClusterIcon.f85713df.svg": "/static/media/ClusterIcon.f85713df.svg", "static/media/DepositIcon.9688e406.svg": "/static/media/DepositIcon.9688e406.svg" }, "entrypoints": [ - "static/js/runtime-main.8da12c69.js", - "static/js/2.f196b75b.chunk.js", - "static/css/main.c2a82e71.chunk.css", - "static/js/main.1a56c0e0.chunk.js" + "static/js/runtime-main.83ebcb38.js", + "static/js/2.ebe1f792.chunk.js", + "static/css/main.a0664ea6.chunk.css", + "static/js/main.02d15bac.chunk.js" ] } \ No newline at end of file diff --git a/AsbCloudWebApi/wwwroot/index.html b/AsbCloudWebApi/wwwroot/index.html index ee10f878..ba0dec49 100644 --- a/AsbCloudWebApi/wwwroot/index.html +++ b/AsbCloudWebApi/wwwroot/index.html @@ -1 +1 @@ -АСБ Vision
\ No newline at end of file +АСБ Vision
\ No newline at end of file