diff --git a/AsbCloudApp/Data/SAUB/TelemetryUserDto.cs b/AsbCloudApp/Data/SAUB/TelemetryUserDto.cs
index 5fb67dc0..ee2504e6 100644
--- a/AsbCloudApp/Data/SAUB/TelemetryUserDto.cs
+++ b/AsbCloudApp/Data/SAUB/TelemetryUserDto.cs
@@ -1,4 +1,6 @@
-namespace AsbCloudApp.Data.SAUB
+using AsbCloudDb.Model;
+
+namespace AsbCloudApp.Data.SAUB
{
///
/// Пользователь панели оператора
@@ -27,5 +29,26 @@
/// Уровень доступа
///
public int Level { get; set; }
+
+ ///
+ /// Собрать отображаемое имя пользователя
+ ///
+ ///
+ public string MakeDisplayName()
+ {
+ if (!string.IsNullOrEmpty(Surname))
+ {
+ var s = Surname;
+ if (!string.IsNullOrEmpty(Name))
+ {
+ s += $"{Name[0]}.";
+ if (!string.IsNullOrEmpty(Patronymic))
+ s += $" {Patronymic[0]}.";
+ }
+ return s;
+ }
+ else
+ return $"User #{Id}";
+ }
}
}
diff --git a/AsbCloudApp/Services/ITelemetryUserService.cs b/AsbCloudApp/Services/ITelemetryUserService.cs
index aa529634..558f24c5 100644
--- a/AsbCloudApp/Services/ITelemetryUserService.cs
+++ b/AsbCloudApp/Services/ITelemetryUserService.cs
@@ -1,15 +1,33 @@
using AsbCloudApp.Data.SAUB;
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
+#nullable enable
///
/// сервис пользователей телеметрии
///
public interface ITelemetryUserService
{
+ ///
+ /// get user by ids
+ ///
+ ///
+ ///
+ ///
+ TelemetryUserDto? GetOrDefault(int idTelemetry, int idUser);
+
+ ///
+ /// get users by id telemetry and predicate
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetUsers(int idTelemetry, Func? predicate = default);
+
///
/// получает и сохраняет/обновляет список пользователей панели оператора
///
@@ -19,4 +37,5 @@ namespace AsbCloudApp.Services
///
Task UpsertAsync(string uid, IEnumerable dtos, CancellationToken token = default);
}
+#nullable disable
}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/CacheExtentions.cs b/AsbCloudInfrastructure/CacheExtentions.cs
new file mode 100644
index 00000000..28e791e9
--- /dev/null
+++ b/AsbCloudInfrastructure/CacheExtentions.cs
@@ -0,0 +1,44 @@
+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
+{
+ public static class MemoryCacheExtentions
+ {
+ private static readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
+
+ public static Task> GetOrCreateBasicAsync(this IMemoryCache memoryCache, IAsbCloudDbContext dbContext, CancellationToken token)
+ where T : class
+ {
+ var cacheTag = typeof(T).FullName;
+ var cache = memoryCache.GetOrCreateAsync(cacheTag, async (cacheEntry) => {
+ cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
+ cacheEntry.SlidingExpiration = CacheOlescence;
+
+ var entities = await dbContext.Set().ToArrayAsync(token);
+ return entities.AsEnumerable();
+ });
+ return cache;
+ }
+
+ public static IEnumerable GetOrCreateBasic(this IMemoryCache memoryCache, IAsbCloudDbContext dbContext)
+ where T : class
+ {
+ var cacheTag = typeof(T).FullName;
+ var cache = memoryCache.GetOrCreate(cacheTag, cacheEntry => {
+ cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
+ cacheEntry.SlidingExpiration = CacheOlescence;
+
+ var entities = dbContext.Set().ToArray();
+ return entities.AsEnumerable();
+ });
+ return cache;
+ }
+ }
+}
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index cc3d096f..90c1bf5c 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -8,7 +8,6 @@ using AsbCloudDb.Model;
using AsbCloudDb.Model.Subsystems;
using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services;
-using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.DailyReport;
using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services.DrillingProgram;
@@ -16,10 +15,10 @@ using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudInfrastructure.Services.Subsystems;
using AsbCloudInfrastructure.Services.WellOperationService;
using AsbCloudInfrastructure.Validators;
-using DocumentFormat.OpenXml.Spreadsheet;
using FluentValidation.AspNetCore;
using Mapster;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
@@ -94,6 +93,7 @@ namespace AsbCloudInfrastructure
// TODO: переместить FluentValidation в описание моделей
services.AddFluentValidationClientsideAdapters();
+ services.AddMemoryCache();
services.AddScoped(provider => provider.GetService());
services.AddScoped();
@@ -101,7 +101,6 @@ namespace AsbCloudInfrastructure
services.AddHostedService();
services.AddHostedService();
services.AddSingleton(new WitsInfoService());
- services.AddSingleton(new CacheDb());
services.AddSingleton(new InstantDataRepository());
services.AddSingleton(provider=> TelemetryDataCache.GetInstance(configuration));
services.AddSingleton(provider=> TelemetryDataCache.GetInstance(configuration));
@@ -143,21 +142,25 @@ namespace AsbCloudInfrastructure
services.AddTransient, CrudServiceBase>(s =>
new CrudCacheServiceBase(
s.GetService(),
+ s.GetService(),
dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService
services.AddTransient, DrillParamsService>();
services.AddTransient, CrudCacheServiceBase>(s =>
new CrudCacheServiceBase(
s.GetService(),
+ s.GetService(),
dbSet => dbSet.Include(d => d.Clusters)));
services.AddTransient, CrudCacheServiceBase>(s =>
new CrudCacheServiceBase(
s.GetService(),
+ s.GetService(),
dbSet => dbSet.Include(c => c.CompanyType)));
services.AddTransient, CrudCacheServiceBase>();
services.AddTransient, CrudCacheServiceBase>(s =>
new CrudCacheServiceBase(
s.GetService(),
+ s.GetService(),
dbSet => dbSet
.Include(c => c.Wells)
.Include(c => c.Deposit))); // может быть включен в сервис ClusterService
diff --git a/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs
index ae9c1fed..2624d39c 100644
--- a/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs
+++ b/AsbCloudInfrastructure/Repository/CrudCacheServiceBase.cs
@@ -1,6 +1,6 @@
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.EfCache;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -21,16 +21,21 @@ namespace AsbCloudInfrastructure.Repository
{
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)
- : base(dbContext) { }
+ public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
+ : base(dbContext)
+ {
+ this.memoryCache = memoryCache;
+ }
- public CrudCacheServiceBase(IAsbCloudDbContext dbContext, ISet includes)
- : base(dbContext, includes) { }
-
- public CrudCacheServiceBase(IAsbCloudDbContext dbContext, Func, IQueryable> makeQuery)
- : base(dbContext, makeQuery) { }
+ public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func, IQueryable> makeQuery)
+ : base(dbContext, makeQuery)
+ {
+ this.memoryCache = memoryCache;
+ }
///
public override async Task InsertAsync(TDto newItem, CancellationToken token)
@@ -103,16 +108,33 @@ namespace AsbCloudInfrastructure.Repository
}
protected virtual Task> GetCacheAsync(CancellationToken token)
- => GetQuery()
- .FromCacheAsync(CacheTag, CacheOlescence, Convert, 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()
- => GetQuery()
- .FromCache(CacheTag, CacheOlescence, Convert);
+ {
+ 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()
- => dbSet.DropCache(CacheTag);
+ => memoryCache.Remove(CacheTag);
}
#nullable disable
}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Repository/CrudServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudServiceBase.cs
index c6e96d09..67b750cc 100644
--- a/AsbCloudInfrastructure/Repository/CrudServiceBase.cs
+++ b/AsbCloudInfrastructure/Repository/CrudServiceBase.cs
@@ -31,20 +31,6 @@ namespace AsbCloudInfrastructure.Repository
GetQuery = () => dbSet;
}
- public CrudServiceBase(IAsbCloudDbContext dbContext, ISet includes)
- {
- this.dbContext = dbContext;
- dbSet = dbContext.Set();
-
- GetQuery = () =>
- {
- IQueryable query = dbSet;
- foreach (var include in includes)
- query = query.Include(include);
- return query;
- };
- }
-
public CrudServiceBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery)
{
dbContext = context;
diff --git a/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs
index 77729a13..a7eb3305 100644
--- a/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs
+++ b/AsbCloudInfrastructure/Repository/CrudWellRelatedCacheServiceBase.cs
@@ -1,6 +1,7 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,14 +15,11 @@ namespace AsbCloudInfrastructure.Repository
where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated
where TEntity : class, IId, IWellRelated
{
- public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context)
- : base(context) { }
+ public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, IMemoryCache memoryCache)
+ : base(context, memoryCache) { }
- public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext dbContext, ISet includes)
- : base(dbContext, includes) { }
-
- public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery)
- : base(context, makeQuery) { }
+ public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, IMemoryCache memoryCache, Func, IQueryable> makeQuery)
+ : base(context, memoryCache, makeQuery) { }
public async Task?> GetByIdWellAsync(int idWell, CancellationToken token)
{
diff --git a/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs b/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs
index 0e9a3ad8..b79277cc 100644
--- a/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs
+++ b/AsbCloudInfrastructure/Repository/CrudWellRelatedServiceBase.cs
@@ -17,9 +17,6 @@ namespace AsbCloudInfrastructure.Repository
public CrudWellRelatedServiceBase(IAsbCloudDbContext context)
: base(context) { }
- public CrudWellRelatedServiceBase(IAsbCloudDbContext dbContext, ISet includes)
- : base(dbContext, includes) { }
-
public CrudWellRelatedServiceBase(IAsbCloudDbContext context, Func, IQueryable> makeQuery)
: base(context, makeQuery) { }
diff --git a/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs b/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs
index 3519a8e0..c9379244 100644
--- a/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs
+++ b/AsbCloudInfrastructure/Repository/SetpointsRequestRepository.cs
@@ -2,6 +2,7 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
namespace AsbCloudInfrastructure.Repository
@@ -10,8 +11,8 @@ namespace AsbCloudInfrastructure.Repository
{
private readonly IWellService wellService;
- public SetpointsRequestRepository(IAsbCloudDbContext dbContext, IWellService wellService)
- : base(dbContext, q => q.Include(s => s.Author)
+ public SetpointsRequestRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, IWellService wellService)
+ : base(dbContext, memoryCache, q => q.Include(s => s.Author)
.Include(s => s.Well))
{
this.wellService = wellService;
diff --git a/AsbCloudInfrastructure/Repository/WitsRecordRepository.cs b/AsbCloudInfrastructure/Repository/WitsRecordRepository.cs
index cc53d92e..35c2fabb 100644
--- a/AsbCloudInfrastructure/Repository/WitsRecordRepository.cs
+++ b/AsbCloudInfrastructure/Repository/WitsRecordRepository.cs
@@ -88,7 +88,7 @@ namespace AsbCloudInfrastructure.Repository
foreach (var entity in entities)
{
- if (!existingEntities.Any(e => e.DateTime == entity.DateTime))
+ if (!existingEntities.Any(e => e == entity.DateTime))
{
dbset.Add(entity);
}
diff --git a/AsbCloudInfrastructure/Services/Cache/CacheDb.cs b/AsbCloudInfrastructure/Services/Cache/CacheDb.cs
deleted file mode 100644
index f8f56f0c..00000000
--- a/AsbCloudInfrastructure/Services/Cache/CacheDb.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace AsbCloudInfrastructure.Services.Cache
-{
- public class CacheDb
- {
- private readonly ConcurrentDictionary cache =
- new ConcurrentDictionary();
-
- public CacheTable GetCachedTable(DbContext context, params string[] includes)
- where TEntity : class
- => GetCachedTable(context, new SortedSet(includes));
-
- public CacheTable GetCachedTable(DbContext context, ISet includes = null)
- where TEntity : class
- {
- var cacheItem = GetCacheTableDataStore();
- var tableCache = new CacheTable(context, cacheItem, includes);
- return tableCache;
- }
-
- public CacheTable GetCachedTable(DbContext context, Func, IQueryable> configureDbSet)
- where TEntity : class
- {
- var cacheItem = GetCacheTableDataStore();
- var tableCache = new CacheTable(context, cacheItem, configureDbSet);
- return tableCache;
- }
-
- private CacheTableDataStore GetCacheTableDataStore()
- where TEntity : class
- {
- var nameOfTEntity = typeof(TEntity).FullName;
- var cacheItem = cache.GetOrAdd(nameOfTEntity, (nameOfTEntity) => new CacheTableDataStore
- {
- NameOfTEntity = nameOfTEntity,
- Entities = new List(),
- });
- return cacheItem;
- }
-
- public void DropAll() => cache.Clear();
-
- public void Drop() => cache.Remove(typeof(TEntity).FullName, out _);
- }
-}
diff --git a/AsbCloudInfrastructure/Services/Cache/CacheTable.cs b/AsbCloudInfrastructure/Services/Cache/CacheTable.cs
deleted file mode 100644
index 46ff4572..00000000
--- a/AsbCloudInfrastructure/Services/Cache/CacheTable.cs
+++ /dev/null
@@ -1,449 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace AsbCloudInfrastructure.Services.Cache
-{
- public class CacheTable : IEnumerable
- where TEntity : class
- {
- private const int semaphoreTimeout = 5_000;
- private static readonly SemaphoreSlim semaphore = new(1);
- private static readonly TimeSpan minPeriodRefresh = TimeSpan.FromSeconds(5);
- private static readonly string nameOfTEntity = typeof(TEntity).Name;
-
- private readonly CacheTableDataStore data;
- private readonly Func, IQueryable> configureDbSet;
- private readonly List cached;
- private readonly DbContext context;
- private readonly DbSet dbSet;
-
- internal CacheTable(DbContext context, CacheTableDataStore data, ISet includes = null)
- {
- this.context = context;
- this.data = data;
- dbSet = context.Set();
-
- if (includes?.Any() == true)
- configureDbSet = (DbSet dbSet) =>
- {
- IQueryable result = dbSet;
- foreach (var include in includes)
- result = result.Include(include);
- return result;
- };
-
- cached = (List)data.Entities;
- if ((cached.Count == 0) || data.IsObsolete)
- Refresh(false);
- }
-
- internal CacheTable(DbContext context, CacheTableDataStore data,
- Func, IQueryable> configureDbSet = null)
- {
- this.context = context;
- this.data = data;
- this.configureDbSet = configureDbSet;
-
- dbSet = context.Set();
-
- cached = (List)data.Entities;
- if ((cached.Count == 0) || data.IsObsolete)
- Refresh(false);
- }
-
- public TEntity this[int index]
- {
- get => cached.ElementAt(index);
- }
-
- ///
- /// Runs action like atomic operation.
- /// wasFree is action argument indicates that semaphore was free.
- /// It may be needed to avoid multiple operations like Refresh().
- ///
- /// (wasFree) => {...}
- /// default if semaphoreTimeout. Or result of func(..)
- private static T Sync(Func func)
- {
- var wasFree = semaphore.CurrentCount > 0;
- T result = default;
- if (func is null || !semaphore.Wait(semaphoreTimeout))
- return result;
-
- try
- {
- result = func.Invoke(wasFree);
- }
- catch (Exception ex)
- {
- Trace.WriteLine($"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{nameOfTEntity}>.Sync()");
- Trace.WriteLine(ex.Message);
- Trace.WriteLine(ex.StackTrace);
- }
- finally
- {
- semaphore.Release();
- }
-
- return result;
- }
-
- ///
- /// Runs action like atomic operation.
- /// wasFree is action argument indicates that semaphore was free.
- /// It may be needed to avoid multiple operations like Refresh().
- ///
- /// (wasFree) => {...}
- /// default if semaphoreTimeout. Or result of func(..)
- private static async Task SyncAsync(Func> funcAsync,
- CancellationToken token = default)
- {
- var wasFree = semaphore.CurrentCount > 0;
- T result = default;
-
- if (funcAsync is null || !await semaphore.WaitAsync(semaphoreTimeout, token).ConfigureAwait(false))
- return result;
-
- try
- {
- result = await funcAsync.Invoke(wasFree, token);
- }
- catch (Exception ex)
- {
- Trace.WriteLine(
- $"{DateTime.Now:yyyy.MM.dd HH:mm:ss:fff} error in CacheTable<{nameOfTEntity}>.SyncAsync()");
- Trace.WriteLine(ex.Message);
- Trace.WriteLine(ex.StackTrace);
- }
- finally
- {
- semaphore.Release();
- }
-
- return result;
- }
-
- private int InternalRefresh(bool force)
- {
- if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
- {
- cached.Clear();
- IQueryable query = configureDbSet is null ? dbSet : configureDbSet(dbSet);
- var entities = query.AsNoTracking().ToList();
- //Trace.WriteLine($"CacheTable<{nameOfTEntity}> refresh");
- cached.AddRange(entities);
- data.LastResreshDate = DateTime.Now;
- }
-
- return cached.Count;
- }
-
- private async Task InternalRefreshAsync(bool force, CancellationToken token = default)
- {
- if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
- {
- cached.Clear();
- IQueryable query = configureDbSet is null ? dbSet : configureDbSet(dbSet);
- var entities = await query.AsNoTracking()
- .ToListAsync(token).ConfigureAwait(false);
- //Trace.WriteLine($"CacheTable<{nameOfTEntity}> refreshAsync");
- cached.AddRange(entities);
- data.LastResreshDate = DateTime.Now;
- }
-
- return cached.Count;
- }
-
- public int Refresh(bool force)
- => Sync((wasFree) => wasFree ? InternalRefresh(force) : 0);
-
- public Task RefreshAsync(bool force, CancellationToken token = default)
- {
- return SyncAsync(
- async (wasFree, token) =>
- {
- return wasFree ? await InternalRefreshAsync(force, token) : 0;
- }, token);
- }
-
- public bool Contains(Func predicate)
- => FirstOrDefault(predicate) != default;
-
- public async Task ContainsAsync(Func predicate, CancellationToken token = default)
- => await FirstOrDefaultAsync(predicate, token) != default;
-
- public TEntity GetOrCreate(Func predicate, Func makeNew)
- => Sync(wasFree =>
- {
- var result = cached.FirstOrDefault(predicate);
- if (result != default)
- return result;
-
- InternalRefresh(true);
- result = cached.FirstOrDefault(predicate);
- if (result != default)
- return result;
-
- var entry = dbSet.Add(makeNew());
- context.SaveChanges();
- InternalRefresh(true);
- return entry.Entity;
- });
-
- public TEntity FirstOrDefault()
- {
- var result = cached.FirstOrDefault();
- if (result != default)
- return result;
-
- Refresh(false);
- return cached.FirstOrDefault();
- }
-
- public async Task FirstOrDefaultAsync(CancellationToken token = default)
- {
- var result = cached.FirstOrDefault();
- if (result != default)
- return result;
-
- await RefreshAsync(false, token);
- return cached.FirstOrDefault();
- }
-
- public TEntity FirstOrDefault(Func predicate)
- {
- var result = cached.FirstOrDefault(predicate);
- if (result != default)
- return result;
-
- Refresh(false);
- return cached.FirstOrDefault(predicate);
- }
-
- public async Task FirstOrDefaultAsync(Func predicate, CancellationToken token = default)
- {
- var result = cached.FirstOrDefault(predicate);
- if (result != default)
- return result;
-
- await RefreshAsync(false, token);
- return cached.FirstOrDefault(predicate);
- }
-
- public IEnumerable Where(Func predicate = default)
- {
- var result = (predicate != default)
- ? cached.Where(predicate)
- : cached;
- if (result.Any())
- return result;
-
- Refresh(false);
- result = (predicate != default)
- ? cached.Where(predicate)
- : cached;
- return result;
- }
-
- public Task> WhereAsync(CancellationToken token = default) =>
- WhereAsync(default, token);
-
- public async Task> WhereAsync(Func predicate = default,
- CancellationToken token = default)
- {
- var result = (predicate != default)
- ? cached.Where(predicate)
- : cached;
- if (result.Any())
- return result;
-
- await RefreshAsync(false, token);
- result = (predicate != default)
- ? cached.Where(predicate)
- : cached;
- return result;
- }
-
- public int Upsert(TEntity entity)
- {
- if (entity == default)
- return 0;
- return Sync((wasFree) =>
- {
- if (dbSet.Contains(entity))
- dbSet.Update(entity);
- else
- dbSet.Add(entity);
- var affected = context.SaveChanges();
- if (affected > 0)
- InternalRefresh(true);
- return affected;
- });
- }
-
- public Task UpsertAsync(TEntity entity, CancellationToken token = default)
- => SyncAsync(async (wasFree, token) =>
- {
- if (dbSet.Contains(entity))
- dbSet.Update(entity);
- else
- dbSet.Add(entity);
- var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
- if (affected > 0)
- await InternalRefreshAsync(true, token);
- return affected;
- }, token);
-
- public int Upsert(IEnumerable entities)
- {
- if (!entities.Any())
- return 0;
-
- return Sync((wasFree) =>
- {
- foreach (var entity in entities)
- {
- if (dbSet.Contains(entity)) // TODO: это очень медленно
- dbSet.Update(entity);
- else
- dbSet.Add(entity);
- }
-
- var affected = context.SaveChanges();
- if (affected > 0)
- InternalRefresh(true);
- return affected;
- });
- }
-
- public Task UpsertAsync(IEnumerable entities, CancellationToken token = default)
- {
- if (!entities.Any())
- return Task.FromResult(0);
-
- return SyncAsync(async (wasFree, token) =>
- {
- var upsertedEntries = new List(entities.Count());
- foreach (var entity in entities)
- {
- if (dbSet.Contains(entity))
- dbSet.Update(entity);
- else
- dbSet.Add(entity);
- }
-
- var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
- if (affected > 0)
- await InternalRefreshAsync(true, token);
- return affected;
- }, token);
- }
-
- public int Remove(Func predicate)
- => Sync(_ =>
- {
- dbSet.RemoveRange(dbSet.Where(predicate));
- var affected = context.SaveChanges();
- if (affected > 0)
- InternalRefresh(true);
- return affected;
- });
-
- public Task RemoveAsync(Func predicate, CancellationToken token = default)
- => SyncAsync(async (wasFree, token) =>
- {
- dbSet.RemoveRange(dbSet.Where(predicate));
- var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
- if (affected > 0)
- await InternalRefreshAsync(true, token);
- return affected;
- }, token);
-
- public TEntity Insert(TEntity entity)
- {
- return Sync(_ =>
- {
- var entry = dbSet.Add(entity);
- var affected = context.SaveChanges();
- if (affected > 0)
- InternalRefresh(true);
- return entry.Entity;
- });
- }
-
- public Task InsertAsync(TEntity entity, CancellationToken token = default)
- {
- return SyncAsync(async (wasFree, token) =>
- {
- var entry = dbSet.Add(entity);
- var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
- if (affected > 0)
- await InternalRefreshAsync(true, token);
- return entry.Entity;
- }, token);
- }
-
- public IEnumerable Insert(IEnumerable newEntities)
- {
- if (newEntities is null)
- return null;
- var count = newEntities.Count();
- if (count == 0)
- return null;
-
- return Sync(_ =>
- {
- var entries = new List>(count);
- foreach (var newEntity in newEntities)
- {
- var entry = dbSet.Add(newEntity);
- entries.Add(entry);
- }
-
- var affected = context.SaveChanges();
- if (affected > 0)
- InternalRefresh(true);
- else
- return null;
-
- return entries.Select(e => e.Entity);
- });
- }
-
- public Task> InsertAsync(IEnumerable newEntities, CancellationToken token = default)
- {
- if (newEntities is null)
- return null;
- var count = newEntities.Count();
- if (count == 0)
- return null;
-
- return SyncAsync(async (wasFree, token) =>
- {
- var entries = new List>(count);
- foreach (var newEntity in newEntities)
- {
- var entry = dbSet.Add(newEntity);
- entries.Add(entry);
- }
- var affected = await context.SaveChangesAsync(token).ConfigureAwait(false);
- if (affected > 0)
- await InternalRefreshAsync(true, token);
- else
- return null;
-
- return entries.Select(e => e.Entity);
- }, token);
- }
-
- public IEnumerator GetEnumerator() => Where().GetEnumerator();
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
- }
-}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/Cache/CacheTableDataStore.cs b/AsbCloudInfrastructure/Services/Cache/CacheTableDataStore.cs
deleted file mode 100644
index 3c2fad37..00000000
--- a/AsbCloudInfrastructure/Services/Cache/CacheTableDataStore.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections;
-
-namespace AsbCloudInfrastructure.Services.Cache
-{
- class CacheTableDataStore
- {
- public string NameOfTEntity { get; set; }
- public DateTime LastResreshDate { get; set; }
-
- //public ISet Includes { get; set; } //TODO: this prop change should update entities
- public IEnumerable Entities { get; set; }
- public TimeSpan ObsolesenceTime { get; set; } = TimeSpan.FromMinutes(15);
- public bool IsObsolete => (DateTime.Now - LastResreshDate > ObsolesenceTime);
-
- }
-}
diff --git a/AsbCloudInfrastructure/Services/FileCategoryService.cs b/AsbCloudInfrastructure/Services/FileCategoryService.cs
index b1976e64..8721ef25 100644
--- a/AsbCloudInfrastructure/Services/FileCategoryService.cs
+++ b/AsbCloudInfrastructure/Services/FileCategoryService.cs
@@ -2,6 +2,7 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
+using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -11,10 +12,8 @@ namespace AsbCloudInfrastructure.Services
{
public class FileCategoryService : CrudCacheServiceBase, IFileCategoryService
{
- public FileCategoryService(IAsbCloudDbContext context)
- : base(context)
- {
- }
+ public FileCategoryService(IAsbCloudDbContext context, IMemoryCache memoryCache)
+ : base(context, memoryCache) { }
//TODO: Перенести в сервис "дело скважины"
public async Task> GetWellCaseCategoriesAsync(CancellationToken token)
diff --git a/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs b/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs
index ce297d83..4d9c4769 100644
--- a/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/SetpointsService.cs
@@ -3,6 +3,7 @@ using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -30,9 +31,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
private readonly SetpointsRequestRepository setpointsRepository;
private readonly ITelemetryService telemetryService;
- public SetpointsService(IAsbCloudDbContext db, ITelemetryService telemetryService, IWellService wellService)
+ public SetpointsService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, IWellService wellService)
{
- setpointsRepository = new SetpointsRequestRepository(db, wellService);
+ setpointsRepository = new SetpointsRequestRepository(db, memoryCache, wellService);
this.telemetryService = telemetryService;
}
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs
index e918025a..941bc012 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataBaseService.cs
@@ -1,7 +1,6 @@
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
@@ -19,18 +18,15 @@ namespace AsbCloudInfrastructure.Services.SAUB
{
protected readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService;
- protected readonly CacheTable cacheTelemetryUsers;
private readonly TelemetryDataCache telemetryDataCache;
public TelemetryDataBaseService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
- TelemetryDataCache telemetryDataCache,
- CacheDb cacheDb)
+ TelemetryDataCache telemetryDataCache)
{
this.db = db;
this.telemetryService = telemetryService;
- cacheTelemetryUsers = cacheDb.GetCachedTable((AsbCloudDbContext)db);
this.telemetryDataCache = telemetryDataCache;
}
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs
index 10a13913..aa6067b3 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs
@@ -1,32 +1,33 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
-using DocumentFormat.OpenXml.Drawing.Charts;
using Mapster;
-using Microsoft.EntityFrameworkCore;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB
{
+#nullable enable
public class TelemetryDataSaubService : TelemetryDataBaseService
{
+ private readonly ITelemetryUserService telemetryUserService;
+
public TelemetryDataSaubService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
- TelemetryDataCache telemetryDataCache,
- CacheDb cacheDb)
- : base(db, telemetryService, telemetryDataCache, cacheDb)
- { }
+ ITelemetryUserService telemetryUserService,
+ TelemetryDataCache telemetryDataCache)
+ : base(db, telemetryService, telemetryDataCache)
+ {
+ this.telemetryUserService = telemetryUserService;
+ }
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
{
var entity = src.Adapt();
- var telemetryUser = cacheTelemetryUsers?
- .FirstOrDefault(u => u.IdTelemetry == src.IdTelemetry && (u.Name == src.User || u.Surname == src.User));
- entity.IdUser = telemetryUser?.IdUser;
+ var telemetryUser = telemetryUserService
+ .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
+ .FirstOrDefault();
+ entity.IdUser = telemetryUser?.Id;
entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
return entity;
}
@@ -34,11 +35,11 @@ namespace AsbCloudInfrastructure.Services.SAUB
public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
{
var dto = src.Adapt();
- var telemetryUser = cacheTelemetryUsers?
- .FirstOrDefault(u => u.IdTelemetry == src.IdTelemetry && u.IdUser == src.IdUser);
+ var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser??int.MinValue);
dto.User = telemetryUser?.MakeDisplayName();
dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
return dto;
}
}
+#nullable disable
}
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSpinService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSpinService.cs
index 14e84955..64862dcc 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSpinService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSpinService.cs
@@ -1,10 +1,7 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using Mapster;
-using System.Threading;
-using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB
{
@@ -13,9 +10,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryDataSpinService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
- TelemetryDataCache telemetryDataCache,
- CacheDb cacheDb)
- : base(db, telemetryService, telemetryDataCache, cacheDb)
+ TelemetryDataCache telemetryDataCache)
+ : base(db, telemetryService, telemetryDataCache)
{ }
public override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset)
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs
index f8574152..b2e15216 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryService.cs
@@ -17,7 +17,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
#nullable enable
public class TelemetryService : ITelemetryService
{
- private const string telemetryCacheTag = "telemetryCache";
+ private const string CacheTag = "TelemetryCache";
private static readonly TimeSpan telemetryCacheObsolescence = TimeSpan.FromMinutes(5);
private readonly IAsbCloudDbContext db;
@@ -39,15 +39,15 @@ namespace AsbCloudInfrastructure.Services.SAUB
private IEnumerable GetTelemetryCache()
{
- var cache = db.Telemetries
+ var cache = db.Set()
.Include(t => t.Well)
- .FromCache(telemetryCacheTag, telemetryCacheObsolescence);
+ .FromCache(CacheTag, telemetryCacheObsolescence);
return cache;
}
private void DropTelemetryCache()
{
- db.Telemetries.DropCache(telemetryCacheTag);
+ db.Telemetries.DropCache(CacheTag);
}
public DateTime GetLastTelemetryDate(int idTelemetry)
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs
index 99dce9d6..190094a1 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryTracker.cs
@@ -1,8 +1,8 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Concurrent;
@@ -39,14 +39,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
private readonly ConcurrentDictionary telemetriesStats;
- public TelemetryTracker(CacheDb cacheDb, IConfiguration configuration)
+ public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache)
{
var contextOptions = new DbContextOptionsBuilder()
.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
.Options;
var db = new AsbCloudDbContext(contextOptions);
- var cacheTelemetry = cacheDb.GetCachedTable(db);
+ var cacheTelemetry = memoryCache.GetOrCreateBasic(db);
var keyValuePairs = new Dictionary(cacheTelemetry.Count());
foreach (var telemetry in cacheTelemetry)
{
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryUserService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryUserService.cs
index 2406a2b0..f5740a3c 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryUserService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryUserService.cs
@@ -3,7 +3,9 @@ using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.EfCache;
+using Mapster;
+using Microsoft.Extensions.Caching.Memory;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -11,15 +13,49 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB
{
+#nullable enable
public class TelemetryUserService : ITelemetryUserService
{
+ private const string CacheTag = "TelemetryUserCacheTag";
+ private readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
+
private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService;
+ private readonly IMemoryCache memoryCache;
- public TelemetryUserService(IAsbCloudDbContext db, ITelemetryService telemetryService)
+ public TelemetryUserService(IAsbCloudDbContext db,
+ ITelemetryService telemetryService,
+ IMemoryCache memoryCache)
{
this.db = db;
this.telemetryService = telemetryService;
+ this.memoryCache = memoryCache;
+ }
+
+ public TelemetryUserDto? GetOrDefault(int idTelemetry, int idUser)
+ {
+ var entity = GetCache()
+ .FirstOrDefault(u => u.IdTelemetry == idTelemetry && u.IdUser == idUser);
+
+ if(entity is null)
+ return null;
+
+ return Convert(entity);
+ }
+
+ public IEnumerable GetUsers(int idTelemetry, Func? predicate = null)
+ {
+ var entities = GetCache()
+ .Where(u => u.IdTelemetry == idTelemetry);
+
+ foreach (var entity in entities)
+ {
+ var dto = Convert(entity);
+ if(predicate?.Invoke(dto)??true)
+ yield return dto;
+ }
+
+ yield break;
}
public async Task UpsertAsync(string uid, IEnumerable dtos, CancellationToken token = default)
@@ -29,17 +65,37 @@ namespace AsbCloudInfrastructure.Services.SAUB
var telemetryId = telemetryService.GetOrCreateTelemetryIdByUid(uid);
- var entities = dtos.Distinct(new TelemetryUserDtoComparer()).Select(dto => new TelemetryUser
- {
- IdUser = dto.Id,
- IdTelemetry = telemetryId,
- Level = dto.Level,
- Name = dto.Name,
- Patronymic = dto.Patronymic,
- Surname = dto.Surname,
+ var entities = dtos.Distinct(new TelemetryUserDtoComparer()).Select(dto => {
+ var entity = dto.Adapt();
+ entity.IdUser = dto.Id;
+ entity.IdTelemetry = telemetryId;
+ return entity;
});
var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryUsers, entities, token);
- db.TelemetryUsers.DropCache();
+ DropCache();
+ }
+
+ private IEnumerable GetCache()
+ {
+ var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => {
+ cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
+ cacheEntry.SlidingExpiration = CacheOlescence;
+
+ var entities = db.Set().ToArray();
+ return entities;
+ });
+ return cache;
+ }
+
+ private void DropCache()
+ => memoryCache.Remove(CacheTag);
+
+ private static TelemetryUserDto Convert(TelemetryUser entity)
+ {
+ var dto = entity.Adapt();
+ dto.Id = entity.IdUser;
+ return dto;
}
}
+#nullable disable
}
diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs
index 749dbbdf..6491265a 100644
--- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs
+++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs
@@ -6,6 +6,7 @@ using AsbCloudDb.Model.Subsystems;
using AsbCloudInfrastructure.Repository;
using Mapster;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -17,7 +18,7 @@ namespace AsbCloudInfrastructure.Services.Subsystems
internal class SubsystemService : CrudCacheServiceBase, ISubsystemService
{
private readonly IWellService wellService;
- public SubsystemService(IAsbCloudDbContext dbContext, IWellService wellService) : base(dbContext)
+ public SubsystemService(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, IWellService wellService) : base(dbContext, memoryCache)
{
this.wellService = wellService;
}
diff --git a/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs b/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs
index c82b9f7a..ec417604 100644
--- a/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs
+++ b/AsbCloudInfrastructure/Services/WellFinalDocumentsService.cs
@@ -121,7 +121,7 @@ namespace AsbCloudInfrastructure.Services
var result = new WellCaseDto
{
IdWell = idWell,
- PermissionToSetPubliher = userRepository.HasPermission(idUser, "WellFinalDocument.editPublisher"),
+ PermissionToSetPubliher = userRepository.HasPermission(idUser, "WellFinalDocuments.editPublisher"),
WellFinalDocuments = docs,
};
return result;
diff --git a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs
index 1eeb1e3a..dd85deef 100644
--- a/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs
+++ b/AsbCloudInfrastructure/Services/WellOperationService/OperationsStatService.cs
@@ -1,9 +1,9 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,18 +15,14 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
public class OperationsStatService : IOperationsStatService
{
private readonly IAsbCloudDbContext db;
+ private readonly IMemoryCache memoryCache;
private readonly IWellService wellService;
- private readonly CacheTable cacheSectionsTypes;
- private readonly CacheTable cacheWellType;
- private readonly CacheTable cacheCluster;
- public OperationsStatService(IAsbCloudDbContext db, CacheDb cache, IWellService wellService)
+ public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
{
this.db = db;
+ this.memoryCache = memoryCache;
this.wellService = wellService;
- cacheSectionsTypes = cache.GetCachedTable((DbContext)db);
- cacheWellType = cache.GetCachedTable((DbContext)db);
- cacheCluster = cache.GetCachedTable((DbContext)db);
}
public async Task GetStatClusterAsync(int idCluster, int idCompany, CancellationToken token = default)
@@ -44,7 +40,9 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
var statsWells = await GetWellsStatAsync(wells, token).ConfigureAwait(false);
- var cluster = await cacheCluster.FirstOrDefaultAsync(c => c.Id == idCluster, token);
+ var cluster = (await memoryCache
+ .GetOrCreateBasicAsync(db, token))
+ .FirstOrDefault(c => c.Id == idCluster);
var statClusterDto = new StatClusterDto
{
Id = idCluster,
@@ -133,7 +131,9 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
private async Task CalcWellStatAsync(Well well, CancellationToken token = default)
{
- var wellType = await cacheWellType.FirstOrDefaultAsync(t => t.Id == well.IdWellType, token);
+ var wellType = (await memoryCache
+ .GetOrCreateBasicAsync(db, token))
+ .FirstOrDefault(t => t.Id == well.IdWellType);
var statWellDto = new StatWellDto()
{
Id = well.Id,
@@ -169,7 +169,8 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
.Select(o => o.IdWellSectionType)
.Distinct();
- var sectionTypes = cacheSectionsTypes
+ var sectionTypes = memoryCache
+ .GetOrCreateBasic(db)
.Where(s => sectionTypeIds.Contains(s.Id))
.ToDictionary(s => s.Id);
diff --git a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs
index 3b1ef55a..f1ae1144 100644
--- a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs
+++ b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs
@@ -1,9 +1,9 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -16,9 +16,8 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
public class WellOperationService : IWellOperationService
{
private readonly IAsbCloudDbContext db;
+ private readonly IMemoryCache memoryCache;
private readonly IWellService wellService;
- private readonly CacheTable cachedOperationCategories;
- private readonly CacheTable cachedSectionTypes;
private Dictionary? firstOperationsCache = null;
@@ -33,22 +32,25 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
public const int idOperationTypeFact = 1;
public const int idOperationTypeRepair = 1031;
- public WellOperationService(IAsbCloudDbContext db, CacheDb cache, IWellService wellService)
+ public WellOperationService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
{
this.db = db;
+ this.memoryCache = memoryCache;
this.wellService = wellService;
- cachedOperationCategories = cache.GetCachedTable((DbContext)db);
- cachedSectionTypes = cache.GetCachedTable((DbContext)db);
}
public IDictionary GetSectionTypes()
- => cachedSectionTypes.ToDictionary(s => s.Id, s => s.Caption);
+ => memoryCache
+ .GetOrCreateBasic(db)
+ .ToDictionary(s => s.Id, s => s.Caption);
public IEnumerable GetCategories()
{
- var operationTypes = cachedOperationCategories
- .Distinct().OrderBy(o => o.Name);
- var result = operationTypes.Adapt>();
+ var operationCategories = memoryCache
+ .GetOrCreateBasic(db)
+ .Distinct()
+ .OrderBy(o => o.Name);
+ var result = operationCategories.Adapt>();
return result;
}
diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs
index 2b33d7bf..c96672f7 100644
--- a/AsbCloudInfrastructure/Services/WellService.cs
+++ b/AsbCloudInfrastructure/Services/WellService.cs
@@ -4,9 +4,9 @@ using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using AsbCloudInfrastructure.Repository;
-using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -34,16 +34,16 @@ namespace AsbCloudInfrastructure.Services
.Include(w => w.Telemetry)
.Include(w => w.WellType)
.Include(w => w.RelationCompaniesWells)
- .ThenInclude(r => r.Company);
+ .ThenInclude(r => r.Company);
- public WellService(IAsbCloudDbContext db, CacheDb cacheDb, ITelemetryService telemetryService, ITimezoneService timezoneService)
- : base(db, MakeQueryWell)
+ public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService)
+ : base(db, memoryCache, MakeQueryWell)
{
this.telemetryService = telemetryService;
this.timezoneService = timezoneService;
- this.wellOperationService = new WellOperationService.WellOperationService(db, cacheDb, this);
- companyTypesService = new CrudCacheServiceBase(dbContext);
+ this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this);
+ companyTypesService = new CrudCacheServiceBase(dbContext, memoryCache);
}
private IEnumerable GetCacheRelationCompanyWell()
@@ -167,13 +167,6 @@ namespace AsbCloudInfrastructure.Services
return dtos;
}
- private IEnumerable GetCompanies(int idWell)
- {
- var relations = GetCacheRelationCompanyWell().Where(r => r.IdWell == idWell);
- var dtos = relations.Select(r => Convert(r.Company));
- return dtos;
- }
-
public string GetStateText(int state)
{
return state switch
@@ -339,7 +332,7 @@ namespace AsbCloudInfrastructure.Services
throw new Exception($"Well id: {idWell} does not exist.");
if (well.IdTelemetry is null)
- throw new Exception($"Well id: {idWell} does not contain telemetry.");
+ throw new KeyNotFoundException($"Well id: {idWell} does not contain telemetry.");
return telemetryService.GetDatesRange((int)well.IdTelemetry);
}
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs
index 91664385..e95c7cc3 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/DepositCrudCacheServiceTest.cs
@@ -22,7 +22,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
protected override ICrudService MakeService()
{
var dbContext = TestHelpter.MakeRealTestContext();
- return new CrudCacheServiceBase(dbContext);
+ return new CrudCacheServiceBase(dbContext, TestHelpter.MemoryCache);
}
}
}
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DetectedOperationServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DetectedOperationServiceTest.cs
index f341a15a..00bca2aa 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/DetectedOperationServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/DetectedOperationServiceTest.cs
@@ -3,8 +3,6 @@ using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
-using AsbCloudInfrastructure.Services;
-using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.DetectOperations;
using Moq;
using System;
@@ -19,7 +17,6 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public class DetectedOperationServiceTest
{
private readonly AsbCloudDbContext context;
- private readonly CacheDb cacheDb;
private readonly DetectedOperationService service;
private readonly DetectedOperationRequest request;
private Deposit deposit = new Deposit { Id = 1, Caption = "Депозит 1" };
diff --git a/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs
index 7c1e60ff..f829c76c 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/DrillerServiceTest.cs
@@ -1,10 +1,5 @@
using AsbCloudApp.Data;
-using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
-using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Repository;
-using AsbCloudInfrastructure.Services;
-using AsbCloudInfrastructure.Services.Cache;
using Moq;
using System.Collections.Generic;
using System.Linq;
diff --git a/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs
index 0c406f1e..b13cab4e 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/EventServiceTest.cs
@@ -1,7 +1,6 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.SAUB;
using Moq;
using System.Collections.Generic;
@@ -14,13 +13,11 @@ namespace AsbCloudWebApi.Tests.ServicesTests;
public class EventServiceTest
{
private readonly AsbCloudDbContext context;
- private readonly CacheDb cacheDb;
private readonly EventService service;
public EventServiceTest()
{
context = TestHelpter.MakeRealTestContext();
- cacheDb = new CacheDb();
var telemetryTracker = new Mock();
var imezoneServiceMock = new Mock();
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object);
diff --git a/AsbCloudWebApi.Tests/ServicesTests/FileCategoryServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/FileCategoryServiceTest.cs
index 345f2693..12874dea 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/FileCategoryServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/FileCategoryServiceTest.cs
@@ -1,7 +1,5 @@
-using AsbCloudApp.Data;
-using AsbCloudDb.Model;
+using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services;
-using AsbCloudInfrastructure.Services.Cache;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -19,7 +17,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
context = TestHelpter.MakeRealTestContext();
context.SaveChanges();
- service = new FileCategoryService(context);
+ service = new FileCategoryService(context, TestHelpter.MemoryCache);
}
~FileCategoryServiceTest()
diff --git a/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs b/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs
index 46d9136d..6d395cf2 100644
--- a/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs
+++ b/AsbCloudWebApi.Tests/ServicesTests/TelemetryDataSaubServiceTest.cs
@@ -2,7 +2,6 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.SAUB;
using Moq;
using System;
@@ -20,9 +19,9 @@ namespace AsbCloudWebApi.Tests.ServicesTests
private readonly Mock timezoneService;
private readonly SimpleTimezoneDto timezone;
private readonly AsbCloudDbContext context;
- private readonly CacheDb cacheDb;
private readonly TelemetryService telemetryService;
-
+ private readonly TelemetryUserService telemetryUserService;
+ private readonly TelemetryDataCache telemetryDataSaubCache;
private readonly DateTime drillingStartDate;
private readonly string uuid;
public TelemetryDataSaubServiceTest()
@@ -41,9 +40,9 @@ namespace AsbCloudWebApi.Tests.ServicesTests
.Returns(timezone);
context = TestHelpter.MakeRealTestContext();
- cacheDb = new CacheDb();
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object);
-
+ telemetryUserService = new TelemetryUserService(context, telemetryService, TestHelpter.MemoryCache);
+ telemetryDataSaubCache = TelemetryDataCache.GetInstance(context, out Task _);
var info = new TelemetryInfoDto
{
TimeZoneOffsetTotalHours = timezone.Hours,
@@ -64,7 +63,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public async Task UpdateDataAsync()
{
// Arrange
- var telemetryDataSaubService = new TelemetryDataSaubService(context, telemetryService, null, cacheDb);
+ var telemetryDataSaubService = new TelemetryDataSaubService(context, telemetryService, telemetryUserService, telemetryDataSaubCache);
var now = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(timezone.Hours)).DateTime;
var tuser = "Завулон";
diff --git a/AsbCloudWebApi.Tests/TestHelpter.cs b/AsbCloudWebApi.Tests/TestHelpter.cs
index 94bf1392..7df75b49 100644
--- a/AsbCloudWebApi.Tests/TestHelpter.cs
+++ b/AsbCloudWebApi.Tests/TestHelpter.cs
@@ -1,25 +1,27 @@
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using Moq;
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using System.Linq.Expressions;
+using System.Threading;
+using System.Threading.Tasks;
namespace AsbCloudWebApi.Tests
{
internal static class TestHelpter
{
- // Попробовать когда-нибудь https://github.com/MichalJankowskii/Moq.EntityFrameworkCore
+ public static IMemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions());
public static AsbCloudDbContext MakeRealTestContext()
{
var options = new DbContextOptionsBuilder()
- //.UseInMemoryDatabase(System.Guid.NewGuid().ToString())
- //.ConfigureWarnings(configBuilder =>
- // configBuilder.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.UseNpgsql("Host=localhost;Database=tests;Username=postgres;Password=q;Persist Security Info=True;Include Error Detail=True")
.Options;
var context = new AsbCloudDbContext(options);
- //context.Database.EnsureDeleted();
context.Database.EnsureCreated();
return context;
}
@@ -31,22 +33,82 @@ namespace AsbCloudWebApi.Tests
contextMock.Setup(o => o.Set())
.Returns(() => dbSetMock.Object);
+ var dbSetProperty = typeof(IAsbCloudDbContext)
+ .GetProperties()
+ .FirstOrDefault(p => p.PropertyType.IsPublic && p.PropertyType == typeof(DbSet));
+
+ if (dbSetProperty is not null)
+ {
+ // https://learn.microsoft.com/ru-ru/dotnet/api/system.linq.expressions.expression?view=net-7.0
+ var objParameterExpr = Expression.Parameter(typeof(IAsbCloudDbContext), "instance");
+ var propertyExpr = Expression.Property(objParameterExpr, dbSetProperty);
+ var expression = Expression.Lambda>>(propertyExpr, objParameterExpr);
+ contextMock.SetupGet(expression).Returns(dbSetMock.Object);
+ }
+
return contextMock;
}
public static Mock> MakeDbSetMock()
where T : class
+ => MakeDbSetMock(new List());
+
+ class DummyAsyncQueriable : IQueryable, IAsyncEnumerable
{
- var dbSetData = new List();
- return MakeDbSetMock(dbSetData);
+ private readonly IQueryable queriable;
+
+ public Type ElementType => queriable.ElementType;
+
+ public Expression Expression => queriable.Expression;
+
+ public IQueryProvider Provider => queriable.Provider;
+
+ class QueriableAsyncEminaretor : IAsyncEnumerator
+ {
+ private readonly IEnumerator syncEnumerator;
+
+ public QueriableAsyncEminaretor(IEnumerator syncEnumerator)
+ {
+ this.syncEnumerator = syncEnumerator;
+ }
+
+ public T2 Current => syncEnumerator.Current;
+
+ public ValueTask DisposeAsync()
+ {
+ syncEnumerator.Dispose();
+ return ValueTask.CompletedTask;
+ }
+
+ public ValueTask MoveNextAsync()
+ {
+ var result = syncEnumerator.MoveNext();
+ return ValueTask.FromResult(result);
+ }
+ }
+
+ public DummyAsyncQueriable(IEnumerable dbSetData)
+ {
+ queriable = dbSetData.ToList().AsQueryable();
+ }
+
+ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
+ {
+ var enumerator = this.AsEnumerable().GetEnumerator();
+ return new QueriableAsyncEminaretor(enumerator);
+ }
+
+ public IEnumerator GetEnumerator()
+ => queriable.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => queriable.GetEnumerator();
}
public static Mock> MakeDbSetMock(IEnumerable dbSetData)
where T : class
{
- var dbSetDataQueriable = dbSetData
- .ToList()
- .AsQueryable();
+ var dbSetDataQueriable = new DummyAsyncQueriable(dbSetData);
Mock> dbSetMock = new();
dbSetMock.As>().Setup(o => o.Provider).Returns(() => dbSetDataQueriable.Provider);
@@ -54,6 +116,10 @@ namespace AsbCloudWebApi.Tests
dbSetMock.As>().Setup(o => o.ElementType).Returns(() => dbSetDataQueriable.ElementType);
dbSetMock.As>().Setup(o => o.GetEnumerator()).Returns(() => dbSetDataQueriable.GetEnumerator());
+ dbSetMock.As>()
+ .Setup(o => o.GetAsyncEnumerator(It.IsAny()))
+ .Returns(() => dbSetDataQueriable.GetAsyncEnumerator());
+
return dbSetMock;
}
}
diff --git a/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs b/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs
index 4a77088a..f7fe0b74 100644
--- a/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs
+++ b/AsbCloudWebApi/Controllers/SAUB/TelemetryController.cs
@@ -1,5 +1,4 @@
-using AsbCloudApp.Data;
-using AsbCloudApp.Data.SAUB;
+using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudWebApi.SignalR;
using Microsoft.AspNetCore.Mvc;
diff --git a/AsbCloudWebApi/Controllers/SAUB/TelemetryDataBaseController.cs b/AsbCloudWebApi/Controllers/SAUB/TelemetryDataBaseController.cs
index 132f5513..51640915 100644
--- a/AsbCloudWebApi/Controllers/SAUB/TelemetryDataBaseController.cs
+++ b/AsbCloudWebApi/Controllers/SAUB/TelemetryDataBaseController.cs
@@ -115,11 +115,15 @@ namespace AsbCloudWebApi.Controllers.SAUB
if (!isCompanyOwnsWell)
return Forbid();
-
- var dataDatesRange = wellService.GetDatesRange(idWell);
-
- return Ok(dataDatesRange);
+ try
+ {
+ var dataDatesRange = wellService.GetDatesRange(idWell);
+ return Ok(dataDatesRange);
+ }
+ catch(KeyNotFoundException)
+ {
+ return NoContent();
+ }
}
-
}
}
diff --git a/ConsoleApp1/ServiceFactory.cs b/ConsoleApp1/ServiceFactory.cs
index a1fb9d91..bf7c9a42 100644
--- a/ConsoleApp1/ServiceFactory.cs
+++ b/ConsoleApp1/ServiceFactory.cs
@@ -1,9 +1,9 @@
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services;
-using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudInfrastructure.Services.WellOperationService;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System.Collections.Generic;
@@ -41,19 +41,20 @@ namespace ConsoleApp1
internal static class ServiceFactory
{
- private static DbContextOptions options = new DbContextOptionsBuilder()
+ private static readonly DbContextOptions options = new DbContextOptionsBuilder()
.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True")
.Options;
- static CacheDb CacheDb { get; } = new CacheDb();
static ConfigurationService ConfigurationService { get; } = new ConfigurationService();
static TimezoneService TimezoneService { get; } = new TimezoneService();
public static AsbCloudDbContext Context { get; } = MakeContext();
public static AsbCloudDbContext MakeContext()
=> MakeContext(options);
+ public static IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
+
public static AsbCloudDbContext MakeContext(DbContextOptions options)
- => new AsbCloudDbContext(options);
+ => new (options);
public static AsbCloudDbContext MakeContext(string cusomConnectionString)
=> MakeContext(new DbContextOptionsBuilder().UseNpgsql(cusomConnectionString).Options);
@@ -62,21 +63,21 @@ namespace ConsoleApp1
=> AsbCloudInfrastructure.DependencyInjection.MapsterSetup();
public static TelemetryTracker MakeTelemetryTracker()
- => new TelemetryTracker(CacheDb, ConfigurationService);
+ => new (ConfigurationService, memoryCache);
public static TelemetryService MakeTelemetryService()
- => new TelemetryService(Context, MakeTelemetryTracker(), TimezoneService);
+ => new (Context, MakeTelemetryTracker(), TimezoneService);
public static WellService MakeWellService()
- => new WellService(Context, CacheDb, MakeTelemetryService(), TimezoneService);
+ => new (Context, memoryCache, MakeTelemetryService(), TimezoneService);
public static WellOperationService MakeWellOperationsService()
- => new WellOperationService(Context, CacheDb, MakeWellService());
+ => new (Context, memoryCache, MakeWellService());
public static OperationsStatService MakeOperationsStatService()
- => new OperationsStatService(Context, CacheDb, MakeWellService());
+ => new (Context, memoryCache, MakeWellService());
public static ScheduleReportService MakeScheduleReportService()
- => new ScheduleReportService(MakeOperationsStatService(), MakeWellService());
+ => new(MakeOperationsStatService(), MakeWellService());
}
}