From fb5483299d4d1affb50b4ddf640981be176e8d7e Mon Sep 17 00:00:00 2001 From: "ai.astrakhantsev" Date: Thu, 1 Dec 2022 15:56:11 +0500 Subject: [PATCH 01/14] =?UTF-8?q?#8101318=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BA=D1=8D=D1=88=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AsbCloudApp/Data/UserRoleDto.cs | 2 +- AsbCloudApp/Repositories/IFileRepository.cs | 2 +- AsbCloudApp/Repositories/IUserRepository.cs | 2 +- .../Repositories/IUserRoleRepository.cs | 2 +- .../{ICrudService.cs => ICrudRepository.cs} | 2 +- AsbCloudApp/Services/IDrillParamsService.cs | 2 +- .../Services/IRepositoryWellRelated.cs | 2 +- AsbCloudApp/Services/IWellService.cs | 2 +- AsbCloudInfrastructure/DependencyInjection.cs | 26 ++-- .../Repository/CacheBase.cs | 70 +++++++++ .../Repository/CrudCacheRepositoryBase.cs | 100 +++++++++++++ .../Repository/CrudCacheServiceBase.cs | 140 ------------------ ...udServiceBase.cs => CrudRepositoryBase.cs} | 52 ++----- .../CrudWellRelatedCacheServiceBase.cs | 2 +- .../Repository/CrudWellRelatedServiceBase.cs | 2 +- .../Repository/FileRepository.cs | 5 - .../Repository/QueryContainer.cs | 28 ++++ .../Repository/SetpointsRequestRepository.cs | 27 ++++ .../Services/DrillParamsService.cs | 2 +- .../Services/FileCategoryService.cs | 2 +- .../SubsystemOperationTimeService.cs | 4 +- .../Services/Subsystems/SubsystemService.cs | 2 +- .../Services/WellService.cs | 12 +- AsbCloudWebApi.Tests/RepositoryFactory.cs | 2 +- .../ServicesTests/CrudServiceTestAbstract.cs | 4 +- .../DepositCrudCacheServiceTest.cs | 4 +- .../ServicesTests/DrillerServiceTest.cs | 4 +- .../Controllers/AdminClusterController.cs | 4 +- .../Controllers/AdminCompanyController.cs | 4 +- .../Controllers/AdminCompanyTypeController.cs | 4 +- .../Controllers/AdminDepositController.cs | 4 +- .../Controllers/AdminPermissionController.cs | 4 +- .../Controllers/AdminTelemetryController.cs | 4 +- .../Controllers/AdminUserController.cs | 2 +- .../Controllers/AdminWellController.cs | 2 +- AsbCloudWebApi/Controllers/CrudController.cs | 6 +- .../Controllers/DrillerController.cs | 4 +- .../Controllers/FileCategoryController.cs | 4 +- .../Subsystems/AdminSubsystemController.cs | 4 +- 39 files changed, 298 insertions(+), 252 deletions(-) rename AsbCloudApp/Services/{ICrudService.cs => ICrudRepository.cs} (98%) create mode 100644 AsbCloudInfrastructure/Repository/CacheBase.cs create mode 100644 AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs delete mode 100644 AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs rename AsbCloudInfrastructure/Repository/{CrudServiceBase.cs => CrudRepositoryBase.cs} (71%) create mode 100644 AsbCloudInfrastructure/Repository/QueryContainer.cs diff --git a/AsbCloudApp/Data/UserRoleDto.cs b/AsbCloudApp/Data/UserRoleDto.cs index aa525c91..8d761a85 100644 --- a/AsbCloudApp/Data/UserRoleDto.cs +++ b/AsbCloudApp/Data/UserRoleDto.cs @@ -29,7 +29,7 @@ namespace AsbCloudApp.Data /// /// Включенные роли /// - public virtual ICollection Roles { get; set; } + public virtual IEnumerable Roles { get; set; } /// /// Пользователи в роли diff --git a/AsbCloudApp/Repositories/IFileRepository.cs b/AsbCloudApp/Repositories/IFileRepository.cs index a05cf909..a62aa0d5 100644 --- a/AsbCloudApp/Repositories/IFileRepository.cs +++ b/AsbCloudApp/Repositories/IFileRepository.cs @@ -11,7 +11,7 @@ namespace AsbCloudApp.Repositories /// /// Сервис доступа к файлам /// - public interface IFileRepository : ICrudService + public interface IFileRepository : ICrudRepository { /// /// Получение файлов по скважине diff --git a/AsbCloudApp/Repositories/IUserRepository.cs b/AsbCloudApp/Repositories/IUserRepository.cs index 4f6b0fac..f23dc67b 100644 --- a/AsbCloudApp/Repositories/IUserRepository.cs +++ b/AsbCloudApp/Repositories/IUserRepository.cs @@ -7,7 +7,7 @@ namespace AsbCloudApp.Repositories /// /// Репозиторий пользователей /// - public interface IUserRepository : ICrudService + public interface IUserRepository : ICrudRepository { /// /// Получить список всех прав пользователя (включая наследование групп) diff --git a/AsbCloudApp/Repositories/IUserRoleRepository.cs b/AsbCloudApp/Repositories/IUserRoleRepository.cs index a2c25704..d20a42cb 100644 --- a/AsbCloudApp/Repositories/IUserRoleRepository.cs +++ b/AsbCloudApp/Repositories/IUserRoleRepository.cs @@ -10,7 +10,7 @@ namespace AsbCloudApp.Repositories /// /// Разрешения на доступ к данным /// - public interface IUserRoleRepository : ICrudService + public interface IUserRoleRepository : ICrudRepository { /// /// получить dto по названиям diff --git a/AsbCloudApp/Services/ICrudService.cs b/AsbCloudApp/Services/ICrudRepository.cs similarity index 98% rename from AsbCloudApp/Services/ICrudService.cs rename to AsbCloudApp/Services/ICrudRepository.cs index bbb7fce8..f402ebb9 100644 --- a/AsbCloudApp/Services/ICrudService.cs +++ b/AsbCloudApp/Services/ICrudRepository.cs @@ -10,7 +10,7 @@ namespace AsbCloudApp.Services /// Сервис получения, добавления, изменения, удаления данных /// /// - public interface ICrudService + public interface ICrudRepository where TDto : Data.IId { /// diff --git a/AsbCloudApp/Services/IDrillParamsService.cs b/AsbCloudApp/Services/IDrillParamsService.cs index e95e74b8..edb5067f 100644 --- a/AsbCloudApp/Services/IDrillParamsService.cs +++ b/AsbCloudApp/Services/IDrillParamsService.cs @@ -8,7 +8,7 @@ namespace AsbCloudApp.Services /// /// The параметры бурения service. /// - public interface IDrillParamsService : ICrudService + public interface IDrillParamsService : ICrudRepository { /// /// default параметры бурения diff --git a/AsbCloudApp/Services/IRepositoryWellRelated.cs b/AsbCloudApp/Services/IRepositoryWellRelated.cs index 6ce2af0e..f608fd04 100644 --- a/AsbCloudApp/Services/IRepositoryWellRelated.cs +++ b/AsbCloudApp/Services/IRepositoryWellRelated.cs @@ -11,7 +11,7 @@ namespace AsbCloudApp.Services /// Для сущностей относящихся к скважине /// /// - public interface IRepositoryWellRelated : ICrudService + public interface IRepositoryWellRelated : ICrudRepository where Tdto : IId, IWellRelated { /// diff --git a/AsbCloudApp/Services/IWellService.cs b/AsbCloudApp/Services/IWellService.cs index b8f9531c..7eef310e 100644 --- a/AsbCloudApp/Services/IWellService.cs +++ b/AsbCloudApp/Services/IWellService.cs @@ -9,7 +9,7 @@ namespace AsbCloudApp.Services /// /// сервис скважин /// - public interface IWellService : ICrudService + public interface IWellService : ICrudRepository { /// /// сервис телеметрии diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 90c1bf5c..ed5ce7c8 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -139,33 +139,33 @@ namespace AsbCloudInfrastructure services.AddTransient(); // admin crud services: - services.AddTransient, CrudServiceBase>(s => - new CrudCacheServiceBase( + services.AddTransient, CrudCacheRepositoryBase>(s => + new CrudCacheRepositoryBase( s.GetService(), s.GetService(), dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService - services.AddTransient, DrillParamsService>(); - services.AddTransient, CrudCacheServiceBase>(s => - new CrudCacheServiceBase( + services.AddTransient, DrillParamsService>(); + services.AddTransient, CrudCacheRepositoryBase>(s => + new CrudCacheRepositoryBase( s.GetService(), s.GetService(), dbSet => dbSet.Include(d => d.Clusters))); - services.AddTransient, CrudCacheServiceBase>(s => - new CrudCacheServiceBase( + services.AddTransient, CrudCacheRepositoryBase>(s => + new CrudCacheRepositoryBase( s.GetService(), s.GetService(), dbSet => dbSet.Include(c => c.CompanyType))); - services.AddTransient, CrudCacheServiceBase>(); - services.AddTransient, CrudCacheServiceBase>(s => - new CrudCacheServiceBase( + services.AddTransient, CrudCacheRepositoryBase>(); + services.AddTransient, CrudCacheRepositoryBase>(s => + new CrudCacheRepositoryBase( s.GetService(), s.GetService(), dbSet => dbSet .Include(c => c.Wells) .Include(c => c.Deposit))); // может быть включен в сервис ClusterService - services.AddTransient, CrudCacheServiceBase>(); + services.AddTransient, CrudCacheRepositoryBase>(); services.AddTransient(); services.AddTransient(); @@ -174,10 +174,10 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); // Subsystem service - services.AddTransient, CrudCacheServiceBase>(); + services.AddTransient, CrudCacheRepositoryBase>(); services.AddTransient(); - services.AddTransient, CrudCacheServiceBase>(); + services.AddTransient, CrudCacheRepositoryBase>(); // TelemetryData services services.AddTransient, TelemetryDataSaubService>(); diff --git a/AsbCloudInfrastructure/Repository/CacheBase.cs b/AsbCloudInfrastructure/Repository/CacheBase.cs new file mode 100644 index 00000000..88761ddb --- /dev/null +++ b/AsbCloudInfrastructure/Repository/CacheBase.cs @@ -0,0 +1,70 @@ +using AsbCloudApp.Data; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Repository +{ + public class CacheBase : QueryContainer + where TDto : AsbCloudApp.Data.IId + where TEntity : class, AsbCloudDb.Model.IId + { + protected readonly IMemoryCache memoryCache; + protected string CacheTag = typeof(TDto).Name; + protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5); + + public CacheBase(IAsbCloudDbContext context, IMemoryCache memoryCache) + : base(context) + { + this.memoryCache = memoryCache; + } + + public CacheBase(IAsbCloudDbContext context, IMemoryCache memoryCache, Func, IQueryable> makeQuery) + : base(context, makeQuery) + { + this.memoryCache = memoryCache; + } + + protected virtual void DropCache() + => memoryCache.Remove(CacheTag); + + protected virtual IEnumerable GetCache() + { + var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => + { + cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence; + cacheEntry.SlidingExpiration = CacheOlescence; + + var entities = this.GetQuery().ToArray(); + var dtos = entities.Select(Convert); + return dtos.ToArray(); + }); + return cache; + } + + protected virtual Task> GetCacheAsync(CancellationToken token) + { + var cache = memoryCache.GetOrCreateAsync(CacheTag, async (cacheEntry) => + { + cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence; + cacheEntry.SlidingExpiration = CacheOlescence; + + var entities = await this.GetQuery().ToArrayAsync(token); + var dtos = entities.Select(Convert); + return dtos.ToArray().AsEnumerable(); + }); + return cache; + } + + protected virtual TDto Convert(TEntity src) => src.Adapt(); + + protected virtual TEntity Convert(TDto src) => src.Adapt(); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs b/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs new file mode 100644 index 00000000..30aaf673 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs @@ -0,0 +1,100 @@ +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Repository +{ +#nullable enable + /// + /// CRUD + /// + /// + /// + public class CrudCacheRepositoryBase : CacheBase, ICrudRepository + where TDto : AsbCloudApp.Data.IId + where TEntity : class, IId + { + protected int KeySelector(TEntity entity) => entity.Id; + protected readonly ICrudRepository crudServiceBase; + + public CrudCacheRepositoryBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache) + : base(dbContext, memoryCache) + { + crudServiceBase = new CrudRepositoryBase(dbContext); + } + + public CrudCacheRepositoryBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func, IQueryable> makeQuery) + : base(dbContext, memoryCache, makeQuery) + { + crudServiceBase = new CrudRepositoryBase(dbContext, makeQuery); + } + + /// + public async Task InsertAsync(TDto newItem, CancellationToken token) + { + var result = await crudServiceBase.InsertAsync(newItem, token); + if (result > 0) + DropCache(); + return result; + } + + /// + public async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) + { + var result = await crudServiceBase.InsertRangeAsync(dtos, token); + if (result > 0) + DropCache(); + return result; + } + + /// + public async Task> GetAllAsync(CancellationToken token) + { + var cache = await GetCacheAsync(token); + return cache; + } + + /// + /// + /// + /// + /// + public TDto? GetOrDefault(int id) + { + var cache = GetCache(); + return cache.FirstOrDefault(d => d.Id == id); + } + + /// + public async Task GetOrDefaultAsync(int id, CancellationToken token) + { + var cache = await GetCacheAsync(token); + return cache.FirstOrDefault(d => d.Id == id); + } + + /// + public async Task UpdateAsync(TDto dto, CancellationToken token) + { + var result = await crudServiceBase.UpdateAsync(dto, token); + if (result > 0) + DropCache(); + return result; + } + + /// + public async Task DeleteAsync(int id, CancellationToken token) + { + var result = await crudServiceBase.DeleteAsync(id, token); + if (result > 0) + DropCache(); + return result; + } + } +#nullable disable +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs deleted file mode 100644 index 2624d39c..00000000 --- a/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs +++ /dev/null @@ -1,140 +0,0 @@ -using AsbCloudDb.Model; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Repository -{ -#nullable enable - /// - /// CRUD - /// - /// - /// - public class CrudCacheServiceBase : CrudServiceBase - where TDto : AsbCloudApp.Data.IId - where TEntity : class, IId - { - protected string CacheTag = typeof(TDto).Name; - protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5); - private readonly IMemoryCache memoryCache; - - protected int KeySelector(TEntity entity) => entity.Id; - - public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache) - : base(dbContext) - { - this.memoryCache = memoryCache; - } - - public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func, IQueryable> makeQuery) - : base(dbContext, makeQuery) - { - this.memoryCache = memoryCache; - } - - /// - public override async Task InsertAsync(TDto newItem, CancellationToken token) - { - var result = await base.InsertAsync(newItem, token); - if (result > 0) - DropCache(); - return result; - } - - /// - public override async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) - { - var result = await base.InsertRangeAsync(dtos, token); - if (result > 0) - DropCache(); - return result; - } - - /// - public override async Task> GetAllAsync(CancellationToken token) - { - var cache = await GetCacheAsync(token); - return cache; - } - - /// - /// - /// - /// - /// - public override TDto? GetOrDefault(int id) - { - var cache = GetCache(); - return cache.FirstOrDefault(d => d.Id == id); - } - - /// - public override async Task GetOrDefaultAsync(int id, CancellationToken token) - { - var cache = await GetCacheAsync(token); - return cache.FirstOrDefault(d => d.Id == id); - } - - /// - public override async Task UpdateAsync(TDto dto, CancellationToken token) - { - var result = await base.UpdateAsync(dto, token); - if (result > 0) - DropCache(); - return result; - } - - /// - public override async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) - { - var result = await base.UpdateRangeAsync(dtos, token); - if (result > 0) - DropCache(); - return result; - } - - /// - public override async Task DeleteAsync(int id, CancellationToken token) - { - var result = await base.DeleteAsync(id, token); - if (result > 0) - DropCache(); - return result; - } - - protected virtual Task> GetCacheAsync(CancellationToken token) - { - var cache = memoryCache.GetOrCreateAsync(CacheTag, async (cacheEntry) => { - cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence; - cacheEntry.SlidingExpiration = CacheOlescence; - - var entities = await GetQuery().ToArrayAsync(token); - var dtos = entities.Select(Convert); - return dtos.ToArray().AsEnumerable(); - }); - return cache; - } - - protected virtual IEnumerable GetCache() - { - var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => { - cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence; - cacheEntry.SlidingExpiration= CacheOlescence; - - var entities = GetQuery().ToArray(); - var dtos = entities.Select(Convert); - return dtos.ToArray(); - }); - return cache; - } - - protected virtual void DropCache() - => memoryCache.Remove(CacheTag); - } -#nullable disable -} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/CrudServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs similarity index 71% rename from AsbCloudInfrastructure/Repository/CrudServiceBase.cs rename to AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs index 67b750cc..da51ab32 100644 --- a/AsbCloudInfrastructure/Repository/CrudServiceBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs @@ -16,33 +16,22 @@ namespace AsbCloudInfrastructure.Repository /// /// /// - public class CrudServiceBase : ICrudService + public class CrudRepositoryBase : QueryContainer, ICrudRepository where TDto : AsbCloudApp.Data.IId where TEntity : class, IId { - protected readonly IAsbCloudDbContext dbContext; - protected readonly DbSet dbSet; - protected readonly Func> GetQuery; + public CrudRepositoryBase(IAsbCloudDbContext context) + : base(context) + { } - public CrudServiceBase(IAsbCloudDbContext context) - { - dbContext = context; - dbSet = context.Set(); - GetQuery = () => dbSet; - } - - public CrudServiceBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery) - { - dbContext = context; - dbSet = context.Set(); - GetQuery = () => makeQuery(dbSet); - } + public CrudRepositoryBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery) + : base(context, makeQuery) + { } /// public virtual async Task> GetAllAsync(CancellationToken token = default) { var entities = await GetQuery() - //.OrderBy(e => e.Id) .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); @@ -117,7 +106,7 @@ namespace AsbCloudInfrastructure.Repository .ConfigureAwait(false); if (existingEntity is null) - return ICrudService.ErrorIdNotFound; + return ICrudRepository.ErrorIdNotFound; var entity = Convert(item); var entry = dbSet.Update(entity); @@ -126,29 +115,6 @@ namespace AsbCloudInfrastructure.Repository return entry.Entity.Id; } - public virtual async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) - { - var ids = dtos.Select(d => d.Id); - var existingEntities = await dbSet - .AsNoTracking() - .Where(d => ids.Contains(d.Id)) - .Select(d => d.Id) - .ToListAsync(token) - .ConfigureAwait(false); - - if (ids.Count() > existingEntities.Count) - return ICrudService.ErrorIdNotFound; - - foreach (var dto in dtos) - { - var entity = Convert(dto); - var entry = dbSet.Update(entity); - } - - var affected = await dbContext.SaveChangesAsync(token); - return affected; - } - /// public virtual Task DeleteAsync(int id, CancellationToken token = default) { @@ -156,7 +122,7 @@ namespace AsbCloudInfrastructure.Repository .AsNoTracking() .FirstOrDefault(e => e.Id == id); if (entity == default) - return Task.FromResult(ICrudService.ErrorIdNotFound); + return Task.FromResult(ICrudRepository.ErrorIdNotFound); var entry = dbSet.Remove(entity); var affected = dbContext.SaveChangesAsync(token); entry.State = EntityState.Detached; diff --git a/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs index a7eb3305..29362c89 100644 --- a/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository { #nullable enable - public class CrudWellRelatedCacheServiceBase : CrudCacheServiceBase, IRepositoryWellRelated + public class CrudWellRelatedCacheServiceBase : CrudCacheRepositoryBase, IRepositoryWellRelated where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated where TEntity : class, IId, IWellRelated { diff --git a/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs index b79277cc..eb59a94d 100644 --- a/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository { #nullable enable - public class CrudWellRelatedServiceBase : CrudServiceBase, IRepositoryWellRelated + public class CrudWellRelatedServiceBase : CrudRepositoryBase, IRepositoryWellRelated where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated where TEntity : class, IId, IWellRelated { diff --git a/AsbCloudInfrastructure/Repository/FileRepository.cs b/AsbCloudInfrastructure/Repository/FileRepository.cs index deb43445..98d899cc 100644 --- a/AsbCloudInfrastructure/Repository/FileRepository.cs +++ b/AsbCloudInfrastructure/Repository/FileRepository.cs @@ -1,15 +1,10 @@ using AsbCloudApp.Data; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; -using AsbCloudApp.Services; using AsbCloudDb; using AsbCloudDb.Model; -using AsbCloudInfrastructure.Services; -using DocumentFormat.OpenXml.Drawing.Charts; -using DocumentFormat.OpenXml.Wordprocessing; using Mapster; using Microsoft.EntityFrameworkCore; -using Org.BouncyCastle.Asn1.Ocsp; using System; using System.Collections.Generic; using System.Linq; diff --git a/AsbCloudInfrastructure/Repository/QueryContainer.cs b/AsbCloudInfrastructure/Repository/QueryContainer.cs new file mode 100644 index 00000000..a1586220 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/QueryContainer.cs @@ -0,0 +1,28 @@ +using AsbCloudDb.Model; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; + +namespace AsbCloudInfrastructure.Repository +{ + public class QueryContainer where TEntity : class, IId + { + protected readonly IAsbCloudDbContext dbContext; + protected readonly DbSet dbSet; + protected readonly Func> GetQuery; + + public QueryContainer(IAsbCloudDbContext context) + { + dbContext = context; + dbSet = context.Set(); + GetQuery = () => dbSet; + } + + public QueryContainer(IAsbCloudDbContext context, Func, IQueryable> makeQuery) + { + dbContext = context; + dbSet = context.Set(); + GetQuery = () => makeQuery(dbSet); + } + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs b/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs index c9379244..325cae07 100644 --- a/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs +++ b/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs @@ -4,6 +4,10 @@ using AsbCloudDb.Model; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; +using System.Linq; namespace AsbCloudInfrastructure.Repository { @@ -18,6 +22,29 @@ namespace AsbCloudInfrastructure.Repository this.wellService = wellService; } + public virtual async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) + { + var ids = dtos.Select(d => d.Id); + var existingEntities = await dbSet + .AsNoTracking() + .Where(d => ids.Contains(d.Id)) + .Select(d => d.Id) + .ToListAsync(token) + .ConfigureAwait(false); + + if (ids.Count() > existingEntities.Count) + return ICrudRepository.ErrorIdNotFound; + + foreach (var dto in dtos) + { + var entity = Convert(dto); + var entry = dbSet.Update(entity); + } + + var affected = await dbContext.SaveChangesAsync(token); + return affected; + } + protected override SetpointsRequestDto Convert(SetpointsRequest src) { var result = base.Convert(src); diff --git a/AsbCloudInfrastructure/Services/DrillParamsService.cs b/AsbCloudInfrastructure/Services/DrillParamsService.cs index a2ac61c6..7c5f8e62 100644 --- a/AsbCloudInfrastructure/Services/DrillParamsService.cs +++ b/AsbCloudInfrastructure/Services/DrillParamsService.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { #nullable enable - public class DrillParamsService : CrudServiceBase, IDrillParamsService + public class DrillParamsService : CrudRepositoryBase, IDrillParamsService { private readonly IAsbCloudDbContext db; private readonly ITelemetryService telemetryService; diff --git a/AsbCloudInfrastructure/Services/FileCategoryService.cs b/AsbCloudInfrastructure/Services/FileCategoryService.cs index 8721ef25..e7370ee8 100644 --- a/AsbCloudInfrastructure/Services/FileCategoryService.cs +++ b/AsbCloudInfrastructure/Services/FileCategoryService.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { - public class FileCategoryService : CrudCacheServiceBase, IFileCategoryService + public class FileCategoryService : CrudCacheRepositoryBase, IFileCategoryService { public FileCategoryService(IAsbCloudDbContext context, IMemoryCache memoryCache) : base(context, memoryCache) { } diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs index ada2ab42..16a48b56 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeService.cs @@ -23,13 +23,13 @@ namespace AsbCloudInfrastructure.Services.Subsystems private readonly IAsbCloudDbContext db; private readonly IWellService wellService; - private readonly ICrudService subsystemService; + private readonly ICrudRepository subsystemService; private readonly IDetectedOperationService detectedOperationService; public const int IdSubsystemAKB = 1; public const int IdSubsystemMSE = 2; public const int IdSubsystemSpin = 65536; public const int IdSubsystemTorque = 65537; - public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudService subsystemService, IDetectedOperationService detectedOperationService) + public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudRepository subsystemService, IDetectedOperationService detectedOperationService) { this.db = db; this.wellService = wellService; diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs index 6491265a..e73e6d84 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Subsystems { #nullable enable - internal class SubsystemService : CrudCacheServiceBase, ISubsystemService + internal class SubsystemService : CrudCacheRepositoryBase, ISubsystemService { private readonly IWellService wellService; public SubsystemService(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, IWellService wellService) : base(dbContext, memoryCache) diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs index c96672f7..8967ad78 100644 --- a/AsbCloudInfrastructure/Services/WellService.cs +++ b/AsbCloudInfrastructure/Services/WellService.cs @@ -15,13 +15,13 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { - public class WellService : CrudCacheServiceBase, IWellService + public class WellService : CrudCacheRepositoryBase, IWellService { private const string relationCompaniesWellsCacheTag = "RelationCompaniesWells"; private static readonly TimeSpan relationCompaniesWellsCacheObsolence = TimeSpan.FromMinutes(15); private readonly ITelemetryService telemetryService; - private readonly ICrudService companyTypesService; + private readonly ICrudRepository companyTypesService; private readonly ITimezoneService timezoneService; private readonly IWellOperationService wellOperationService; @@ -43,7 +43,7 @@ namespace AsbCloudInfrastructure.Services this.timezoneService = timezoneService; this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this); - companyTypesService = new CrudCacheServiceBase(dbContext, memoryCache); + companyTypesService = new CrudCacheRepositoryBase(dbContext, memoryCache); } private IEnumerable GetCacheRelationCompanyWell() @@ -86,7 +86,7 @@ namespace AsbCloudInfrastructure.Services return wellsDtos.ToList(); } - public override async Task InsertAsync(WellDto dto, CancellationToken token = default) + public async Task InsertAsync(WellDto dto, CancellationToken token = default) { if (dto.IdWellType is < 1 or > 2) throw new ArgumentInvalidException("Тип скважины указан неправильно.", nameof(dto)); @@ -112,12 +112,12 @@ namespace AsbCloudInfrastructure.Services return result; } - public override Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) + public Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) { throw new NotImplementedException(); } - public override async Task UpdateAsync(WellDto dto, + public async Task UpdateAsync(WellDto dto, CancellationToken token = default) { if (dto.IdWellType is < 1 or > 2) diff --git a/AsbCloudWebApi.Tests/RepositoryFactory.cs b/AsbCloudWebApi.Tests/RepositoryFactory.cs index a8588dda..984fb494 100644 --- a/AsbCloudWebApi.Tests/RepositoryFactory.cs +++ b/AsbCloudWebApi.Tests/RepositoryFactory.cs @@ -11,7 +11,7 @@ namespace AsbCloudWebApi.Tests { public static Mock Make(ICollection data) where TDto : AsbCloudApp.Data.IId - where TRepository : class, ICrudService + where TRepository : class, ICrudRepository { var repositoryMock = new Mock(); diff --git a/AsbCloudWebApi.Tests/ServicesTests/CrudServiceTestAbstract.cs b/AsbCloudWebApi.Tests/ServicesTests/CrudServiceTestAbstract.cs index d427e038..4b9a6ebb 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/CrudServiceTestAbstract.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/CrudServiceTestAbstract.cs @@ -9,7 +9,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests public abstract class CrudServiceTestAbstract where TDto : AsbCloudApp.Data.IId { - private readonly ICrudService service; + private readonly ICrudRepository service; public CrudServiceTestAbstract() { @@ -17,7 +17,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests service = MakeService(); } - protected abstract ICrudService MakeService(); + protected abstract ICrudRepository MakeService(); protected abstract TDto MakeNewItem(); [Fact] diff --git a/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs index e95c7cc3..f907f889 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs @@ -19,10 +19,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests return item; } - protected override ICrudService MakeService() + protected override ICrudRepository MakeService() { var dbContext = TestHelpter.MakeRealTestContext(); - return new CrudCacheServiceBase(dbContext, TestHelpter.MemoryCache); + return new CrudCacheRepositoryBase(dbContext, TestHelpter.MemoryCache); } } } diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs index f829c76c..b0f0e518 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs @@ -27,11 +27,11 @@ namespace AsbCloudWebApi.Tests.ServicesTests Surname = "Тестович" } }; - private ICrudService service; + private ICrudRepository service; public DrillerServiceTest() { - var repositoryMock = RepositoryFactory.Make, DrillerDto>(Drillers); + var repositoryMock = RepositoryFactory.Make, DrillerDto>(Drillers); repositoryMock.Setup(x => x.GetAllAsync(It.IsAny())) .Returns(() => { diff --git a/AsbCloudWebApi/Controllers/AdminClusterController.cs b/AsbCloudWebApi/Controllers/AdminClusterController.cs index 2f8d927a..511b786b 100644 --- a/AsbCloudWebApi/Controllers/AdminClusterController.cs +++ b/AsbCloudWebApi/Controllers/AdminClusterController.cs @@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/cluster")] [ApiController] [Authorize] - public class AdminClusterController : CrudController> + public class AdminClusterController : CrudController> { - public AdminClusterController(ICrudService service) + public AdminClusterController(ICrudRepository service) : base(service) { } } diff --git a/AsbCloudWebApi/Controllers/AdminCompanyController.cs b/AsbCloudWebApi/Controllers/AdminCompanyController.cs index 04f6109f..f6ebb3d2 100644 --- a/AsbCloudWebApi/Controllers/AdminCompanyController.cs +++ b/AsbCloudWebApi/Controllers/AdminCompanyController.cs @@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/company")] [ApiController] [Authorize] - public class AdminCompanyController : CrudController> + public class AdminCompanyController : CrudController> { - public AdminCompanyController(ICrudService service) + public AdminCompanyController(ICrudRepository service) : base(service) { } diff --git a/AsbCloudWebApi/Controllers/AdminCompanyTypeController.cs b/AsbCloudWebApi/Controllers/AdminCompanyTypeController.cs index 60c3ea22..eb05c245 100644 --- a/AsbCloudWebApi/Controllers/AdminCompanyTypeController.cs +++ b/AsbCloudWebApi/Controllers/AdminCompanyTypeController.cs @@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/companyType")] [ApiController] [Authorize] - public class AdminCompanyTypeController : CrudController> + public class AdminCompanyTypeController : CrudController> { - public AdminCompanyTypeController(ICrudService service) + public AdminCompanyTypeController(ICrudRepository service) : base(service) { } } diff --git a/AsbCloudWebApi/Controllers/AdminDepositController.cs b/AsbCloudWebApi/Controllers/AdminDepositController.cs index c35be313..b39e4f01 100644 --- a/AsbCloudWebApi/Controllers/AdminDepositController.cs +++ b/AsbCloudWebApi/Controllers/AdminDepositController.cs @@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/deposit")] [ApiController] [Authorize] - public class AdminDepositController : CrudController> + public class AdminDepositController : CrudController> { - public AdminDepositController(ICrudService service) + public AdminDepositController(ICrudRepository service) : base(service) { } diff --git a/AsbCloudWebApi/Controllers/AdminPermissionController.cs b/AsbCloudWebApi/Controllers/AdminPermissionController.cs index ffbfe213..31d7bec6 100644 --- a/AsbCloudWebApi/Controllers/AdminPermissionController.cs +++ b/AsbCloudWebApi/Controllers/AdminPermissionController.cs @@ -15,9 +15,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/permission")] [ApiController] [Authorize] - public class AdminPermissionController : CrudController> + public class AdminPermissionController : CrudController> { - public AdminPermissionController(ICrudService service) + public AdminPermissionController(ICrudRepository service) : base(service) { } diff --git a/AsbCloudWebApi/Controllers/AdminTelemetryController.cs b/AsbCloudWebApi/Controllers/AdminTelemetryController.cs index 1daad23c..b55d623a 100644 --- a/AsbCloudWebApi/Controllers/AdminTelemetryController.cs +++ b/AsbCloudWebApi/Controllers/AdminTelemetryController.cs @@ -13,11 +13,11 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/telemetry")] [ApiController] [Authorize] - public class AdminTelemetryController : CrudController> + public class AdminTelemetryController : CrudController> { private readonly ITelemetryService telemetryService; - public AdminTelemetryController(ICrudService service, + public AdminTelemetryController(ICrudRepository service, ITelemetryService telemetryService) : base(service) { diff --git a/AsbCloudWebApi/Controllers/AdminUserController.cs b/AsbCloudWebApi/Controllers/AdminUserController.cs index 0ce262d0..288f838a 100644 --- a/AsbCloudWebApi/Controllers/AdminUserController.cs +++ b/AsbCloudWebApi/Controllers/AdminUserController.cs @@ -13,7 +13,7 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/user")] [ApiController] [Authorize] - public class AdminUserController : CrudController> + public class AdminUserController : CrudController> { public AdminUserController(IUserRepository service) : base(service) diff --git a/AsbCloudWebApi/Controllers/AdminWellController.cs b/AsbCloudWebApi/Controllers/AdminWellController.cs index 3441af43..4a670652 100644 --- a/AsbCloudWebApi/Controllers/AdminWellController.cs +++ b/AsbCloudWebApi/Controllers/AdminWellController.cs @@ -13,7 +13,7 @@ namespace AsbCloudWebApi.Controllers [Route("api/admin/well")] [ApiController] [Authorize] - public class AdminWellController : CrudController> + public class AdminWellController : CrudController> { public AdminWellController(IWellService service) : base(service) diff --git a/AsbCloudWebApi/Controllers/CrudController.cs b/AsbCloudWebApi/Controllers/CrudController.cs index db66e468..4b0a6c19 100644 --- a/AsbCloudWebApi/Controllers/CrudController.cs +++ b/AsbCloudWebApi/Controllers/CrudController.cs @@ -21,7 +21,7 @@ namespace AsbCloudWebApi.Controllers [Authorize] public abstract class CrudController : ControllerBase where T : IId - where TService : ICrudService + where TService : ICrudRepository { protected readonly TService service; @@ -115,7 +115,7 @@ namespace AsbCloudWebApi.Controllers return Forbid(); var result = await service.UpdateAsync(value, token).ConfigureAwait(false); - if (result == ICrudService.ErrorIdNotFound) + if (result == ICrudRepository.ErrorIdNotFound) return BadRequest($"id:{value.Id} does not exist in the db"); return Ok(result); } @@ -134,7 +134,7 @@ namespace AsbCloudWebApi.Controllers return Forbid(); var result = await service.DeleteAsync(id, token).ConfigureAwait(false); - if (result == ICrudService.ErrorIdNotFound) + if (result == ICrudRepository.ErrorIdNotFound) return NoContent(); return Ok(result); } diff --git a/AsbCloudWebApi/Controllers/DrillerController.cs b/AsbCloudWebApi/Controllers/DrillerController.cs index 2175c1ce..0fa84e4a 100644 --- a/AsbCloudWebApi/Controllers/DrillerController.cs +++ b/AsbCloudWebApi/Controllers/DrillerController.cs @@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/driller")] [ApiController] [Authorize] - public class DrillerController : CrudController> + public class DrillerController : CrudController> { - public DrillerController(ICrudService service) + public DrillerController(ICrudRepository service) : base(service) { } } diff --git a/AsbCloudWebApi/Controllers/FileCategoryController.cs b/AsbCloudWebApi/Controllers/FileCategoryController.cs index 5567c5e7..2a19cbe7 100644 --- a/AsbCloudWebApi/Controllers/FileCategoryController.cs +++ b/AsbCloudWebApi/Controllers/FileCategoryController.cs @@ -11,9 +11,9 @@ namespace AsbCloudWebApi.Controllers [Route("api/[Controller]")] [ApiController] [Authorize] - public class FileCategoryController : CrudController> + public class FileCategoryController : CrudController> { - public FileCategoryController(ICrudService service, IFileCategoryService fileCategoryService) + public FileCategoryController(ICrudRepository service, IFileCategoryService fileCategoryService) : base(service) { } diff --git a/AsbCloudWebApi/Controllers/Subsystems/AdminSubsystemController.cs b/AsbCloudWebApi/Controllers/Subsystems/AdminSubsystemController.cs index dc7ac408..454062e1 100644 --- a/AsbCloudWebApi/Controllers/Subsystems/AdminSubsystemController.cs +++ b/AsbCloudWebApi/Controllers/Subsystems/AdminSubsystemController.cs @@ -12,9 +12,9 @@ namespace AsbCloudWebApi.Controllers.Subsystems [Route("api/admin/subsystem")] [ApiController] [Authorize] - public class AdminSubsystemController : CrudController> + public class AdminSubsystemController : CrudController> { - public AdminSubsystemController(ICrudService service) + public AdminSubsystemController(ICrudRepository service) : base(service) { } From f61db91dd25787f5379f5a1fd3dc81605e61d357 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Thu, 1 Dec 2022 17:48:35 +0500 Subject: [PATCH 02/14] Add new background service. --- .../Background/BackgroundWorkerService.cs | 247 ++++++++++++++++++ .../BackgroundWorkerServiceTest.cs | 120 +++++++++ .../BackgroundWorkerService_WorkQueue_Test.cs | 109 ++++++++ 3 files changed, 476 insertions(+) create mode 100644 AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs create mode 100644 AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs create mode 100644 AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs diff --git a/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs new file mode 100644 index 00000000..4f940cfd --- /dev/null +++ b/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs @@ -0,0 +1,247 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.Background +{ +# nullable enable + public class BackgroundWorkerService : BackgroundService + { + private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); + private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); + private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2); + private readonly IServiceProvider serviceProvider; + private readonly WorkQueue workQueue = new WorkQueue(); + + public BackgroundWorkerService(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + /// + /// Добавление задачи в очередь. + /// Не периодические задачи будут выполняться вперед. + /// + /// + /// Id mast be unique + public void Push(WorkBase work) + { + workQueue.Push(work); + } + + protected override async Task ExecuteAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + var dateStart = DateTime.Now; + var work = workQueue.Pop(); + if (work is null) + { + await Task.Delay(executePeriod, token); + continue; + } + + using (IServiceScope scope = serviceProvider.CreateScope()) + { + try + { + var task = work.ActionAsync(work.Id, scope.ServiceProvider, token); + await task.WaitAsync(work.Timeout, token); + + work.ExecutionTime = DateTime.Now - dateStart; + Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}"); + } + catch (Exception exception) + { + Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}"); + if (work.OnErrorAsync is not null) + { + using var task = Task.Run( + async () => await work.OnErrorAsync(work.Id, exception, token), + token); + await task.WaitAsync(exceptionHandleTimeout, token); + } + } + } + + await Task.Delay(minDelay, token); + } + } + } + + /// + /// + /// Очередь работ + /// + /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. + /// + public class WorkQueue + { + private Queue Primary = new (8); + private readonly List Periodic = new (8); + internal TimeSpan MaxTimeToNextWork { get; set; } = TimeSpan.FromSeconds(20); + + /// + /// Добавление работы. + /// + /// + /// Id mast be unique + public void Push(WorkBase work) + { + if (Periodic.Any(w => w.Id == work.Id)) + throw new ArgumentException("work.Id is not unique", nameof(work)); + + if (Primary.Any(w => w.Id == work.Id)) + throw new ArgumentException("work.Id is not unique", nameof(work)); + + if (work is WorkPeriodic workPeriodic) + { + Periodic.Add(workPeriodic); + return; + } + + Primary.Enqueue(work); + } + + /// + /// Удаление работы по ID + /// + /// + /// + public bool Delete(string id) + { + var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); + if(workPeriodic is not null) + { + Periodic.Remove(workPeriodic); + return true; + } + + var work = Primary.FirstOrDefault(w => w.Id == id); + if (work is not null) + { + Primary = new Queue(Primary.Where(w => w.Id != id)); + return true; + } + + return false; + } + + /// + /// + /// Возвращает приоритетную задачу. + /// + /// + /// Если приоритетные закончились, то ищет ближайшую периодическую. + /// Если до старта ближайшей периодической работы меньше 20 сек, + /// то этой задаче устанавливается время последнего запуска в now и она возвращается. + /// Если больше 20 сек, то возвращается null. + /// + /// + /// + /// + public WorkBase? Pop(TimeSpan? maxTimeToNextWork = null) + { + if (Primary.Any()) + return Primary.Dequeue(); + + var maxTimeToNextWorkLocal = maxTimeToNextWork ?? MaxTimeToNextWork; + var work = GetNextPeriodic(); + if (work is null || work.NextStart - DateTime.Now > maxTimeToNextWorkLocal) + return null; + + work.LastStart = DateTime.Now; + return work; + } + + private WorkPeriodic? GetNextPeriodic() + { + var work = Periodic + .OrderBy(w => w.NextStart) + .FirstOrDefault(); + return work; + } + } + + public class WorkBase + { + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. + /// + public string Id { get; private set; } + + /// + /// Делегат работы. + /// + /// Параметры: + /// + /// + /// string + /// Id Идентификатор работы + /// + /// + /// IServiceProvider + /// Поставщик сервисов + /// + /// + /// CancellationToken + /// Токен отмены задачи + /// + /// + /// + /// + public Func ActionAsync { get; set; } + + /// + /// Делегат обработки ошибки. + /// Не должен выполняться долго. + /// + public Func? OnErrorAsync { get; set; } + + /// + /// максимально допустимое время выполнения работы + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); + + /// + /// Фактическое время успешного выполнения работы + /// + public TimeSpan? ExecutionTime { get; set; } + + /// + /// Время последнего запуска + /// + public DateTime LastStart { get; set; } + + public WorkBase(string id, Func actionAsync) + { + Id = id; + ActionAsync = actionAsync; + } + } + + public class WorkPeriodic : WorkBase + { + /// + /// Период выполнения задачи + /// + public TimeSpan Period { get; set; } + + /// + /// Время следующего запуска + /// + public DateTime NextStart => LastStart + Period; + + public WorkPeriodic(string id, Func actionAsync, TimeSpan period) + :base(id, actionAsync) + { + Period = period; + } + } +#nullable disable +} diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs new file mode 100644 index 00000000..3aad83ad --- /dev/null +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs @@ -0,0 +1,120 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using System; +using AsbCloudInfrastructure.Services.Background; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Org.BouncyCastle.Asn1.X509.Qualified; + +namespace AsbCloudWebApi.Tests.ServicesTests +{ + public class BackgroundWorkerServiceTest + { + private readonly Mock mockServiceProvider; + private readonly Mock mockServiceScopeFactory; + + public BackgroundWorkerServiceTest() + { + var mockServiceScope = new Mock(); + mockServiceScopeFactory = new Mock(); + mockServiceProvider = new Mock(); + + mockServiceScope.SetReturnsDefault(mockServiceProvider.Object); + mockServiceProvider.SetReturnsDefault(mockServiceScopeFactory.Object); + mockServiceProvider.Setup(s=>s.GetService(It.IsAny())) + .Returns(mockServiceScopeFactory.Object); + mockServiceScopeFactory.SetReturnsDefault(mockServiceScope.Object); + } + + [Fact] + public async Task Push_makes_new_scope_after_start() + { + mockServiceScopeFactory.Invocations.Clear(); + + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var work = new WorkBase("", (_, _, _) => Task.CompletedTask ); + backgroundService.Push(work); + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(10); + + mockServiceScopeFactory.Verify(f => f.CreateScope()); + } + + [Fact] + public async Task Push_makes_primary_work_done() + { + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var workDone = false; + var work = new WorkBase("", (_, _, _) => + { + workDone = true; + return Task.CompletedTask; + }); + backgroundService.Push(work); + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(10); + + Assert.True(workDone); + } + + [Fact] + public async Task Push_makes_pperiodic_work_done() + { + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var workDone = false; + var work = new WorkPeriodic("", (_, _, _) => + { + workDone = true; + return Task.CompletedTask; + }, + TimeSpan.FromMilliseconds(10)); + backgroundService.Push(work); + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(10); + + Assert.True(workDone); + } + + [Fact] + public async Task Aborts_long_work() + { + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var workCanceled = false; + var work = new WorkBase("", async(_, _, _) => await Task.Delay(1000000)); + work.Timeout = TimeSpan.FromMilliseconds(1); + work.OnErrorAsync = async (id, ex, token) => + { + workCanceled = ex is System.TimeoutException; + await Task.CompletedTask; + }; + + backgroundService.Push(work); + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(20*4); + + Assert.True(workCanceled); + } + + [Fact] + public async Task Execution_continues_after_work_exception() + { + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var work2done = false; + var work1 = new WorkBase("1", (_, _, _) => throw new Exception()); + var work2 = new WorkBase("2", (_, _, _) => + { + work2done = true; + return Task.CompletedTask; + }); + + backgroundService.Push(work1); + backgroundService.Push(work2); + + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(2_100); + + Assert.True(work2done); + } + } +} diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs new file mode 100644 index 00000000..1807e24a --- /dev/null +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs @@ -0,0 +1,109 @@ +using AsbCloudInfrastructure.Services.Background; +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AsbCloudWebApi.Tests.ServicesTests +{ + public class BackgroundWorkerService_WorkQueue_Test + { + private readonly TimeSpan period = TimeSpan.FromSeconds(10); + private readonly Func somAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask; + + [Fact] + public void Push_not_unique_id_should_throw() + { + var work1 = new WorkPeriodic("1", somAction, TimeSpan.FromSeconds(30)); + var work2 = new WorkBase("1", somAction); + + var queue = new WorkQueue(); + queue.Push(work1); + + Assert.Throws( + () => queue.Push(work2)); + } + + [Fact] + public void Pop_should_return_null() + { + var work1 = new WorkPeriodic("1", somAction, TimeSpan.FromSeconds(30)) + { LastStart = DateTime.Now }; + + var queue = new WorkQueue(); + queue.Push(work1); + var workpoPoped= queue.Pop(); + + Assert.Null(workpoPoped); + } + + [Fact] + public void Pop_primary_first() + { + var work1 = new WorkBase("1", somAction); + var work2 = new WorkPeriodic("1", somAction, period); + + var queue = new WorkQueue(); + queue.Push(work2); + queue.Push(work1); + var workpoPoped= queue.Pop(); + + Assert.Equal(work1, workpoPoped); + } + + [Fact] + public void Pop_second_after_delete_first() + { + var work1 = new WorkPeriodic("1", somAction, period); + var work2 = new WorkPeriodic("2", somAction, period); + + var queue = new WorkQueue(); + queue.Push(work1); + queue.Push(work2); + queue.Delete("1"); + + var workpoPoped= queue.Pop(); + + Assert.Equal(work2, workpoPoped); + } + + [Fact] + public void Pop_closest_to_nextStart() + { + var work1 = new WorkPeriodic("1", somAction, period) { + LastStart = DateTime.Now, + }; + var work2 = new WorkPeriodic("2", somAction, period); + + var queue = new WorkQueue(); + queue.Push(work1); + queue.Push(work2); + + var workpoPoped= queue.Pop(); + + Assert.Equal(work2, workpoPoped); + } + + [Fact] + public void Pop_closest_to_explicit_nextStart() + { + var baseTime = DateTime.Now - period; + var work1 = new WorkPeriodic("1", somAction, period) + { + LastStart = baseTime - TimeSpan.FromSeconds(-1), + }; + var work2 = new WorkPeriodic("2", somAction, period) + { + LastStart = baseTime, + }; + + var queue = new WorkQueue(); + queue.Push(work1); + queue.Push(work2); + + var workpoPoped= queue.Pop(); + + Assert.Equal(work2, workpoPoped); + } + } +} From 06fe0e09ff1664d5dba8e84af556920a9af73e17 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 10:57:27 +0500 Subject: [PATCH 03/14] BackgroundWorkerService cleanup and improve tests --- .../Background/BackgroundWorkerService.cs | 185 ++---------------- .../Services/Background/WorkBase.cs | 69 +++++++ .../Services/Background/WorkPeriodic.cs | 36 ++++ .../Services/Background/WorkQueue.cs | 101 ++++++++++ .../BackgroundWorkerServiceTest.cs | 116 ++++++++++- .../BackgroundWorkerService_WorkQueue_Test.cs | 109 ----------- 6 files changed, 327 insertions(+), 289 deletions(-) create mode 100644 AsbCloudInfrastructure/Services/Background/WorkBase.cs create mode 100644 AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs create mode 100644 AsbCloudInfrastructure/Services/Background/WorkQueue.cs delete mode 100644 AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs diff --git a/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs index 4f940cfd..52bcbf6b 100644 --- a/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs +++ b/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs @@ -1,15 +1,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Background { # nullable enable + /// + /// Сервис для фонового выполнения работы + /// public class BackgroundWorkerService : BackgroundService { private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); @@ -34,6 +35,16 @@ namespace AsbCloudInfrastructure.Services.Background workQueue.Push(work); } + /// + /// Удаление работы по ID + /// + /// + /// + public bool Delete(string id) + { + return workQueue.Delete(id); + } + protected override async Task ExecuteAsync(CancellationToken token) { while (!token.IsCancellationRequested) @@ -73,175 +84,5 @@ namespace AsbCloudInfrastructure.Services.Background } } } - - /// - /// - /// Очередь работ - /// - /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. - /// - public class WorkQueue - { - private Queue Primary = new (8); - private readonly List Periodic = new (8); - internal TimeSpan MaxTimeToNextWork { get; set; } = TimeSpan.FromSeconds(20); - - /// - /// Добавление работы. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) - { - if (Periodic.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (Primary.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (work is WorkPeriodic workPeriodic) - { - Periodic.Add(workPeriodic); - return; - } - - Primary.Enqueue(work); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); - if(workPeriodic is not null) - { - Periodic.Remove(workPeriodic); - return true; - } - - var work = Primary.FirstOrDefault(w => w.Id == id); - if (work is not null) - { - Primary = new Queue(Primary.Where(w => w.Id != id)); - return true; - } - - return false; - } - - /// - /// - /// Возвращает приоритетную задачу. - /// - /// - /// Если приоритетные закончились, то ищет ближайшую периодическую. - /// Если до старта ближайшей периодической работы меньше 20 сек, - /// то этой задаче устанавливается время последнего запуска в now и она возвращается. - /// Если больше 20 сек, то возвращается null. - /// - /// - /// - /// - public WorkBase? Pop(TimeSpan? maxTimeToNextWork = null) - { - if (Primary.Any()) - return Primary.Dequeue(); - - var maxTimeToNextWorkLocal = maxTimeToNextWork ?? MaxTimeToNextWork; - var work = GetNextPeriodic(); - if (work is null || work.NextStart - DateTime.Now > maxTimeToNextWorkLocal) - return null; - - work.LastStart = DateTime.Now; - return work; - } - - private WorkPeriodic? GetNextPeriodic() - { - var work = Periodic - .OrderBy(w => w.NextStart) - .FirstOrDefault(); - return work; - } - } - - public class WorkBase - { - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. - /// - public string Id { get; private set; } - - /// - /// Делегат работы. - /// - /// Параметры: - /// - /// - /// string - /// Id Идентификатор работы - /// - /// - /// IServiceProvider - /// Поставщик сервисов - /// - /// - /// CancellationToken - /// Токен отмены задачи - /// - /// - /// - /// - public Func ActionAsync { get; set; } - - /// - /// Делегат обработки ошибки. - /// Не должен выполняться долго. - /// - public Func? OnErrorAsync { get; set; } - - /// - /// максимально допустимое время выполнения работы - /// - public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Фактическое время успешного выполнения работы - /// - public TimeSpan? ExecutionTime { get; set; } - - /// - /// Время последнего запуска - /// - public DateTime LastStart { get; set; } - - public WorkBase(string id, Func actionAsync) - { - Id = id; - ActionAsync = actionAsync; - } - } - - public class WorkPeriodic : WorkBase - { - /// - /// Период выполнения задачи - /// - public TimeSpan Period { get; set; } - - /// - /// Время следующего запуска - /// - public DateTime NextStart => LastStart + Period; - - public WorkPeriodic(string id, Func actionAsync, TimeSpan period) - :base(id, actionAsync) - { - Period = period; - } - } #nullable disable } diff --git a/AsbCloudInfrastructure/Services/Background/WorkBase.cs b/AsbCloudInfrastructure/Services/Background/WorkBase.cs new file mode 100644 index 00000000..7f305bea --- /dev/null +++ b/AsbCloudInfrastructure/Services/Background/WorkBase.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.Background +{ +#nullable enable + /// + /// Класс разовой работы. + /// Разовая работа приоритетнее периодической. + /// + public class WorkBase + { + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. + /// + public string Id { get; private set; } + + /// + /// Делегат работы. + /// + /// Параметры: + /// + /// + /// string + /// Id Идентификатор работы + /// + /// + /// IServiceProvider + /// Поставщик сервисов + /// + /// + /// CancellationToken + /// Токен отмены задачи + /// + /// + /// + /// + public Func ActionAsync { get; set; } + + /// + /// Делегат обработки ошибки. + /// Не должен выполняться долго. + /// + public Func? OnErrorAsync { get; set; } + + /// + /// максимально допустимое время выполнения работы + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); + + /// + /// Фактическое время успешного выполнения работы + /// + public TimeSpan? ExecutionTime { get; set; } + + /// + /// Время последнего запуска + /// + public DateTime LastStart { get; set; } + + public WorkBase(string id, Func actionAsync) + { + Id = id; + ActionAsync = actionAsync; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs new file mode 100644 index 00000000..288a37ca --- /dev/null +++ b/AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Services.Background +{ +#nullable enable + /// + /// Класс периодической работы. + /// + public class WorkPeriodic : WorkBase + { + /// + /// Период выполнения задачи + /// + public TimeSpan Period { get; set; } + + /// + /// Время следующего запуска + /// + public DateTime NextStart => LastStart + Period; + + /// + /// Класс периодической работы + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки + /// Делегат работы + /// Период выполнения задачи + public WorkPeriodic(string id, Func actionAsync, TimeSpan period) + :base(id, actionAsync) + { + Period = period; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Services/Background/WorkQueue.cs b/AsbCloudInfrastructure/Services/Background/WorkQueue.cs new file mode 100644 index 00000000..90ce7ec7 --- /dev/null +++ b/AsbCloudInfrastructure/Services/Background/WorkQueue.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudInfrastructure.Services.Background +{ +#nullable enable + /// + /// + /// Очередь работ + /// + /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. + /// + class WorkQueue + { + private Queue Primary = new (8); + private readonly List Periodic = new (8); + + /// + /// Добавление работы. + /// + /// + /// Id mast be unique + public void Push(WorkBase work) + { + if (Periodic.Any(w => w.Id == work.Id)) + throw new ArgumentException("work.Id is not unique", nameof(work)); + + if (Primary.Any(w => w.Id == work.Id)) + throw new ArgumentException("work.Id is not unique", nameof(work)); + + if (work is WorkPeriodic workPeriodic) + { + Periodic.Add(workPeriodic); + return; + } + + Primary.Enqueue(work); + } + + /// + /// Удаление работы по ID + /// + /// + /// + public bool Delete(string id) + { + var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); + if(workPeriodic is not null) + { + Periodic.Remove(workPeriodic); + return true; + } + + var work = Primary.FirstOrDefault(w => w.Id == id); + if (work is not null) + { + Primary = new Queue(Primary.Where(w => w.Id != id)); + return true; + } + + return false; + } + + /// + /// + /// Возвращает приоритетную задачу. + /// + /// + /// Если приоритетные закончились, то ищет ближайшую периодическую. + /// Если до старта ближайшей периодической работы меньше 20 сек, + /// то этой задаче устанавливается время последнего запуска в now и она возвращается. + /// Если больше 20 сек, то возвращается null. + /// + /// + /// + /// + public WorkBase? Pop() + { + if (Primary.Any()) + return Primary.Dequeue(); + + var work = GetNextPeriodic(); + if (work is null || work.NextStart > DateTime.Now) + return null; + + work.LastStart = DateTime.Now; + return work; + } + + private WorkPeriodic? GetNextPeriodic() + { + var work = Periodic + .OrderBy(w => w.NextStart) + .ThenByDescending(w => w.Period) + .FirstOrDefault(); + return work; + } + } +#nullable disable +} diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs index 3aad83ad..a2feb973 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs @@ -5,7 +5,6 @@ using AsbCloudInfrastructure.Services.Background; using System.Threading; using System.Threading.Tasks; using Xunit; -using Org.BouncyCastle.Asn1.X509.Qualified; namespace AsbCloudWebApi.Tests.ServicesTests { @@ -13,6 +12,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests { private readonly Mock mockServiceProvider; private readonly Mock mockServiceScopeFactory; + private readonly TimeSpan period = TimeSpan.FromSeconds(10); + private readonly Func someAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask; public BackgroundWorkerServiceTest() { @@ -22,7 +23,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests mockServiceScope.SetReturnsDefault(mockServiceProvider.Object); mockServiceProvider.SetReturnsDefault(mockServiceScopeFactory.Object); - mockServiceProvider.Setup(s=>s.GetService(It.IsAny())) + mockServiceProvider.Setup(s => s.GetService(It.IsAny())) .Returns(mockServiceScopeFactory.Object); mockServiceScopeFactory.SetReturnsDefault(mockServiceScope.Object); } @@ -32,8 +33,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests { mockServiceScopeFactory.Invocations.Clear(); - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); - var work = new WorkBase("", (_, _, _) => Task.CompletedTask ); + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var work = new WorkBase("", (_, _, _) => Task.CompletedTask); backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); @@ -42,7 +43,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests } [Fact] - public async Task Push_makes_primary_work_done() + public async Task Makes_primary_work_done() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workDone = false; @@ -59,7 +60,19 @@ namespace AsbCloudWebApi.Tests.ServicesTests } [Fact] - public async Task Push_makes_pperiodic_work_done() + public async Task Sets_ExecutionTime_after_work_done() + { + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var work = new WorkBase("", someAction); + backgroundService.Push(work); + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(10); + + Assert.True(work.ExecutionTime > TimeSpan.Zero); + } + + [Fact] + public async Task Makes_periodic_work_done() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workDone = false; @@ -70,6 +83,80 @@ namespace AsbCloudWebApi.Tests.ServicesTests }, TimeSpan.FromMilliseconds(10)); backgroundService.Push(work); + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(20); + + Assert.True(workDone); + } + + [Fact] + public async Task Does_not_start_periodic_work() + { + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var workDone = false; + var work = new WorkPeriodic("", (_, _, _) => + { + workDone = true; + return Task.CompletedTask; + }, + TimeSpan.FromSeconds(30)); + work.LastStart = DateTime.Now; + backgroundService.Push(work); + + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(20); + + Assert.False(workDone); + } + + [Fact] + public async Task Follows_work_priority() + { + var order = 0; + var work1Order = -1; + var work2Order = -1; + + var work1 = new WorkPeriodic("1", (_, _, _) => + { + work1Order = order++; + return Task.CompletedTask; + }, + TimeSpan.FromMilliseconds(1) + ); + + var work2 = new WorkBase("2", (_, _, _) => + { + work2Order = order++; + return Task.CompletedTask; + }); + + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + backgroundService.Push(work2); + backgroundService.Push(work1); + + await backgroundService.StartAsync(CancellationToken.None); + await Task.Delay(2_100); + + Assert.True(work2Order < work1Order); + } + + [Fact] + public async Task Runs_second_after_delete_first() + { + var workDone = false; + + var work1 = new WorkBase("1", someAction); + var work2 = new WorkPeriodic("2", (_, _, _) => + { + workDone = true; + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(1)); + + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + backgroundService.Push(work1); + backgroundService.Push(work2); + backgroundService.Delete("1"); + await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); @@ -81,7 +168,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workCanceled = false; - var work = new WorkBase("", async(_, _, _) => await Task.Delay(1000000)); + var work = new WorkBase("", async (_, _, _) => await Task.Delay(1000000)); work.Timeout = TimeSpan.FromMilliseconds(1); work.OnErrorAsync = async (id, ex, token) => { @@ -91,7 +178,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); - await Task.Delay(20*4); + await Task.Delay(20 * 4); Assert.True(workCanceled); } @@ -116,5 +203,18 @@ namespace AsbCloudWebApi.Tests.ServicesTests Assert.True(work2done); } + + [Fact] + public void Push_not_unique_id_should_throw() + { + var work1 = new WorkPeriodic("1", someAction, TimeSpan.FromSeconds(30)); + var work2 = new WorkBase("1", someAction); + + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + backgroundService.Push(work1); + + Assert.Throws( + () => backgroundService.Push(work2)); + } } } diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs deleted file mode 100644 index 1807e24a..00000000 --- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerService_WorkQueue_Test.cs +++ /dev/null @@ -1,109 +0,0 @@ -using AsbCloudInfrastructure.Services.Background; -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace AsbCloudWebApi.Tests.ServicesTests -{ - public class BackgroundWorkerService_WorkQueue_Test - { - private readonly TimeSpan period = TimeSpan.FromSeconds(10); - private readonly Func somAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask; - - [Fact] - public void Push_not_unique_id_should_throw() - { - var work1 = new WorkPeriodic("1", somAction, TimeSpan.FromSeconds(30)); - var work2 = new WorkBase("1", somAction); - - var queue = new WorkQueue(); - queue.Push(work1); - - Assert.Throws( - () => queue.Push(work2)); - } - - [Fact] - public void Pop_should_return_null() - { - var work1 = new WorkPeriodic("1", somAction, TimeSpan.FromSeconds(30)) - { LastStart = DateTime.Now }; - - var queue = new WorkQueue(); - queue.Push(work1); - var workpoPoped= queue.Pop(); - - Assert.Null(workpoPoped); - } - - [Fact] - public void Pop_primary_first() - { - var work1 = new WorkBase("1", somAction); - var work2 = new WorkPeriodic("1", somAction, period); - - var queue = new WorkQueue(); - queue.Push(work2); - queue.Push(work1); - var workpoPoped= queue.Pop(); - - Assert.Equal(work1, workpoPoped); - } - - [Fact] - public void Pop_second_after_delete_first() - { - var work1 = new WorkPeriodic("1", somAction, period); - var work2 = new WorkPeriodic("2", somAction, period); - - var queue = new WorkQueue(); - queue.Push(work1); - queue.Push(work2); - queue.Delete("1"); - - var workpoPoped= queue.Pop(); - - Assert.Equal(work2, workpoPoped); - } - - [Fact] - public void Pop_closest_to_nextStart() - { - var work1 = new WorkPeriodic("1", somAction, period) { - LastStart = DateTime.Now, - }; - var work2 = new WorkPeriodic("2", somAction, period); - - var queue = new WorkQueue(); - queue.Push(work1); - queue.Push(work2); - - var workpoPoped= queue.Pop(); - - Assert.Equal(work2, workpoPoped); - } - - [Fact] - public void Pop_closest_to_explicit_nextStart() - { - var baseTime = DateTime.Now - period; - var work1 = new WorkPeriodic("1", somAction, period) - { - LastStart = baseTime - TimeSpan.FromSeconds(-1), - }; - var work2 = new WorkPeriodic("2", somAction, period) - { - LastStart = baseTime, - }; - - var queue = new WorkQueue(); - queue.Push(work1); - queue.Push(work2); - - var workpoPoped= queue.Pop(); - - Assert.Equal(work2, workpoPoped); - } - } -} From c6a1c4dae6c8b0333df22f0b6fb2e0891ac75be7 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 10:58:19 +0500 Subject: [PATCH 04/14] AsbCloudDbContext Add active reference count prop --- AsbCloudDb/Model/AsbCloudDbContext.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/AsbCloudDb/Model/AsbCloudDbContext.cs b/AsbCloudDb/Model/AsbCloudDbContext.cs index 4e91eafe..97df2748 100644 --- a/AsbCloudDb/Model/AsbCloudDbContext.cs +++ b/AsbCloudDb/Model/AsbCloudDbContext.cs @@ -60,20 +60,32 @@ namespace AsbCloudDb.Model public DbSet Record50 => Set(); public DbSet Record60 => Set(); public DbSet Record61 => Set(); - + + public int ReferenceCount { get; private set; } + public AsbCloudDbContext() : base() { + ReferenceCount++; } public AsbCloudDbContext(DbContextOptions options) : base(options) { + ReferenceCount++; + } + + ~AsbCloudDbContext() + { + ReferenceCount--; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { + if (!optionsBuilder.IsConfigured) - optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"); + optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True" + //, builder=>builder.EnableRetryOnFailure(2, System.TimeSpan.FromMinutes(1)) + ); } protected override void OnModelCreating(ModelBuilder modelBuilder) From 98c7599c4b672dcf05b827acc7e2f4e0d0b43618 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 14:45:20 +0500 Subject: [PATCH 05/14] BackgroundWorker move to toplevel folder --- .../Background/BackgroundWorkerService.cs | 100 ++++++++++++++++ AsbCloudInfrastructure/Background/WorkBase.cs | 69 +++++++++++ .../Background/WorkPeriodic.cs | 36 ++++++ .../Background/WorkQueue.cs | 107 ++++++++++++++++++ .../BackgroundWorkerServiceTest.cs | 42 +++++-- 5 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 AsbCloudInfrastructure/Background/BackgroundWorkerService.cs create mode 100644 AsbCloudInfrastructure/Background/WorkBase.cs create mode 100644 AsbCloudInfrastructure/Background/WorkPeriodic.cs create mode 100644 AsbCloudInfrastructure/Background/WorkQueue.cs diff --git a/AsbCloudInfrastructure/Background/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Background/BackgroundWorkerService.cs new file mode 100644 index 00000000..e823284d --- /dev/null +++ b/AsbCloudInfrastructure/Background/BackgroundWorkerService.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Background +{ +# nullable enable + /// + /// Сервис для фонового выполнения работы + /// + public class BackgroundWorker : BackgroundService + { + private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); + private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); + private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2); + private readonly IServiceProvider serviceProvider; + private readonly WorkQueue workQueue = new WorkQueue(); + + public BackgroundWorker(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; +#warning move StartAsync(CancellationToken.None).Wait() to THE factory + Task.Delay(1_000) + .ContinueWith(_=> StartAsync(CancellationToken.None).Wait()); + } + + /// + /// Добавление задачи в очередь. + /// Не периодические задачи будут выполняться вперед. + /// + /// + /// Id mast be unique + public void Push(WorkBase work) + { + workQueue.Push(work); + } + + /// + /// Проверяет наличие работы с указанным Id + /// + /// + /// + public bool Contains(string id) + { + return workQueue.Contains(id); + } + + /// + /// Удаление работы по ID + /// + /// + /// + public bool Delete(string id) + { + return workQueue.Delete(id); + } + + protected override async Task ExecuteAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + var dateStart = DateTime.Now; + var work = workQueue.Pop(); + if (work is null) + { + await Task.Delay(executePeriod, token); + continue; + } + + using var scope = serviceProvider.CreateScope(); + + try + { + var task = work.ActionAsync(work.Id, scope.ServiceProvider, token); + await task.WaitAsync(work.Timeout, token); + + work.ExecutionTime = DateTime.Now - dateStart; + Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}"); + } + catch (Exception exception) + { + Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}"); + if (work.OnErrorAsync is not null) + { + using var task = Task.Run( + async () => await work.OnErrorAsync(work.Id, exception, token), + token); + await task.WaitAsync(exceptionHandleTimeout, token); + } + } + + await Task.Delay(minDelay, token); + } + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Background/WorkBase.cs b/AsbCloudInfrastructure/Background/WorkBase.cs new file mode 100644 index 00000000..ce07a2fa --- /dev/null +++ b/AsbCloudInfrastructure/Background/WorkBase.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Background +{ +#nullable enable + /// + /// Класс разовой работы. + /// Разовая работа приоритетнее периодической. + /// + public class WorkBase + { + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. + /// + public string Id { get; private set; } + + /// + /// Делегат работы. + /// + /// Параметры: + /// + /// + /// string + /// Id Идентификатор работы + /// + /// + /// IServiceProvider + /// Поставщик сервисов + /// + /// + /// CancellationToken + /// Токен отмены задачи + /// + /// + /// + /// + internal Func ActionAsync { get; set; } + + /// + /// Делегат обработки ошибки. + /// Не должен выполняться долго. + /// + public Func? OnErrorAsync { get; set; } + + /// + /// максимально допустимое время выполнения работы + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); + + /// + /// Фактическое время успешного выполнения работы + /// + public TimeSpan? ExecutionTime { get; internal set; } + + /// + /// Время последнего запуска + /// + public DateTime LastStart { get; set; } + + public WorkBase(string id, Func actionAsync) + { + Id = id; + ActionAsync = actionAsync; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Background/WorkPeriodic.cs new file mode 100644 index 00000000..ae29ee78 --- /dev/null +++ b/AsbCloudInfrastructure/Background/WorkPeriodic.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AsbCloudInfrastructure.Background +{ +#nullable enable + /// + /// Класс периодической работы. + /// + public class WorkPeriodic : WorkBase + { + /// + /// Период выполнения задачи + /// + public TimeSpan Period { get; set; } + + /// + /// Время следующего запуска + /// + public DateTime NextStart => LastStart + Period; + + /// + /// Класс периодической работы + /// + /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки + /// Делегат работы + /// Период выполнения задачи + public WorkPeriodic(string id, Func actionAsync, TimeSpan period) + : base(id, actionAsync) + { + Period = period; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Background/WorkQueue.cs b/AsbCloudInfrastructure/Background/WorkQueue.cs new file mode 100644 index 00000000..5521d373 --- /dev/null +++ b/AsbCloudInfrastructure/Background/WorkQueue.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudInfrastructure.Background +{ +#nullable enable + /// + /// + /// Очередь работ + /// + /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. + /// + class WorkQueue + { + private Queue Primary = new(8); + private readonly List Periodic = new(8); + + /// + /// Добавление работы. + /// + /// + /// Id mast be unique + public void Push(WorkBase work) + { + if (Periodic.Any(w => w.Id == work.Id)) + throw new ArgumentException("work.Id is not unique", nameof(work)); + + if (Primary.Any(w => w.Id == work.Id)) + throw new ArgumentException("work.Id is not unique", nameof(work)); + + if (work is WorkPeriodic workPeriodic) + { + Periodic.Add(workPeriodic); + return; + } + + Primary.Enqueue(work); + } + + /// + /// Удаление работы по ID + /// + /// + /// + public bool Delete(string id) + { + var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); + if (workPeriodic is not null) + { + Periodic.Remove(workPeriodic); + return true; + } + + var work = Primary.FirstOrDefault(w => w.Id == id); + if (work is not null) + { + Primary = new Queue(Primary.Where(w => w.Id != id)); + return true; + } + + return false; + } + + public bool Contains(string id) + { + var result = Periodic.Any(w => w.Id == id) || Primary.Any(w => w.Id == id); + return result; + } + + /// + /// + /// Возвращает приоритетную задачу. + /// + /// + /// Если приоритетные закончились, то ищет ближайшую периодическую. + /// Если до старта ближайшей периодической работы меньше 20 сек, + /// то этой задаче устанавливается время последнего запуска в now и она возвращается. + /// Если больше 20 сек, то возвращается null. + /// + /// + /// + /// + public WorkBase? Pop() + { + if (Primary.Any()) + return Primary.Dequeue(); + + var work = GetNextPeriodic(); + if (work is null || work.NextStart > DateTime.Now) + return null; + + work.LastStart = DateTime.Now; + return work; + } + + private WorkPeriodic? GetNextPeriodic() + { + var work = Periodic + .OrderBy(w => w.NextStart) + .ThenByDescending(w => w.Period) + .FirstOrDefault(); + return work; + } + } +#nullable disable +} diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs index a2feb973..24b8e999 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs @@ -12,7 +12,6 @@ namespace AsbCloudWebApi.Tests.ServicesTests { private readonly Mock mockServiceProvider; private readonly Mock mockServiceScopeFactory; - private readonly TimeSpan period = TimeSpan.FromSeconds(10); private readonly Func someAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask; public BackgroundWorkerServiceTest() @@ -28,13 +27,34 @@ namespace AsbCloudWebApi.Tests.ServicesTests mockServiceScopeFactory.SetReturnsDefault(mockServiceScope.Object); } + [Fact] + public void Contains_returns_true() + { + mockServiceScopeFactory.Invocations.Clear(); + + var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + const string work1Id = "long name 1"; + const string work2Id = "long name 2"; + + var work1 = new WorkBase(work1Id, someAction); + var work2 = new WorkPeriodic(work2Id, someAction, TimeSpan.Zero); + + backgroundService.Push(work1); + backgroundService.Push(work2); + + Assert.True(backgroundService.Contains(work1Id)); + Assert.True(backgroundService.Contains(work2Id)); + Assert.False(backgroundService.Contains(work2Id + work1Id)); + Assert.False(backgroundService.Contains(string.Empty)); + } + [Fact] public async Task Push_makes_new_scope_after_start() { mockServiceScopeFactory.Invocations.Clear(); var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); - var work = new WorkBase("", (_, _, _) => Task.CompletedTask); + var work = new WorkBase("", someAction); backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); @@ -99,8 +119,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests workDone = true; return Task.CompletedTask; }, - TimeSpan.FromSeconds(30)); - work.LastStart = DateTime.Now; + TimeSpan.FromSeconds(30)) + { + LastStart = DateTime.Now + }; backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); @@ -168,12 +190,14 @@ namespace AsbCloudWebApi.Tests.ServicesTests { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workCanceled = false; - var work = new WorkBase("", async (_, _, _) => await Task.Delay(1000000)); - work.Timeout = TimeSpan.FromMilliseconds(1); - work.OnErrorAsync = async (id, ex, token) => + var work = new WorkBase("", async (_, _, token) => await Task.Delay(1000000, token)) { - workCanceled = ex is System.TimeoutException; - await Task.CompletedTask; + Timeout = TimeSpan.FromMilliseconds(1), + OnErrorAsync = async (id, ex, token) => + { + workCanceled = ex is System.TimeoutException; + await Task.CompletedTask; + } }; backgroundService.Push(work); From 89e0495d099d9b68145cd68ef38d369b47c52183 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 14:48:23 +0500 Subject: [PATCH 06/14] BackgroundWorker adapt other services to this one. --- AsbCloudApp/Services/FileService.cs | 4 +- .../Services/IBackgroundWorkerService.cs | 50 ----- AsbCloudApp/Services/IReportService.cs | 5 +- AsbCloudInfrastructure/DependencyInjection.cs | 3 +- .../ReportDataSourcePgCloud.cs | 5 +- .../Background/BackgroundWorkerService.cs | 88 -------- .../Services/Background/WorkBase.cs | 69 ------- .../Services/Background/WorkPeriodic.cs | 36 ---- .../Services/Background/WorkQueue.cs | 101 --------- .../Services/BackgroundWorkerService.cs | 193 ------------------ .../DrillingProgram/DrillingProgramService.cs | 61 +++--- .../Services/Email/EmailService.cs | 46 +++-- .../Services/ReportService.cs | 77 +++---- .../DrillingProgramServiceTest.cs | 33 +-- .../Controllers/ReportController.cs | 2 +- 15 files changed, 125 insertions(+), 648 deletions(-) delete mode 100644 AsbCloudApp/Services/IBackgroundWorkerService.cs delete mode 100644 AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs delete mode 100644 AsbCloudInfrastructure/Services/Background/WorkBase.cs delete mode 100644 AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs delete mode 100644 AsbCloudInfrastructure/Services/Background/WorkQueue.cs delete mode 100644 AsbCloudInfrastructure/Services/BackgroundWorkerService.cs diff --git a/AsbCloudApp/Services/FileService.cs b/AsbCloudApp/Services/FileService.cs index 9b7ca156..d361cf38 100644 --- a/AsbCloudApp/Services/FileService.cs +++ b/AsbCloudApp/Services/FileService.cs @@ -73,7 +73,7 @@ namespace AsbCloudApp.Services /// /// /// - public async Task SaveAsync(int idWell, int? idUser, int idCategory, + public async Task SaveAsync(int idWell, int? idUser, int idCategory, string fileFullName, Stream fileStream, CancellationToken token) { //save info to db @@ -93,7 +93,7 @@ namespace AsbCloudApp.Services string filePath = fileStorageRepository.MakeFilePath(idWell, idCategory, fileFullName, fileId); await fileStorageRepository.SaveFileAsync(filePath, fileStream, token); - return await GetOrDefaultAsync(fileId, token); + return (await GetOrDefaultAsync(fileId, token))!; } /// diff --git a/AsbCloudApp/Services/IBackgroundWorkerService.cs b/AsbCloudApp/Services/IBackgroundWorkerService.cs deleted file mode 100644 index 2ccc47ab..00000000 --- a/AsbCloudApp/Services/IBackgroundWorkerService.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudApp.Services -{ - /// - /// Сервис выстраивает очередь из фоновых задач. Ограничивает количество одновременно выполняющихся задач. - /// - public interface IBackgroundWorkerService - { - /// - /// Проверка, есть ли задача в очереди - /// - /// идентификатор задачи - /// - bool Contains(string id); - - /// - /// Добавляет в очередь задач новую задачу - /// - /// идентификатор задачи - /// делегат - /// id задачи в очереди - string Enqueue(string id, Func func); - - /// - /// Добавляет в очередь задач новую задачу - /// - /// - /// - string Enqueue(Func func); - - /// - /// Добавляет в очередь задач новую задачу - /// - /// идентификатор задачи - /// - /// - /// - string Enqueue(string id, Func func, Func onError); - - /// - /// Пробуем удалить задачу по идентификатору - /// - /// - /// - bool TryRemove(string id); - } -} \ No newline at end of file diff --git a/AsbCloudApp/Services/IReportService.cs b/AsbCloudApp/Services/IReportService.cs index c66861b7..1f4a7d35 100644 --- a/AsbCloudApp/Services/IReportService.cs +++ b/AsbCloudApp/Services/IReportService.cs @@ -16,7 +16,6 @@ namespace AsbCloudApp.Services /// int ReportCategoryId { get; } - // TODO: rename this method /// /// Поставить рапорт в очередь на формирование /// @@ -28,7 +27,7 @@ namespace AsbCloudApp.Services /// /// /// - string CreateReport(int idWell, int idUser, int stepSeconds, + string EnqueueCreateReportWork(int idWell, int idUser, int stepSeconds, int format, DateTime begin, DateTime end, Action handleReportProgress); @@ -49,7 +48,7 @@ namespace AsbCloudApp.Services /// /// /// - DatesRangeDto GetDatesRangeOrDefault(int idWell); + DatesRangeDto? GetDatesRangeOrDefault(int idWell); /// /// Список готовых рапортов diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 90c1bf5c..a66ebe9e 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -6,6 +6,7 @@ using AsbCloudApp.Services; using AsbCloudApp.Services.Subsystems; using AsbCloudDb.Model; using AsbCloudDb.Model.Subsystems; +using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.DailyReport; @@ -106,7 +107,7 @@ namespace AsbCloudInfrastructure services.AddSingleton(provider=> TelemetryDataCache.GetInstance(configuration)); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(provider => ReduceSamplingService.GetInstance(configuration)); services.AddTransient(); diff --git a/AsbCloudInfrastructure/ReportDataSourcePgCloud.cs b/AsbCloudInfrastructure/ReportDataSourcePgCloud.cs index b42f44cd..f85a3108 100644 --- a/AsbCloudInfrastructure/ReportDataSourcePgCloud.cs +++ b/AsbCloudInfrastructure/ReportDataSourcePgCloud.cs @@ -10,7 +10,7 @@ namespace AsbCloudInfrastructure { public class ReportDataSourcePgCloud : IReportDataSource { - private readonly AsbCloudDbContext context; + private readonly IAsbCloudDbContext context; private readonly int? idTelemetry; private readonly WellInfoReport info; @@ -25,7 +25,7 @@ namespace AsbCloudInfrastructure {3, "Информация"}, }; - public ReportDataSourcePgCloud(AsbCloudDbContext context, int idWell) + public ReportDataSourcePgCloud(IAsbCloudDbContext context, int idWell) { this.context = context; @@ -65,6 +65,7 @@ namespace AsbCloudInfrastructure public AnalyzeResult Analyze() { + // TODO: Replace by linq methods. var messagesStat = (from item in context.TelemetryMessages where item.IdTelemetry == idTelemetry group item.DateTime by item.IdTelemetry into g diff --git a/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs deleted file mode 100644 index 52bcbf6b..00000000 --- a/AsbCloudInfrastructure/Services/Background/BackgroundWorkerService.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services.Background -{ -# nullable enable - /// - /// Сервис для фонового выполнения работы - /// - public class BackgroundWorkerService : BackgroundService - { - private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); - private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2); - private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2); - private readonly IServiceProvider serviceProvider; - private readonly WorkQueue workQueue = new WorkQueue(); - - public BackgroundWorkerService(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - } - - /// - /// Добавление задачи в очередь. - /// Не периодические задачи будут выполняться вперед. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) - { - workQueue.Push(work); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - return workQueue.Delete(id); - } - - protected override async Task ExecuteAsync(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - var dateStart = DateTime.Now; - var work = workQueue.Pop(); - if (work is null) - { - await Task.Delay(executePeriod, token); - continue; - } - - using (IServiceScope scope = serviceProvider.CreateScope()) - { - try - { - var task = work.ActionAsync(work.Id, scope.ServiceProvider, token); - await task.WaitAsync(work.Timeout, token); - - work.ExecutionTime = DateTime.Now - dateStart; - Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}"); - } - catch (Exception exception) - { - Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}"); - if (work.OnErrorAsync is not null) - { - using var task = Task.Run( - async () => await work.OnErrorAsync(work.Id, exception, token), - token); - await task.WaitAsync(exceptionHandleTimeout, token); - } - } - } - - await Task.Delay(minDelay, token); - } - } - } -#nullable disable -} diff --git a/AsbCloudInfrastructure/Services/Background/WorkBase.cs b/AsbCloudInfrastructure/Services/Background/WorkBase.cs deleted file mode 100644 index 7f305bea..00000000 --- a/AsbCloudInfrastructure/Services/Background/WorkBase.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services.Background -{ -#nullable enable - /// - /// Класс разовой работы. - /// Разовая работа приоритетнее периодической. - /// - public class WorkBase - { - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки. - /// - public string Id { get; private set; } - - /// - /// Делегат работы. - /// - /// Параметры: - /// - /// - /// string - /// Id Идентификатор работы - /// - /// - /// IServiceProvider - /// Поставщик сервисов - /// - /// - /// CancellationToken - /// Токен отмены задачи - /// - /// - /// - /// - public Func ActionAsync { get; set; } - - /// - /// Делегат обработки ошибки. - /// Не должен выполняться долго. - /// - public Func? OnErrorAsync { get; set; } - - /// - /// максимально допустимое время выполнения работы - /// - public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Фактическое время успешного выполнения работы - /// - public TimeSpan? ExecutionTime { get; set; } - - /// - /// Время последнего запуска - /// - public DateTime LastStart { get; set; } - - public WorkBase(string id, Func actionAsync) - { - Id = id; - ActionAsync = actionAsync; - } - } -#nullable disable -} diff --git a/AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs b/AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs deleted file mode 100644 index 288a37ca..00000000 --- a/AsbCloudInfrastructure/Services/Background/WorkPeriodic.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services.Background -{ -#nullable enable - /// - /// Класс периодической работы. - /// - public class WorkPeriodic : WorkBase - { - /// - /// Период выполнения задачи - /// - public TimeSpan Period { get; set; } - - /// - /// Время следующего запуска - /// - public DateTime NextStart => LastStart + Period; - - /// - /// Класс периодической работы - /// - /// Идентификатор работы. Должен быть уникальным. Используется в логах и передается в колбэки - /// Делегат работы - /// Период выполнения задачи - public WorkPeriodic(string id, Func actionAsync, TimeSpan period) - :base(id, actionAsync) - { - Period = period; - } - } -#nullable disable -} diff --git a/AsbCloudInfrastructure/Services/Background/WorkQueue.cs b/AsbCloudInfrastructure/Services/Background/WorkQueue.cs deleted file mode 100644 index 90ce7ec7..00000000 --- a/AsbCloudInfrastructure/Services/Background/WorkQueue.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace AsbCloudInfrastructure.Services.Background -{ -#nullable enable - /// - /// - /// Очередь работ - /// - /// Не периодические задачи будут возвращаться первыми, как самые приоритетные. - /// - class WorkQueue - { - private Queue Primary = new (8); - private readonly List Periodic = new (8); - - /// - /// Добавление работы. - /// - /// - /// Id mast be unique - public void Push(WorkBase work) - { - if (Periodic.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (Primary.Any(w => w.Id == work.Id)) - throw new ArgumentException("work.Id is not unique", nameof(work)); - - if (work is WorkPeriodic workPeriodic) - { - Periodic.Add(workPeriodic); - return; - } - - Primary.Enqueue(work); - } - - /// - /// Удаление работы по ID - /// - /// - /// - public bool Delete(string id) - { - var workPeriodic = Periodic.FirstOrDefault(w => w.Id == id); - if(workPeriodic is not null) - { - Periodic.Remove(workPeriodic); - return true; - } - - var work = Primary.FirstOrDefault(w => w.Id == id); - if (work is not null) - { - Primary = new Queue(Primary.Where(w => w.Id != id)); - return true; - } - - return false; - } - - /// - /// - /// Возвращает приоритетную задачу. - /// - /// - /// Если приоритетные закончились, то ищет ближайшую периодическую. - /// Если до старта ближайшей периодической работы меньше 20 сек, - /// то этой задаче устанавливается время последнего запуска в now и она возвращается. - /// Если больше 20 сек, то возвращается null. - /// - /// - /// - /// - public WorkBase? Pop() - { - if (Primary.Any()) - return Primary.Dequeue(); - - var work = GetNextPeriodic(); - if (work is null || work.NextStart > DateTime.Now) - return null; - - work.LastStart = DateTime.Now; - return work; - } - - private WorkPeriodic? GetNextPeriodic() - { - var work = Periodic - .OrderBy(w => w.NextStart) - .ThenByDescending(w => w.Period) - .FirstOrDefault(); - return work; - } - } -#nullable disable -} diff --git a/AsbCloudInfrastructure/Services/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Services/BackgroundWorkerService.cs deleted file mode 100644 index ab4d723a..00000000 --- a/AsbCloudInfrastructure/Services/BackgroundWorkerService.cs +++ /dev/null @@ -1,193 +0,0 @@ -using AsbCloudApp.Services; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace AsbCloudInfrastructure.Services -{ - /// - /// Сервис выстраивает очередь из фоновых задач. Ограничивает количество одновременно выполняющихся задач. - /// - public class BackgroundWorkerService : IDisposable, IBackgroundWorkerService - { - private readonly Worker[] workers; - private readonly Dictionary works = new Dictionary(); - private bool isRunning = false; - private CancellationTokenSource cts; - private Task task; - - public BackgroundWorkerService(IConfiguration configuration) - { - var workersCount = configuration.GetValue("BackgroundWorkersCount", 4); - workers = new Worker[workersCount]; - for (int i = 0; i < workers.Length; i++) - workers[i] = new Worker(); - } - - ~BackgroundWorkerService() - { - Dispose(); - } - - public string Enqueue(Func func) - { - var work = new Work - { - ActionAsync = func - }; - return Enqueue(work); - } - - public string Enqueue(string id, Func func) - { - var work = new Work(id, func); - return Enqueue(work); - } - - public string Enqueue(string id, Func func, Func onError) - { - var work = new Work(id, func) - { - OnErrorAsync = onError - }; - return Enqueue(work); - } - - string Enqueue(Work work) - { - works[work.Id] = work; - if (!isRunning) - { - isRunning = true; - cts = new CancellationTokenSource(); - task = Task.Run(() => ExecuteAsync(cts.Token), cts.Token); - } - return work.Id; - } - - private Work Dequeue() - { - var item = works.First(); - works.Remove(item.Key); - return item.Value; - } - - public bool TryRemove(string id) - => works.Remove(id); - - public bool Contains(string id) - => works.ContainsKey(id); - - protected async Task ExecuteAsync(CancellationToken token) - { - while (works.Any() && !token.IsCancellationRequested) - { - var freeworker = workers.FirstOrDefault(w => !w.IsBusy); - if (freeworker is not null) - { - var work = Dequeue(); - freeworker.Start(work); - } - else - await Task.Delay(10, token).ConfigureAwait(false); - } - isRunning = false; - } - - public void Dispose() - { - cts?.Cancel(); - task?.Wait(1); - task?.Dispose(); - cts?.Dispose(); - task = null; - cts = null; - GC.SuppressFinalize(this); - } - } - - class Worker : IDisposable - { - private CancellationTokenSource cts; - private Task task; - public bool IsBusy { get; private set; } - - ~Worker() - { - Dispose(); - } - - public void Dispose() - { - Stop(); - GC.SuppressFinalize(this); - } - - public void Start(Work work) - { - IsBusy = true; - cts = new CancellationTokenSource(); - task = Task.Run(async () => - { - try - { - var actionTask = work.ActionAsync(work.Id, cts.Token); - await actionTask.WaitAsync(TimeSpan.FromMinutes(2), cts.Token); - } - catch (Exception ex) - { - Trace.TraceError(ex.Message); - - if (work.OnErrorAsync is not null) - { - try - { - await work.OnErrorAsync(work.Id, ex, cts.Token).ConfigureAwait(false); - } - catch (Exception exOnErrorHandler) - { - Trace.TraceError(exOnErrorHandler.Message); - } - } - } - finally - { - cts?.Dispose(); - cts = null; - IsBusy = false; - } - }, cts.Token); - } - - public void Stop() - { - cts?.Cancel(); - task?.Wait(1); - task = null; - cts?.Dispose(); - cts = null; - IsBusy = false; - } - } - class Work - { - public string Id { get; private set; } - public Func ActionAsync { get; set; } - public Func OnErrorAsync { get; set; } - - public Work() - { - Id = Guid.NewGuid().ToString(); - } - - public Work(string id, Func actionAsync) - { - Id = id; - ActionAsync = actionAsync; - } - } -} diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index b1a6d0c6..9ef92fd8 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -3,10 +3,11 @@ using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; -using AsbCloudInfrastructure.Repository; +using AsbCloudInfrastructure.Background; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.IO; @@ -16,6 +17,7 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.DrillingProgram { +# nullable enable public class DrillingProgramService : IDrillingProgramService { private static readonly Dictionary drillingProgramCreateErrors = new Dictionary(); @@ -25,9 +27,8 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private readonly IUserRepository userRepository; private readonly IWellService wellService; private readonly IConfiguration configuration; - private readonly IBackgroundWorkerService backgroundWorker; + private readonly BackgroundWorker backgroundWorker; private readonly IEmailService emailService; - private readonly string connectionString; private const int idFileCategoryDrillingProgram = 1000; private const int idFileCategoryDrillingProgramPartsStart = 1001; @@ -55,7 +56,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram IUserRepository userRepository, IWellService wellService, IConfiguration configuration, - IBackgroundWorkerService backgroundWorker, + BackgroundWorker backgroundWorker, IEmailService emailService) { this.context = context; @@ -64,7 +65,6 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram this.wellService = wellService; this.configuration = configuration; this.backgroundWorker = backgroundWorker; - this.connectionString = configuration.GetConnectionString("DefaultConnection"); this.emailService = emailService; } @@ -127,7 +127,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram { Parts = parts, Program = files.FirstOrDefault(f => f.IdCategory == idFileCategoryDrillingProgram) - .Adapt(), + ?.Adapt(), PermissionToEdit = userRepository.HasPermission(idUser, "DrillingProgram.edit"), }; @@ -157,7 +157,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram else state.IdState = idStateNotInitialized; - await TryEnqueueMakeProgramAsync(idWell, state, token); + await EnqueueMakeProgramWorkAsync(idWell, state, token); return state; } @@ -299,7 +299,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram .AsNoTracking() .FirstOrDefaultAsync(p => p.IdWell == fileInfo.IdWell && p.IdFileCategory == fileInfo.IdCategory, token); - var user = part.RelatedUsers.FirstOrDefault(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover)?.User; + var user = part?.RelatedUsers.FirstOrDefault(r => r.IdUser == idUser && r.IdUserRole == idUserRoleApprover)?.User; if (user is null) throw new ForbidException($"User {idUser} is not in the approvers list."); @@ -323,11 +323,11 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram else { // если все согласованты согласовали - оповещаем публикатора - var approvers = part.RelatedUsers + var approvers = part!.RelatedUsers .Where(u => u.IdUserRole == idUserRoleApprover); if (approvers .All(user => fileInfo.FileMarks - .Any(mark => (mark.IdMarkType == idMarkTypeApprove && mark.User.Id == user.IdUser && !mark.IsDeleted)) || + ?.Any(mark => (mark.IdMarkType == idMarkTypeApprove && mark.User.Id == user.IdUser && !mark.IsDeleted)) == true || (fileMarkDto.IdMarkType == idMarkTypeApprove && user.IdUser == idUser))) { await NotifyPublisherOnFullAccepAsync(fileMarkDto, token); @@ -359,7 +359,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private async Task NotifyPublisherOnFullAccepAsync(FileMarkDto fileMark, CancellationToken token) { var file = await fileService.GetOrDefaultAsync(fileMark.IdFile, token); - var well = await wellService.GetOrDefaultAsync(file.IdWell, token); + var well = await wellService.GetOrDefaultAsync(file!.IdWell, token); var user = file.Author; var factory = new DrillingMailBodyFactory(configuration); var subject = factory.MakeSubject(well, "Загруженный вами документ полностью согласован"); @@ -371,7 +371,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private async Task NotifyPublisherOnRejectAsync(FileMarkDto fileMark, CancellationToken token) { var file = await fileService.GetOrDefaultAsync(fileMark.IdFile, token); - var well = await wellService.GetOrDefaultAsync(file.IdWell, token); + var well = await wellService.GetOrDefaultAsync(file!.IdWell, token); var user = file.Author; var factory = new DrillingMailBodyFactory(configuration); var subject = factory.MakeSubject(well, "Загруженный вами документ отклонен"); @@ -405,12 +405,12 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram emailService.EnqueueSend(user.Email, subject, body); } - private DrillingProgramPartDto ConvertPart(int idUser, List fileCategories, List files, DrillingProgramPart partEntity, double timezoneOffset) + private static DrillingProgramPartDto ConvertPart(int idUser, List fileCategories, List files, DrillingProgramPart partEntity, double timezoneOffset) { var part = new DrillingProgramPartDto { IdFileCategory = partEntity.IdFileCategory, - Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory).Name, + Name = fileCategories.FirstOrDefault(c => c.Id == partEntity.IdFileCategory)!.Name, Approvers = partEntity.RelatedUsers .Where(r => r.IdUserRole == idUserRoleApprover) .Select(r => r.User.Adapt()), @@ -464,31 +464,27 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram return part; } - private async Task TryEnqueueMakeProgramAsync(int idWell, DrillingProgramStateDto state, CancellationToken token) + private async Task EnqueueMakeProgramWorkAsync(int idWell, DrillingProgramStateDto state, CancellationToken token) { if (state.IdState == idStateCreating) { var workId = MakeWorkId(idWell); if (!backgroundWorker.Contains(workId)) { - var well = await wellService.GetOrDefaultAsync(idWell, token); + var well = (await wellService.GetOrDefaultAsync(idWell, token))!; var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.xlsx"; var tempResultFilePath = Path.Combine(Path.GetTempPath(), "drillingProgram", resultFileName); - async Task funcProgramMake(string id, CancellationToken token) + + var workAction = async (string workId, IServiceProvider serviceProvider, CancellationToken token) => { - var contextOptions = new DbContextOptionsBuilder() - .UseNpgsql(connectionString) - .Options; - using var context = new AsbCloudDbContext(contextOptions); - var fileRepository = new FileRepository(context); - var fileStorageRepository = new FileStorageRepository(); - var fileService = new FileService(fileRepository, fileStorageRepository); + var context = serviceProvider.GetRequiredService(); + var fileService = serviceProvider.GetRequiredService(); var files = state.Parts.Select(p => fileService.GetUrl(p.File)); DrillingProgramMaker.UniteExcelFiles(files, tempResultFilePath, state.Parts, well); await fileService.MoveAsync(idWell, null, idFileCategoryDrillingProgram, resultFileName, tempResultFilePath, token); - } + }; - Task funcOnErrorProgramMake(string workId, Exception exception, CancellationToken token) + var onErrorAction = (string workId, Exception exception, CancellationToken token) => { var message = $"Не удалось сформировать программу бурения по скважине {well?.Caption}"; drillingProgramCreateErrors[workId] = new() @@ -497,9 +493,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram Exception = exception.Message, }; return Task.CompletedTask; - } + }; - backgroundWorker.Enqueue(workId, funcProgramMake, funcOnErrorProgramMake); + var work = new WorkBase(workId, workAction) + { + ExecutionTime = TimeSpan.FromMinutes(1), + OnErrorAsync = onErrorAction + }; + + backgroundWorker.Push(work); } } } @@ -513,7 +515,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token) { var workId = MakeWorkId(idWell); - backgroundWorker.TryRemove(workId); + backgroundWorker.Delete(workId); var filesIds = await context.Files .Where(f => f.IdWell == idWell && @@ -529,4 +531,5 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private static string MakeWorkId(int idWell) => $"Make drilling program for wellId {idWell}"; } +#nullable disable } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/Email/EmailService.cs b/AsbCloudInfrastructure/Services/Email/EmailService.cs index 84a1f73f..568d4098 100644 --- a/AsbCloudInfrastructure/Services/Email/EmailService.cs +++ b/AsbCloudInfrastructure/Services/Email/EmailService.cs @@ -8,26 +8,28 @@ using System.Linq; using System.Net.Mail; using System.Threading; using System.Threading.Tasks; +using AsbCloudInfrastructure.Background; namespace AsbCloudInfrastructure.Services { +#nullable enable public class EmailService : IEmailService { - private readonly IBackgroundWorkerService backgroundWorker; + private readonly BackgroundWorker backgroundWorker; private readonly bool IsConfigured; private readonly string sender; private readonly string smtpServer; private readonly string smtpPassword; - public EmailService(IBackgroundWorkerService backgroundWorker, IConfiguration configuration) + public EmailService(BackgroundWorker backgroundWorker, IConfiguration configuration) { - sender = configuration.GetValue("email:sender", null); - smtpPassword = configuration.GetValue("email:password", null); - smtpServer = configuration.GetValue("email:smtpServer", null); + sender = configuration.GetValue("email:sender", string.Empty); + smtpPassword = configuration.GetValue("email:password", string.Empty); + smtpServer = configuration.GetValue("email:smtpServer", string.Empty); - var configError = (string.IsNullOrEmpty(sender) || + var configError = string.IsNullOrEmpty(sender) || string.IsNullOrEmpty(smtpPassword) || - string.IsNullOrEmpty(smtpServer)); + string.IsNullOrEmpty(smtpServer); IsConfigured = !configError; @@ -44,20 +46,21 @@ namespace AsbCloudInfrastructure.Services Trace.TraceWarning("smtp is not configured"); return; } - var jobId = CalcJobId(addresses, subject, htmlBody); - if (!backgroundWorker.Contains(jobId)) + var workId = MakeWorkId(addresses, subject, htmlBody); + if (!backgroundWorker.Contains(workId)) { - var action = MakeEmailSendJobAsync(addresses, subject, htmlBody); - backgroundWorker.Enqueue(jobId, action); + var workAction = MakeEmailSendWorkAction(addresses, subject, htmlBody); + var work = new WorkBase(workId, workAction); + backgroundWorker.Push(work); } } - private Func MakeEmailSendJobAsync(IEnumerable addresses, string subject, string htmlBody) + private Func MakeEmailSendWorkAction(IEnumerable addresses, string subject, string htmlBody) { var mailAddresses = new List(); foreach (var address in addresses) { - if (MailAddress.TryCreate(address, out MailAddress mailAddress)) + if (MailAddress.TryCreate(address, out MailAddress? mailAddress)) mailAddresses.Add(mailAddress); else Trace.TraceWarning($"Mail {address} is not correct."); @@ -69,16 +72,16 @@ namespace AsbCloudInfrastructure.Services if (string.IsNullOrEmpty(subject)) throw new ArgumentInvalidException($"{nameof(subject)} should be set", nameof(subject)); - var func = async (string id, CancellationToken token) => + var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) => { var from = new MailAddress(sender); - - var message = new MailMessage(); - message.From = from; + var message = new MailMessage + { + From = from + }; foreach (var mailAddress in mailAddresses) message.To.Add(mailAddress); - //message.To.Add("support@digitaldrilling.ru"); message.BodyEncoding = System.Text.Encoding.UTF8; message.Body = htmlBody; @@ -91,12 +94,12 @@ namespace AsbCloudInfrastructure.Services client.Credentials = new System.Net.NetworkCredential(sender, smtpPassword); await client.SendMailAsync(message, token); - Trace.TraceInformation($"Send email to {string.Join(',', addresses)} subj:{subject} html body count {htmlBody.Count()}"); + Trace.TraceInformation($"Send email to {string.Join(',', addresses)} subj:{subject} html body count {htmlBody.Length}"); }; - return func; + return workAction; } - private string CalcJobId(IEnumerable addresses, string subject, string content) + private static string MakeWorkId(IEnumerable addresses, string subject, string content) { var hash = GetHashCode(addresses); hash ^= subject.GetHashCode(); @@ -114,4 +117,5 @@ namespace AsbCloudInfrastructure.Services return hash; } } +#nullable disable } diff --git a/AsbCloudInfrastructure/Services/ReportService.cs b/AsbCloudInfrastructure/Services/ReportService.cs index 7143e063..c46d4682 100644 --- a/AsbCloudInfrastructure/Services/ReportService.cs +++ b/AsbCloudInfrastructure/Services/ReportService.cs @@ -1,11 +1,11 @@ using AsbCloudApp.Data; using AsbCloudApp.Services; using AsbCloudDb.Model; -using AsbCloudInfrastructure.Repository; +using AsbCloudInfrastructure.Background; using AsbSaubReport; using Mapster; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.IO; @@ -15,30 +15,32 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services { +#nullable enable public class ReportService : IReportService { private readonly IAsbCloudDbContext db; - private readonly string connectionString; private readonly ITelemetryService telemetryService; private readonly IWellService wellService; - private readonly IBackgroundWorkerService backgroundWorkerService; - - public ReportService(IAsbCloudDbContext db, IConfiguration configuration, - ITelemetryService telemetryService, IWellService wellService, IBackgroundWorkerService backgroundWorkerService) - { - this.db = db; - this.connectionString = configuration.GetConnectionString("DefaultConnection"); - this.wellService = wellService; - this.backgroundWorkerService = backgroundWorkerService; - this.telemetryService = telemetryService; - ReportCategoryId = db.FileCategories.AsNoTracking() - .FirstOrDefault(c => - c.Name.Equals("Рапорт")).Id; - } + private readonly BackgroundWorker backgroundWorkerService; public int ReportCategoryId { get; private set; } - public string CreateReport(int idWell, int idUser, int stepSeconds, int format, DateTime begin, + public ReportService(IAsbCloudDbContext db, + ITelemetryService telemetryService, + IWellService wellService, + BackgroundWorker backgroundWorkerService) + { + this.db = db; + this.wellService = wellService; + this.backgroundWorkerService = backgroundWorkerService; + this.telemetryService = telemetryService; + ReportCategoryId = db.FileCategories + .AsNoTracking() + .First(c => c.Name.Equals("Рапорт")) + .Id; + } + + public string EnqueueCreateReportWork(int idWell, int idUser, int stepSeconds, int format, DateTime begin, DateTime end, Action progressHandler) { var timezoneOffset = wellService.GetTimezone(idWell).Hours; @@ -47,12 +49,12 @@ namespace AsbCloudInfrastructure.Services var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset); var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset); - var newReportId = backgroundWorkerService.Enqueue(async (id, token) => + var workId = $"create report by wellid:{idWell} for userid:{idUser} requested at {DateTime.Now}"; + + var workAction = async (string id, IServiceProvider serviceProvider, CancellationToken token) => { - var contextOptions = new DbContextOptionsBuilder() - .UseNpgsql(connectionString) - .Options; - using var context = new AsbCloudDbContext(contextOptions); + using var context = serviceProvider.GetRequiredService(); + var fileService = serviceProvider.GetRequiredService(); var tempDir = Path.Combine(Path.GetTempPath(), "report"); @@ -65,11 +67,8 @@ namespace AsbCloudInfrastructure.Services progressHandler.Invoke(e.Adapt(), id); }; generator.Make(reportFileName); - - var fileRepository = new FileRepository(context); - var fileStorageRepository = new FileStorageRepository(); - var fileService = new FileService(fileRepository, fileStorageRepository); - var fileInfo = await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token); + + var fileInfo = (await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token))!; progressHandler.Invoke(new { @@ -91,13 +90,17 @@ namespace AsbCloudInfrastructure.Services }; context.ReportProperties.Add(newReportProperties); context.SaveChanges(); - }); + }; + + var work = new WorkBase(workId, workAction); + backgroundWorkerService.Push(work); + progressHandler.Invoke(new ReportProgressDto { Operation = "Ожидает начала в очереди.", Progress = 0f, - }, newReportId); - return newReportId; + }, workId); + return workId; } public int GetReportPagesCount(int idWell, DateTime begin, DateTime end, int stepSeconds, int format) @@ -106,12 +109,12 @@ namespace AsbCloudInfrastructure.Services var beginRemote = begin.ToTimeZoneOffsetHours(timezoneOffset); var endRemote = end.ToTimeZoneOffsetHours(timezoneOffset); - var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, (AsbCloudDbContext)db); + var generator = GetReportGenerator(idWell, beginRemote, endRemote, stepSeconds, format, db); var pagesCount = generator.GetPagesCount(); return pagesCount; } - public DatesRangeDto GetDatesRangeOrDefault(int idWell) + public DatesRangeDto? GetDatesRangeOrDefault(int idWell) { var idTelemetry = telemetryService.GetOrDefaultIdTelemetryByIdWell(idWell); if (idTelemetry is null) @@ -128,8 +131,8 @@ namespace AsbCloudInfrastructure.Services .OrderBy(o => o.File.UploadDate) .AsNoTracking() .Take(1024); - var properties = await propertiesQuery.ToListAsync(token); - return properties.Select(p => new ReportPropertiesDto + var entities = await propertiesQuery.ToListAsync(token); + var dtos = entities.Select(p => new ReportPropertiesDto { Id = p.Id, Name = p.File.Name, @@ -151,10 +154,11 @@ namespace AsbCloudInfrastructure.Services Step = p.Step, Format = p.Format == 0 ? ".pdf" : ".las" }); + return dtos; } private static IReportGenerator GetReportGenerator(int idWell, DateTime begin, - DateTime end, int stepSeconds, int format, AsbCloudDbContext context) + DateTime end, int stepSeconds, int format, IAsbCloudDbContext context) { var dataSource = new ReportDataSourcePgCloud(context, idWell); IReportGenerator generator = format switch @@ -173,4 +177,5 @@ namespace AsbCloudInfrastructure.Services return generator; } } +#nullable disable } diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs index e823189b..05833333 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/DrillingProgramServiceTest.cs @@ -2,7 +2,7 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb.Model; -using AsbCloudInfrastructure.Repository; +using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Services.DrillingProgram; using Mapster; using Microsoft.Extensions.Configuration; @@ -83,8 +83,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests private readonly Mock userRepositoryMock; private readonly Mock wellServiceMock; private readonly Mock configurationMock; - private readonly Mock backgroundWorkerMock; - private readonly Mock emailService; + private readonly Mock backgroundWorkerMock; + private readonly Mock emailServiceMock; public DrillingProgramServiceTest() { @@ -102,7 +102,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests userRepositoryMock = new Mock(); wellServiceMock = new Mock(); configurationMock = new Mock(); - backgroundWorkerMock = new Mock(); + backgroundWorkerMock = new Mock(); + emailServiceMock = new Mock(); } [Fact] @@ -115,7 +116,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var users = await service.GetAvailableUsers(idWell, CancellationToken.None); @@ -132,7 +133,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var result = await service.AddPartsAsync(idWell, new int[] { 1001, 1002 }, CancellationToken.None); @@ -151,7 +152,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var result = await service.RemovePartsAsync(idWell, new int[] { 1005 }, CancellationToken.None); @@ -174,7 +175,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var result = await service.AddUserAsync(idWell, 1001, publisher1.Id, 1, CancellationToken.None); @@ -209,7 +210,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var result = await service.RemoveUserAsync(idWell, idFileCategory, publisher1.Id, idUserRole, CancellationToken.None); @@ -235,7 +236,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var fileMark = new FileMarkDto { @@ -266,7 +267,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var fileMark = new FileMarkDto { IdFile = file1001.Id, @@ -304,7 +305,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var fileMark = new FileMarkDto { @@ -331,7 +332,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); @@ -358,12 +359,12 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); Assert.Equal(2, state.IdState); - backgroundWorkerMock.Verify(s => s.Enqueue(It.IsAny>())); + backgroundWorkerMock.Verify(s => s.Push(It.IsAny())); } [Fact] @@ -388,7 +389,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests wellServiceMock.Object, configurationMock.Object, backgroundWorkerMock.Object, - emailService.Object); + emailServiceMock.Object); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); diff --git a/AsbCloudWebApi/Controllers/ReportController.cs b/AsbCloudWebApi/Controllers/ReportController.cs index d3df1092..8b1081ab 100644 --- a/AsbCloudWebApi/Controllers/ReportController.cs +++ b/AsbCloudWebApi/Controllers/ReportController.cs @@ -68,7 +68,7 @@ namespace AsbCloudWebApi.Controllers ).ConfigureAwait(false); }, token); - var id = reportService.CreateReport(idWell, (int)idUser, + var id = reportService.EnqueueCreateReportWork(idWell, (int)idUser, stepSeconds, format, begin, end, HandleReportProgressAsync); return Ok(id); From 71aff8d1ec985831f9ad096f9fdae3f657164638 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 14:49:00 +0500 Subject: [PATCH 07/14] nit. comments and cleanup --- AsbCloudInfrastructure/Helper.cs | 55 ------------------- AsbCloudInfrastructure/MapsterExtension.cs | 12 ---- ...Extentions.cs => MemoryCacheExtentions.cs} | 0 AsbCloudInfrastructure/Startup.cs | 4 +- .../Middlewares/RequerstTrackerMiddleware.cs | 1 + .../SimplifyExceptionsMiddleware.cs | 2 +- AsbCloudWebApi/Program.cs | 41 +------------- 7 files changed, 4 insertions(+), 111 deletions(-) delete mode 100644 AsbCloudInfrastructure/Helper.cs delete mode 100644 AsbCloudInfrastructure/MapsterExtension.cs rename AsbCloudInfrastructure/{CacheExtentions.cs => MemoryCacheExtentions.cs} (100%) diff --git a/AsbCloudInfrastructure/Helper.cs b/AsbCloudInfrastructure/Helper.cs deleted file mode 100644 index 01fc97b5..00000000 --- a/AsbCloudInfrastructure/Helper.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; - -namespace AsbCloudInfrastructure -{ - public static class Helper - { - public static T Max(params T[] items) - where T : IComparable - { - var count = items.Length; - if (count < 1) - throw new ArgumentException("Count of params must be greater than 1"); - - var max = items[0]; - for (var i = 1; i < count; i++) - if (max.CompareTo(items[i]) < 0) - max = items[i]; - - return max; - } - - public static T Min(params T[] items) - where T : IComparable - { - var count = items.Length; - if (count < 1) - throw new ArgumentException("Count of params must be greater than 1"); - - var min = items[0]; - for (var i = 1; i < count; i++) - if (min.CompareTo(items[i]) > 0) - min = items[i]; - - return min; - } - - public static (T min, T max) MinMax(params T[] items) - where T : IComparable - { - var count = items.Length; - if (count < 1) - throw new ArgumentException("Count of params must be greater than 1"); - - var min = items[0]; - var max = items[0]; - for (var i = 1; i < count; i++) - if (max.CompareTo(items[i]) < 0) - max = items[i]; - else if (min.CompareTo(items[i]) > 0) - min = items[i]; - - return (min, max); - } - } -} diff --git a/AsbCloudInfrastructure/MapsterExtension.cs b/AsbCloudInfrastructure/MapsterExtension.cs deleted file mode 100644 index 94ed630c..00000000 --- a/AsbCloudInfrastructure/MapsterExtension.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Mapster -{ - public static class MapsterExtension - { - //public static IEnumerable Adapt(this IEnumerable sourceList) - //{ - // return sourceList.Select(item => item.Adapt()); - //} - - - } -} diff --git a/AsbCloudInfrastructure/CacheExtentions.cs b/AsbCloudInfrastructure/MemoryCacheExtentions.cs similarity index 100% rename from AsbCloudInfrastructure/CacheExtentions.cs rename to AsbCloudInfrastructure/MemoryCacheExtentions.cs diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 5ccb0df7..c8dc24a9 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -1,17 +1,15 @@ using AsbCloudApp.Services; using AsbCloudDb.Model; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; -using System.Linq; namespace AsbCloudInfrastructure { public class Startup { - public static void BeforeRunHandler(IHost host, IConfigurationRoot configuration) + public static void BeforeRunHandler(IHost host) { using var scope = host.Services.CreateScope(); var context = scope.ServiceProvider.GetService(); diff --git a/AsbCloudWebApi/Middlewares/RequerstTrackerMiddleware.cs b/AsbCloudWebApi/Middlewares/RequerstTrackerMiddleware.cs index b35b40e5..551b4d03 100644 --- a/AsbCloudWebApi/Middlewares/RequerstTrackerMiddleware.cs +++ b/AsbCloudWebApi/Middlewares/RequerstTrackerMiddleware.cs @@ -43,6 +43,7 @@ namespace AsbCloudWebApi.Middlewares sw.Stop(); requestLog.ElapsedMilliseconds = sw.ElapsedMilliseconds; requestLog.Status = context.Response.StatusCode; + // TODO: Add request params and body size. service.RegisterRequestError(requestLog, ex); throw; } diff --git a/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs b/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs index c09f9749..dd9fff9f 100644 --- a/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs +++ b/AsbCloudWebApi/Middlewares/SimplifyExceptionsMiddleware.cs @@ -40,7 +40,7 @@ namespace AsbCloudWebApi.Middlewares { Console.WriteLine(ex.Message); } - catch (Exception ex) + catch (Exception ex) // TODO: find explicit exception. Use Trace. Add body size to message. { if (ex.Message.Contains("Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.")) Console.WriteLine("Reading the request body timed out due to data arriving too slowly."); diff --git a/AsbCloudWebApi/Program.cs b/AsbCloudWebApi/Program.cs index b21fa05f..18825902 100644 --- a/AsbCloudWebApi/Program.cs +++ b/AsbCloudWebApi/Program.cs @@ -1,10 +1,5 @@ -using DocumentFormat.OpenXml.InkML; using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using System; -using System.Linq; namespace AsbCloudWebApi { @@ -15,30 +10,8 @@ namespace AsbCloudWebApi public static void Main(string[] args) { - IConfigurationRoot configuration = new ConfigurationBuilder() - .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) - .AddJsonFile("appsettings.json") - .Build(); - - if (args?.Length > 0) - { - if (args.Contains("db_init")) - { - var connectionStringName = "DefaultConnection"; - - var context = AsbCloudInfrastructure.DependencyInjection.MakeContext(configuration.GetConnectionString(connectionStringName)); - context.Database.SetCommandTimeout(TimeSpan.FromSeconds(5 * 60)); - context.Database.Migrate(); - - Console.WriteLine(" ."); - return; - } - WriteHelp(); - return; - } - var host = CreateHostBuilder(args).Build(); - AsbCloudInfrastructure.Startup.BeforeRunHandler(host, configuration); + AsbCloudInfrastructure.Startup.BeforeRunHandler(host); host.Run(); } @@ -48,17 +21,5 @@ namespace AsbCloudWebApi { webBuilder.UseStartup(); }); - - private static void WriteHelp() - { - Console.WriteLine(" ."); - Console.WriteLine(" :"); - Console.WriteLine("db_init - ."); - Console.WriteLine(" \"DefaultConnection\""); - Console.WriteLine(" , "); - Console.WriteLine(" , ."); - Console.WriteLine(" public"); - Console.WriteLine(""); - } } } From 8e9baf22d8104283a78594f0f53ea3328fc8f793 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 17:18:16 +0500 Subject: [PATCH 08/14] BackgroundWorker Move periodic services --- AsbCloudApp/Services/IReportService.cs | 3 + ...ndWorkerService.cs => BackgroundWorker.cs} | 17 ++--- AsbCloudInfrastructure/DependencyInjection.cs | 3 - .../DetectedOperationService.cs | 2 +- ...ce.cs => OperationDetectionWorkFactory.cs} | 63 +++++-------------- .../LimitingParameterBackgroundService.cs | 62 ++++++------------ .../Services/SAUB/TelemetryTracker.cs | 1 + ... SubsystemOperationTimeCalcWorkFactory.cs} | 60 +++++------------- AsbCloudInfrastructure/Startup.cs | 22 +++++-- 9 files changed, 82 insertions(+), 151 deletions(-) rename AsbCloudInfrastructure/Background/{BackgroundWorkerService.cs => BackgroundWorker.cs} (89%) rename AsbCloudInfrastructure/Services/DetectOperations/{OperationDetectionBackgroundService.cs => OperationDetectionWorkFactory.cs} (69%) rename AsbCloudInfrastructure/Services/Subsystems/{SubsystemOperationTimeBackgroundService.cs => SubsystemOperationTimeCalcWorkFactory.cs} (86%) diff --git a/AsbCloudApp/Services/IReportService.cs b/AsbCloudApp/Services/IReportService.cs index 1f4a7d35..e5e24648 100644 --- a/AsbCloudApp/Services/IReportService.cs +++ b/AsbCloudApp/Services/IReportService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; namespace AsbCloudApp.Services { +#nullable enable /// /// Сервис рапортов /// @@ -57,5 +58,7 @@ namespace AsbCloudApp.Services /// /// Task> GetAllReportsByWellAsync(int idWell, CancellationToken token); + +#nullable disable } } diff --git a/AsbCloudInfrastructure/Background/BackgroundWorkerService.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs similarity index 89% rename from AsbCloudInfrastructure/Background/BackgroundWorkerService.cs rename to AsbCloudInfrastructure/Background/BackgroundWorker.cs index e823284d..531d775e 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorkerService.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using AsbCloudInfrastructure.Services.DetectOperations; +using AsbCloudInfrastructure.Services.Subsystems; +using AsbCloudInfrastructure.Services; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Diagnostics; @@ -18,13 +21,10 @@ namespace AsbCloudInfrastructure.Background private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2); private readonly IServiceProvider serviceProvider; private readonly WorkQueue workQueue = new WorkQueue(); - + public string? CurrentWorkId; public BackgroundWorker(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; -#warning move StartAsync(CancellationToken.None).Wait() to THE factory - Task.Delay(1_000) - .ContinueWith(_=> StartAsync(CancellationToken.None).Wait()); } /// @@ -69,11 +69,12 @@ namespace AsbCloudInfrastructure.Background await Task.Delay(executePeriod, token); continue; } - + CurrentWorkId = work.Id; using var scope = serviceProvider.CreateScope(); try { + Trace.TraceInformation($"Backgroud work:\"{work.Id}\" start."); var task = work.ActionAsync(work.Id, scope.ServiceProvider, token); await task.WaitAsync(work.Timeout, token); @@ -90,8 +91,8 @@ namespace AsbCloudInfrastructure.Background token); await task.WaitAsync(exceptionHandleTimeout, token); } - } - + } + CurrentWorkId = null; await Task.Delay(minDelay, token); } } diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index a66ebe9e..35b956e2 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -98,9 +98,6 @@ namespace AsbCloudInfrastructure services.AddScoped(provider => provider.GetService()); services.AddScoped(); - services.AddHostedService(); - services.AddHostedService(); - services.AddHostedService(); services.AddSingleton(new WitsInfoService()); services.AddSingleton(new InstantDataRepository()); services.AddSingleton(provider=> TelemetryDataCache.GetInstance(configuration)); diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs index 87decac8..7cca4f97 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs @@ -268,7 +268,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations return query; } - private DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable operationValues, IEnumerable schedules) + private static DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable operationValues, IEnumerable schedules) { var dto = operation.Adapt(); dto.IdWell = well.Id; diff --git a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs similarity index 69% rename from AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs rename to AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs index e4b7c450..8ff6ebb8 100644 --- a/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/DetectOperations/OperationDetectionWorkFactory.cs @@ -1,7 +1,5 @@ using AsbCloudDb.Model; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; using System; using System.Collections.Generic; using System.Diagnostics; @@ -9,14 +7,16 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using AsbCloudInfrastructure.Services.DetectOperations.Detectors; +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; namespace AsbCloudInfrastructure.Services.DetectOperations { #nullable enable - public class OperationDetectionBackgroundService : BackgroundService + public static class OperationDetectionWorkFactory { - private readonly string connectionString; - private readonly TimeSpan period = TimeSpan.FromHours(1); + private const string workId = "Operation detection"; + private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); private static readonly DetectorAbstract[] detectors = new DetectorAbstract[] { @@ -31,49 +31,18 @@ namespace AsbCloudInfrastructure.Services.DetectOperations new DetectorTemplatingWhileDrilling(), }; - public OperationDetectionBackgroundService(IConfiguration configuration) - { - connectionString = configuration.GetConnectionString("DefaultConnection"); + public static WorkPeriodic MakeWork() + { + var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod); + workPeriodic.Timeout = TimeSpan.FromMinutes(30); + return workPeriodic; } - protected override async Task ExecuteAsync(CancellationToken token = default) + // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. + private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) { - var timeToStartAnalysis = DateTime.Now; - var options = new DbContextOptionsBuilder() - .UseNpgsql(connectionString) - .Options; + using var db = serviceProvider.GetRequiredService(); - while (!token.IsCancellationRequested) - { - if (DateTime.Now > timeToStartAnalysis) - { - timeToStartAnalysis = DateTime.Now + period; - try - { - using var context = new AsbCloudDbContext(options); - var added = await DetectedAllTelemetriesAsync(context, token); - Trace.TraceInformation($"Total detection complete. Added {added} operations."); - } - catch (Exception ex) - { - Trace.TraceError(ex.Message); - } - GC.Collect(); - } - - var ms = (int)(timeToStartAnalysis - DateTime.Now).TotalMilliseconds; - ms = ms > 100 ? ms : 100; - await Task.Delay(ms, token).ConfigureAwait(false); - } - } - - public override async Task StopAsync(CancellationToken token) - { - await base.StopAsync(token).ConfigureAwait(false); - } - - private static async Task DetectedAllTelemetriesAsync(IAsbCloudDbContext db, CancellationToken token) - { var lastDetectedDates = await db.DetectedOperations .GroupBy(o => o.IdTelemetry) .Select(g => new @@ -88,7 +57,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations .Select(t => t.Id) .ToListAsync(token); - var JounedlastDetectedDates = telemetryIds + var joinedlastDetectedDates = telemetryIds .GroupJoin(lastDetectedDates, t => t, o => o.IdTelemetry, @@ -97,8 +66,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations IdTelemetry = outer, inner.SingleOrDefault()?.LastDate, }); + var affected = 0; - foreach (var item in JounedlastDetectedDates) + foreach (var item in joinedlastDetectedDates) { var stopwatch = Stopwatch.StartNew(); var newOperations = await DetectOperationsAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); @@ -109,7 +79,6 @@ namespace AsbCloudInfrastructure.Services.DetectOperations affected += await db.SaveChangesAsync(token); } } - return affected; } private static async Task> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs index 261ceb9c..1420ea89 100644 --- a/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/LimitingParameterBackgroundService.cs @@ -1,61 +1,37 @@ using AsbCloudDb.Model; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; using System; using System.Data.Common; using System.Data; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; namespace AsbCloudInfrastructure.Services { #nullable enable - internal class LimitingParameterBackgroundService : BackgroundService + internal static class LimitingParameterCalcWorkFactory { - private readonly string connectionString; - private readonly TimeSpan period = TimeSpan.FromHours(1); + private const string workId = "Limiting parameter calc"; + private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); - public LimitingParameterBackgroundService(IConfiguration configuration) + public static WorkPeriodic MakeWork() { - connectionString = configuration.GetConnectionString("DefaultConnection"); - } - - protected override async Task ExecuteAsync(CancellationToken token) - { - var timeToStart = DateTime.Now; - var options = new DbContextOptionsBuilder() - .UseNpgsql(connectionString) - .Options; - while (!token.IsCancellationRequested) + var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) { - if (DateTime.Now > timeToStart) - { - timeToStart = DateTime.Now + period; - try - { - using var context = new AsbCloudDbContext(options); - var added = await LimitingParameterAsync(context, token); - Trace.TraceInformation($"Total limiting parameter complete. Added {added} limiting parameters."); - } - catch (Exception ex) - { - Trace.TraceError(ex.Message); - } - GC.Collect(); - } - var ms = (int)(timeToStart - DateTime.Now).TotalMilliseconds; - ms = ms > 100 ? ms : 100; - await Task.Delay(ms, token).ConfigureAwait(false); - } + Timeout = TimeSpan.FromMinutes(30) + }; + return workPeriodic; } - private static async Task LimitingParameterAsync(IAsbCloudDbContext context, CancellationToken token) + // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. + private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) { - var lastDetectedDates = await context.LimitingParameter + using var db = serviceProvider.GetRequiredService(); + var lastDetectedDates = await db.LimitingParameter .GroupBy(o => o.IdTelemetry) .Select(g => new { @@ -64,7 +40,7 @@ namespace AsbCloudInfrastructure.Services }) .ToListAsync(token); - var telemetryIds = await context.Telemetries + var telemetryIds = await db.Telemetries .Where(t => t.Info != null && t.TimeZone != null) .Select(t => t.Id) .ToListAsync(token); @@ -79,17 +55,15 @@ namespace AsbCloudInfrastructure.Services inner.SingleOrDefault()?.LastDate, }); - var affected = 0; foreach (var item in telemetryLastDetectedDates) { - var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, context, token); + var newLimitingParameters = await GetLimitingParameterAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newLimitingParameters?.Any() == true) { - context.LimitingParameter.AddRange(newLimitingParameters); - affected += await context.SaveChangesAsync(token); + db.LimitingParameter.AddRange(newLimitingParameters); + await db.SaveChangesAsync(token); } } - return affected; } private static async Task> GetLimitingParameterAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs index 190094a1..d3f80a9b 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs @@ -41,6 +41,7 @@ namespace AsbCloudInfrastructure.Services.SAUB public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache) { + // TODO: make this background work var contextOptions = new DbContextOptionsBuilder() .UseNpgsql(configuration.GetConnectionString("DefaultConnection")) .Options; diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs similarity index 86% rename from AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs rename to AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs index 7f241ca3..9efc29a8 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeCalcWorkFactory.cs @@ -1,9 +1,9 @@ using AsbCloudDb.Model; using AsbCloudDb.Model.Subsystems; +using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Services.Subsystems.Utils; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Data; @@ -16,56 +16,30 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Subsystems { #nullable enable - internal class SubsystemOperationTimeBackgroundService : BackgroundService + internal static class SubsystemOperationTimeCalcWorkFactory { - private readonly string connectionString; - private readonly TimeSpan period = TimeSpan.FromHours(1); + private const string workId = "Subsystem operation time calc"; + private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); + private const int idSubsytemTorqueMaster = 65537; private const int idSubsytemSpinMaster = 65536; private const int idSubsytemAkb = 1; private const int idSubsytemMse = 2; - public SubsystemOperationTimeBackgroundService(IConfiguration configuration) + public static WorkPeriodic MakeWork() { - connectionString = configuration.GetConnectionString("DefaultConnection"); - } - - protected override async Task ExecuteAsync(CancellationToken token) - { - var timeToStart = DateTime.Now; - var options = new DbContextOptionsBuilder() - .UseNpgsql(connectionString) - .Options; - while (!token.IsCancellationRequested) + var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) { - if (DateTime.Now > timeToStart) - { - timeToStart = DateTime.Now + period; - try - { - using var context = new AsbCloudDbContext(options); - var added = await OperationTimeAllTelemetriesAsync(context, token); - Trace.TraceInformation($"Total subsystem operation time complete. Added {added} operations time."); - } - catch (Exception ex) - { - Trace.TraceError(ex.Message); - } - GC.Collect(); - } - var ms = (int)(timeToStart - DateTime.Now).TotalMilliseconds; - ms = ms > 100 ? ms : 100; - await Task.Delay(ms, token).ConfigureAwait(false); - } + Timeout = TimeSpan.FromMinutes(30) + }; + return workPeriodic; } - public override async Task StopAsync(CancellationToken token) + // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. + private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) { - await base.StopAsync(token).ConfigureAwait(false); - } + using var db = serviceProvider.GetRequiredService(); - private static async Task OperationTimeAllTelemetriesAsync(IAsbCloudDbContext db, CancellationToken token) - { var lastDetectedDates = await db.SubsystemOperationTimes .GroupBy(o => o.IdTelemetry) .Select(g => new @@ -90,23 +64,21 @@ namespace AsbCloudInfrastructure.Services.Subsystems inner.SingleOrDefault()?.LastDate, }); - var affected = 0; foreach (var item in telemetryLastDetectedDates) { var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSaub?.Any() == true) { db.SubsystemOperationTimes.AddRange(newOperationsSaub); - affected += await db.SaveChangesAsync(token); + await db.SaveChangesAsync(token); } var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSpin?.Any() == true) { db.SubsystemOperationTimes.AddRange(newOperationsSpin); - affected += await db.SaveChangesAsync(token); + await db.SaveChangesAsync(token); } } - return affected; } private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index c8dc24a9..01768371 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -1,9 +1,14 @@ using AsbCloudApp.Services; using AsbCloudDb.Model; +using AsbCloudInfrastructure.Services.DetectOperations; +using AsbCloudInfrastructure.Services.Subsystems; +using AsbCloudInfrastructure.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; +using System.Threading.Tasks; +using System.Threading; namespace AsbCloudInfrastructure { @@ -12,13 +17,22 @@ namespace AsbCloudInfrastructure public static void BeforeRunHandler(IHost host) { using var scope = host.Services.CreateScope(); - var context = scope.ServiceProvider.GetService(); - context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60)); + var provider = scope.ServiceProvider; + var context = provider.GetService(); + context.Database.SetCommandTimeout(TimeSpan.FromSeconds(2 * 60)); context.Database.Migrate(); - var wellService = scope.ServiceProvider.GetService(); - wellService.EnshureTimezonesIsSetAsync(System.Threading.CancellationToken.None).Wait(); + var wellService = provider.GetRequiredService(); + wellService.EnshureTimezonesIsSetAsync(CancellationToken.None).Wait();// TODO: make this background work + + var backgroundWorker = provider.GetRequiredService(); + backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork()); + backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork()); + backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork()); + + Task.Delay(1_000) + .ContinueWith(async (_) => await backgroundWorker.StartAsync(CancellationToken.None)); } } } From b05aadccb4b8b1a9ff6a9657ab23203a3fb1e1c6 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 2 Dec 2022 17:44:47 +0500 Subject: [PATCH 09/14] Add memory monitor --- AsbCloudInfrastructure/Startup.cs | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 01768371..09e92644 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Hosting; using System; using System.Threading.Tasks; using System.Threading; +using AsbCloudInfrastructure.Background; namespace AsbCloudInfrastructure { @@ -30,9 +31,42 @@ namespace AsbCloudInfrastructure backgroundWorker.Push(OperationDetectionWorkFactory.MakeWork()); backgroundWorker.Push(SubsystemOperationTimeCalcWorkFactory.MakeWork()); backgroundWorker.Push(LimitingParameterCalcWorkFactory.MakeWork()); + backgroundWorker.Push(MakeMemoryMonitoringWork()); Task.Delay(1_000) .ContinueWith(async (_) => await backgroundWorker.StartAsync(CancellationToken.None)); } + + static WorkPeriodic MakeMemoryMonitoringWork() + { + var workId = "Memory monitoring"; + var workAction = (string _, IServiceProvider _, CancellationToken _) => { + var bytes = GC.GetTotalMemory(false); + var bytesString = FromatBytes(bytes); + System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes"); + return Task.CompletedTask; + }; + var workPeriod = TimeSpan.FromMinutes(1); + var work = new WorkPeriodic(workId, workAction, workPeriod); + return work; + } + + static string FromatBytes(long bytes) + { + const double gigaByte = 1024 * 1024 * 1024; + const double megaByte = 1024 * 1024; + const double kiloByte = 1024; + + if (bytes > 10 * gigaByte) + return (bytes / gigaByte).ToString("### ### ###.## Gb"); + + if (bytes > 10 * megaByte) + return (bytes / megaByte).ToString("### ### ###.## Mb"); + + if (bytes > 10 * kiloByte) + return (bytes / megaByte).ToString("### ### ###.## Kb"); + + return bytes.ToString("### ### ###"); + } } } From 85b99135e6f34bc4128ec9464b353518c68e9546 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 5 Dec 2022 08:47:00 +0500 Subject: [PATCH 10/14] nit tests refactoring --- .../BackgroundWorkerServiceTest.cs | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs index 24b8e999..133f6b36 100644 --- a/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs +++ b/AsbCloudWebApi.Tests/ServicesTests/BackgroundWorkerServiceTest.cs @@ -1,20 +1,20 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using System; -using AsbCloudInfrastructure.Services.Background; +using AsbCloudInfrastructure.Background; using System.Threading; using System.Threading.Tasks; using Xunit; namespace AsbCloudWebApi.Tests.ServicesTests { - public class BackgroundWorkerServiceTest + public class BackgroundWorkerTest { private readonly Mock mockServiceProvider; private readonly Mock mockServiceScopeFactory; private readonly Func someAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask; - public BackgroundWorkerServiceTest() + public BackgroundWorkerTest() { var mockServiceScope = new Mock(); mockServiceScopeFactory = new Mock(); @@ -32,20 +32,20 @@ namespace AsbCloudWebApi.Tests.ServicesTests { mockServiceScopeFactory.Invocations.Clear(); - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); const string work1Id = "long name 1"; const string work2Id = "long name 2"; var work1 = new WorkBase(work1Id, someAction); var work2 = new WorkPeriodic(work2Id, someAction, TimeSpan.Zero); - backgroundService.Push(work1); - backgroundService.Push(work2); + BackgroundWorker.Push(work1); + BackgroundWorker.Push(work2); - Assert.True(backgroundService.Contains(work1Id)); - Assert.True(backgroundService.Contains(work2Id)); - Assert.False(backgroundService.Contains(work2Id + work1Id)); - Assert.False(backgroundService.Contains(string.Empty)); + Assert.True(BackgroundWorker.Contains(work1Id)); + Assert.True(BackgroundWorker.Contains(work2Id)); + Assert.False(BackgroundWorker.Contains(work2Id + work1Id)); + Assert.False(BackgroundWorker.Contains(string.Empty)); } [Fact] @@ -53,10 +53,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests { mockServiceScopeFactory.Invocations.Clear(); - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var work = new WorkBase("", someAction); - backgroundService.Push(work); - await backgroundService.StartAsync(CancellationToken.None); + BackgroundWorker.Push(work); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(10); mockServiceScopeFactory.Verify(f => f.CreateScope()); @@ -65,15 +65,15 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task Makes_primary_work_done() { - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var workDone = false; var work = new WorkBase("", (_, _, _) => { workDone = true; return Task.CompletedTask; }); - backgroundService.Push(work); - await backgroundService.StartAsync(CancellationToken.None); + BackgroundWorker.Push(work); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(10); Assert.True(workDone); @@ -82,10 +82,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task Sets_ExecutionTime_after_work_done() { - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var work = new WorkBase("", someAction); - backgroundService.Push(work); - await backgroundService.StartAsync(CancellationToken.None); + BackgroundWorker.Push(work); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(10); Assert.True(work.ExecutionTime > TimeSpan.Zero); @@ -94,7 +94,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task Makes_periodic_work_done() { - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var workDone = false; var work = new WorkPeriodic("", (_, _, _) => { @@ -102,8 +102,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests return Task.CompletedTask; }, TimeSpan.FromMilliseconds(10)); - backgroundService.Push(work); - await backgroundService.StartAsync(CancellationToken.None); + BackgroundWorker.Push(work); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(20); Assert.True(workDone); @@ -112,7 +112,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task Does_not_start_periodic_work() { - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var workDone = false; var work = new WorkPeriodic("", (_, _, _) => { @@ -123,9 +123,9 @@ namespace AsbCloudWebApi.Tests.ServicesTests { LastStart = DateTime.Now }; - backgroundService.Push(work); + BackgroundWorker.Push(work); - await backgroundService.StartAsync(CancellationToken.None); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(20); Assert.False(workDone); @@ -152,11 +152,11 @@ namespace AsbCloudWebApi.Tests.ServicesTests return Task.CompletedTask; }); - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); - backgroundService.Push(work2); - backgroundService.Push(work1); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); + BackgroundWorker.Push(work2); + BackgroundWorker.Push(work1); - await backgroundService.StartAsync(CancellationToken.None); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(2_100); Assert.True(work2Order < work1Order); @@ -174,12 +174,12 @@ namespace AsbCloudWebApi.Tests.ServicesTests return Task.CompletedTask; }, TimeSpan.FromMilliseconds(1)); - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); - backgroundService.Push(work1); - backgroundService.Push(work2); - backgroundService.Delete("1"); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); + BackgroundWorker.Push(work1); + BackgroundWorker.Push(work2); + BackgroundWorker.Delete("1"); - await backgroundService.StartAsync(CancellationToken.None); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(10); Assert.True(workDone); @@ -188,7 +188,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task Aborts_long_work() { - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var workCanceled = false; var work = new WorkBase("", async (_, _, token) => await Task.Delay(1000000, token)) { @@ -200,8 +200,8 @@ namespace AsbCloudWebApi.Tests.ServicesTests } }; - backgroundService.Push(work); - await backgroundService.StartAsync(CancellationToken.None); + BackgroundWorker.Push(work); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(20 * 4); Assert.True(workCanceled); @@ -210,7 +210,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests [Fact] public async Task Execution_continues_after_work_exception() { - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); var work2done = false; var work1 = new WorkBase("1", (_, _, _) => throw new Exception()); var work2 = new WorkBase("2", (_, _, _) => @@ -219,10 +219,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests return Task.CompletedTask; }); - backgroundService.Push(work1); - backgroundService.Push(work2); + BackgroundWorker.Push(work1); + BackgroundWorker.Push(work2); - await backgroundService.StartAsync(CancellationToken.None); + await BackgroundWorker.StartAsync(CancellationToken.None); await Task.Delay(2_100); Assert.True(work2done); @@ -234,11 +234,11 @@ namespace AsbCloudWebApi.Tests.ServicesTests var work1 = new WorkPeriodic("1", someAction, TimeSpan.FromSeconds(30)); var work2 = new WorkBase("1", someAction); - var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); - backgroundService.Push(work1); + var BackgroundWorker = new BackgroundWorker(mockServiceProvider.Object); + BackgroundWorker.Push(work1); Assert.Throws( - () => backgroundService.Push(work2)); + () => BackgroundWorker.Push(work2)); } } } From 289d1831b4d3402e3bb6086bf89d8600f2fbe13d Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 5 Dec 2022 09:36:02 +0500 Subject: [PATCH 11/14] CacheBase makes cache for TEntity only. Converter moved to CrudCacheRepositoryBase --- .../Repository/CacheBase.cs | 26 ++++------- .../Repository/CrudCacheRepositoryBase.cs | 46 ++++++++++++------- .../CrudWellRelatedCacheServiceBase.cs | 5 +- .../Services/FileCategoryService.cs | 3 +- .../Services/WellService.cs | 16 +++---- 5 files changed, 52 insertions(+), 44 deletions(-) diff --git a/AsbCloudInfrastructure/Repository/CacheBase.cs b/AsbCloudInfrastructure/Repository/CacheBase.cs index 88761ddb..042f2517 100644 --- a/AsbCloudInfrastructure/Repository/CacheBase.cs +++ b/AsbCloudInfrastructure/Repository/CacheBase.cs @@ -1,7 +1,4 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Services; -using AsbCloudDb.Model; -using Mapster; +using AsbCloudDb.Model; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; @@ -12,12 +9,12 @@ using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository { - public class CacheBase : QueryContainer - where TDto : AsbCloudApp.Data.IId +#nullable enable + public class CacheBase : QueryContainer where TEntity : class, AsbCloudDb.Model.IId { protected readonly IMemoryCache memoryCache; - protected string CacheTag = typeof(TDto).Name; + protected string CacheTag = typeof(TEntity).Name; protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5); public CacheBase(IAsbCloudDbContext context, IMemoryCache memoryCache) @@ -35,7 +32,7 @@ namespace AsbCloudInfrastructure.Repository protected virtual void DropCache() => memoryCache.Remove(CacheTag); - protected virtual IEnumerable GetCache() + protected virtual IEnumerable GetCache() { var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => { @@ -43,13 +40,12 @@ namespace AsbCloudInfrastructure.Repository cacheEntry.SlidingExpiration = CacheOlescence; var entities = this.GetQuery().ToArray(); - var dtos = entities.Select(Convert); - return dtos.ToArray(); + return entities; }); return cache; } - protected virtual Task> GetCacheAsync(CancellationToken token) + protected virtual Task> GetCacheAsync(CancellationToken token) { var cache = memoryCache.GetOrCreateAsync(CacheTag, async (cacheEntry) => { @@ -57,14 +53,10 @@ namespace AsbCloudInfrastructure.Repository cacheEntry.SlidingExpiration = CacheOlescence; var entities = await this.GetQuery().ToArrayAsync(token); - var dtos = entities.Select(Convert); - return dtos.ToArray().AsEnumerable(); + return entities.AsEnumerable(); }); return cache; } - - protected virtual TDto Convert(TEntity src) => src.Adapt(); - - protected virtual TEntity Convert(TDto src) => src.Adapt(); } +#nullable disable } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs b/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs index 30aaf673..ebea5d96 100644 --- a/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudCacheRepositoryBase.cs @@ -1,5 +1,6 @@ using AsbCloudApp.Services; using AsbCloudDb.Model; +using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; @@ -16,7 +17,7 @@ namespace AsbCloudInfrastructure.Repository /// /// /// - public class CrudCacheRepositoryBase : CacheBase, ICrudRepository + public class CrudCacheRepositoryBase : CacheBase, ICrudRepository where TDto : AsbCloudApp.Data.IId where TEntity : class, IId { @@ -36,7 +37,7 @@ namespace AsbCloudInfrastructure.Repository } /// - public async Task InsertAsync(TDto newItem, CancellationToken token) + public virtual async Task InsertAsync(TDto newItem, CancellationToken token) { var result = await crudServiceBase.InsertAsync(newItem, token); if (result > 0) @@ -45,7 +46,7 @@ namespace AsbCloudInfrastructure.Repository } /// - public async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) + public virtual async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) { var result = await crudServiceBase.InsertRangeAsync(dtos, token); if (result > 0) @@ -53,11 +54,21 @@ namespace AsbCloudInfrastructure.Repository return result; } + /// + public virtual async Task UpdateAsync(TDto dto, CancellationToken token) + { + var result = await crudServiceBase.UpdateAsync(dto, token); + if (result > 0) + DropCache(); + return result; + } + /// public async Task> GetAllAsync(CancellationToken token) { var cache = await GetCacheAsync(token); - return cache; + var dtos = cache.Select(Convert); + return dtos; } /// @@ -68,33 +79,36 @@ namespace AsbCloudInfrastructure.Repository public TDto? GetOrDefault(int id) { var cache = GetCache(); - return cache.FirstOrDefault(d => d.Id == id); + var cacheItem = cache.FirstOrDefault(d => d.Id == id); + if (cacheItem is null) + return default; + var dto = Convert(cacheItem); + return dto; } /// public async Task GetOrDefaultAsync(int id, CancellationToken token) { var cache = await GetCacheAsync(token); - return cache.FirstOrDefault(d => d.Id == id); + var cacheItem = cache.FirstOrDefault(d => d.Id == id); + if (cacheItem is null) + return default; + var dto = Convert(cacheItem); + return dto; } /// - public async Task UpdateAsync(TDto dto, CancellationToken token) - { - var result = await crudServiceBase.UpdateAsync(dto, token); - if (result > 0) - DropCache(); - return result; - } - - /// - public async Task DeleteAsync(int id, CancellationToken token) + public virtual async Task DeleteAsync(int id, CancellationToken token) { var result = await crudServiceBase.DeleteAsync(id, token); if (result > 0) DropCache(); return result; } + + protected virtual TDto Convert(TEntity src) => src.Adapt(); + + protected virtual TEntity Convert(TDto src) => src.Adapt(); } #nullable disable } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs index 29362c89..9ac2c8b7 100644 --- a/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs @@ -27,7 +27,7 @@ namespace AsbCloudInfrastructure.Repository var dtos = cache .Where(e => e.IdWell == idWell) - .ToList(); + .Select(Convert); return dtos; } @@ -41,7 +41,8 @@ namespace AsbCloudInfrastructure.Repository var dtos = cache .Where(e => idsWells.Contains(e.IdWell)) - .ToList(); + .Select(Convert); + return dtos; } } diff --git a/AsbCloudInfrastructure/Services/FileCategoryService.cs b/AsbCloudInfrastructure/Services/FileCategoryService.cs index e7370ee8..b50c9418 100644 --- a/AsbCloudInfrastructure/Services/FileCategoryService.cs +++ b/AsbCloudInfrastructure/Services/FileCategoryService.cs @@ -22,7 +22,8 @@ namespace AsbCloudInfrastructure.Services .ConfigureAwait(false); var dtos = cache .Where(f => f.Id >= 10000) - .Where(f => f.Id <= 20000); + .Where(f => f.Id <= 20000) + .Select(Convert); return dtos; } diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs index 8967ad78..3d73dbe4 100644 --- a/AsbCloudInfrastructure/Services/WellService.cs +++ b/AsbCloudInfrastructure/Services/WellService.cs @@ -81,12 +81,13 @@ namespace AsbCloudInfrastructure.Services .Select(r => r.IdWell); var wellsDtos = (await GetCacheAsync(token)) - .Where(w => wellsIds.Contains(w.Id)); + .Where(w => wellsIds.Contains(w.Id)) + .Select(Convert); - return wellsDtos.ToList(); + return wellsDtos; } - public async Task InsertAsync(WellDto dto, CancellationToken token = default) + public override async Task InsertAsync(WellDto dto, CancellationToken token = default) { if (dto.IdWellType is < 1 or > 2) throw new ArgumentInvalidException("Тип скважины указан неправильно.", nameof(dto)); @@ -112,12 +113,12 @@ namespace AsbCloudInfrastructure.Services return result; } - public Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) + public override Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) { throw new NotImplementedException(); } - public async Task UpdateAsync(WellDto dto, + public override async Task UpdateAsync(WellDto dto, CancellationToken token = default) { if (dto.IdWellType is < 1 or > 2) @@ -154,9 +155,8 @@ namespace AsbCloudInfrastructure.Services public async Task GetWellCaptionByIdAsync(int idWell, CancellationToken token) { - var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false); - var dto = Convert(entity); - return dto.Caption; + var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false); + return entity!.Caption; } public async Task> GetCompaniesAsync(int idWell, CancellationToken token) From 8094c8b1b845048d8ae20b300149aa0840da4b9c Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 5 Dec 2022 10:53:24 +0500 Subject: [PATCH 12/14] Decrease TelemetryDataCache size. --- AsbCloudDb/Model/AsbCloudDbContext.cs | 2 +- AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs | 2 +- AsbCloudInfrastructure/Startup.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AsbCloudDb/Model/AsbCloudDbContext.cs b/AsbCloudDb/Model/AsbCloudDbContext.cs index 97df2748..8354d87f 100644 --- a/AsbCloudDb/Model/AsbCloudDbContext.cs +++ b/AsbCloudDb/Model/AsbCloudDbContext.cs @@ -61,7 +61,7 @@ namespace AsbCloudDb.Model public DbSet Record60 => Set(); public DbSet Record61 => Set(); - public int ReferenceCount { get; private set; } + public static int ReferenceCount { get; private set; } public AsbCloudDbContext() : base() { diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index af2d471a..00c583c1 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -14,7 +14,7 @@ namespace AsbCloudInfrastructure.Services.SAUB public class TelemetryDataCache where TDto : AsbCloudApp.Data.ITelemetryData { - private const int activeWellCapacity = 24 * 60 * 60; + private const int activeWellCapacity = 12 * 60 * 60; private const int doneWellCapacity = 65 * 60; private readonly ConcurrentDictionary> caches; diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 09e92644..36a85cc1 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -43,7 +43,7 @@ namespace AsbCloudInfrastructure var workAction = (string _, IServiceProvider _, CancellationToken _) => { var bytes = GC.GetTotalMemory(false); var bytesString = FromatBytes(bytes); - System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes"); + System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDb.Model.AsbCloudDbContext.ReferenceCount}"); return Task.CompletedTask; }; var workPeriod = TimeSpan.FromMinutes(1); From 84dfa183d79ed93dccfa0dd9545dff50a72c0168 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 5 Dec 2022 10:53:24 +0500 Subject: [PATCH 13/14] Decrease TelemetryDataCache size. --- AsbCloudDb/Model/AsbCloudDbContext.cs | 2 +- AsbCloudInfrastructure/Background/BackgroundWorker.cs | 5 +---- AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs | 2 +- AsbCloudInfrastructure/Startup.cs | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/AsbCloudDb/Model/AsbCloudDbContext.cs b/AsbCloudDb/Model/AsbCloudDbContext.cs index 97df2748..8354d87f 100644 --- a/AsbCloudDb/Model/AsbCloudDbContext.cs +++ b/AsbCloudDb/Model/AsbCloudDbContext.cs @@ -61,7 +61,7 @@ namespace AsbCloudDb.Model public DbSet Record60 => Set(); public DbSet Record61 => Set(); - public int ReferenceCount { get; private set; } + public static int ReferenceCount { get; private set; } public AsbCloudDbContext() : base() { diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index 531d775e..9453e6a9 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -1,7 +1,4 @@ -using AsbCloudInfrastructure.Services.DetectOperations; -using AsbCloudInfrastructure.Services.Subsystems; -using AsbCloudInfrastructure.Services; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Diagnostics; diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs index af2d471a..00c583c1 100644 --- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs +++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataCache.cs @@ -14,7 +14,7 @@ namespace AsbCloudInfrastructure.Services.SAUB public class TelemetryDataCache where TDto : AsbCloudApp.Data.ITelemetryData { - private const int activeWellCapacity = 24 * 60 * 60; + private const int activeWellCapacity = 12 * 60 * 60; private const int doneWellCapacity = 65 * 60; private readonly ConcurrentDictionary> caches; diff --git a/AsbCloudInfrastructure/Startup.cs b/AsbCloudInfrastructure/Startup.cs index 09e92644..36a85cc1 100644 --- a/AsbCloudInfrastructure/Startup.cs +++ b/AsbCloudInfrastructure/Startup.cs @@ -43,7 +43,7 @@ namespace AsbCloudInfrastructure var workAction = (string _, IServiceProvider _, CancellationToken _) => { var bytes = GC.GetTotalMemory(false); var bytesString = FromatBytes(bytes); - System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes"); + System.Diagnostics.Trace.TraceInformation($"Total memory allocated is {bytesString} bytes. DbContext count is:{AsbCloudDb.Model.AsbCloudDbContext.ReferenceCount}"); return Task.CompletedTask; }; var workPeriod = TimeSpan.FromMinutes(1); From 29d4121440bb878023ffc25ef1be39b71690a288 Mon Sep 17 00:00:00 2001 From: "ai.astrakhantsev" Date: Tue, 6 Dec 2022 10:03:24 +0500 Subject: [PATCH 14/14] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BE=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B8=D0=B2=D0=B0?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D1=85=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D1=81?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/ILimitingParameterService.cs | 6 ++++++ .../Services/LimitingParameterService.cs | 7 ++++++- .../LimitingParameterController.cs | 12 +++++++++++ .../SubsystemOperationTimeController.cs | 20 ++++++++++++++++++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/AsbCloudApp/Services/ILimitingParameterService.cs b/AsbCloudApp/Services/ILimitingParameterService.cs index 3e82eba1..5f752a62 100644 --- a/AsbCloudApp/Services/ILimitingParameterService.cs +++ b/AsbCloudApp/Services/ILimitingParameterService.cs @@ -19,6 +19,12 @@ namespace AsbCloudApp.Services /// /// Task> GetStatAsync(LimitingParameterRequest request, CancellationToken token); + + /// + /// Получение списка ограничений + /// + /// + Dictionary GetLimitingParameteraNames(); } #nullable disable } diff --git a/AsbCloudInfrastructure/Services/LimitingParameterService.cs b/AsbCloudInfrastructure/Services/LimitingParameterService.cs index c50729f8..0a609507 100644 --- a/AsbCloudInfrastructure/Services/LimitingParameterService.cs +++ b/AsbCloudInfrastructure/Services/LimitingParameterService.cs @@ -15,7 +15,7 @@ namespace AsbCloudInfrastructure.Services { private readonly ILimitingParameterRepository limitingParameterRepository; private readonly IWellService wellService; - private readonly Dictionary feedRegulatorData = new Dictionary() + private readonly Dictionary feedRegulatorData = new () { { 0, "Нет ограничения" }, { 1, "МСП" }, @@ -63,6 +63,11 @@ namespace AsbCloudInfrastructure.Services return result; } + public Dictionary GetLimitingParameteraNames() //TODO: Перенести получение ограничений в репозиторий + { + return feedRegulatorData; + } + private IEnumerable TrimLimitingParameters(IEnumerable data, LimitingParameterRequest request) { var result = data.Select((x) => diff --git a/AsbCloudWebApi/Controllers/LimitingParameterController.cs b/AsbCloudWebApi/Controllers/LimitingParameterController.cs index 1df008e8..9bd5784d 100644 --- a/AsbCloudWebApi/Controllers/LimitingParameterController.cs +++ b/AsbCloudWebApi/Controllers/LimitingParameterController.cs @@ -40,6 +40,18 @@ namespace AsbCloudWebApi.Controllers return Ok(subsystemResult); } + /// + /// Получение словаря названий ограничений + /// + /// + [HttpGet("names")] + [ProducesResponseType(typeof(Dictionary), (int)System.Net.HttpStatusCode.OK)] + public IActionResult GetLimitingParameteraNames() + { + var feedRegulatorData = limitingParameterService.GetLimitingParameteraNames(); + return Ok(feedRegulatorData); + } + protected async Task UserHasAccesToWellAsync(int idWell, CancellationToken token) { var idCompany = User.GetCompanyId(); diff --git a/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs b/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs index e2b1a945..65f503ea 100644 --- a/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs +++ b/AsbCloudWebApi/Controllers/Subsystems/SubsystemOperationTimeController.cs @@ -3,7 +3,6 @@ using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudApp.Services.Subsystems; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Threading; @@ -25,6 +24,14 @@ namespace AsbCloudWebApi.Controllers.Subsystems private readonly ISubsystemOperationTimeService subsystemOperationTimeService; private readonly IWellService wellService; private readonly ISubsystemService subsystemService; + + private readonly Dictionary subsystemNames = new() + { + { 1, "SAUB" }, + { 65537, "Torque Master" }, + { 65536, "Spin Master" } + }; + public SubsystemOperationTimeController(ISubsystemOperationTimeService subsystemOperationTimeService, IWellService wellService, ISubsystemService subsystemService) { this.subsystemOperationTimeService = subsystemOperationTimeService; @@ -131,6 +138,17 @@ namespace AsbCloudWebApi.Controllers.Subsystems return Ok(result); } + /// + /// Получение словаря названий подсистем + /// + /// + [HttpGet("names")] + [ProducesResponseType(typeof(Dictionary), (int)System.Net.HttpStatusCode.OK)] + public IActionResult GetSubsystemsNames() + { + return Ok(subsystemNames); + } + protected async Task UserHasAccesToWellAsync(int idWell, CancellationToken token) { var idCompany = User.GetCompanyId();