merge dev to 7887519

This commit is contained in:
ngfrolov 2022-11-18 16:45:36 +05:00
commit dc67f53f40
34 changed files with 290 additions and 682 deletions

View File

@ -1,4 +1,6 @@
namespace AsbCloudApp.Data.SAUB using AsbCloudDb.Model;
namespace AsbCloudApp.Data.SAUB
{ {
/// <summary> /// <summary>
/// Пользователь панели оператора /// Пользователь панели оператора
@ -27,5 +29,26 @@
/// Уровень доступа /// Уровень доступа
/// </summary> /// </summary>
public int Level { get; set; } public int Level { get; set; }
/// <summary>
/// Собрать отображаемое имя пользователя
/// </summary>
/// <returns></returns>
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}";
}
} }
} }

View File

@ -1,15 +1,33 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace AsbCloudApp.Services namespace AsbCloudApp.Services
{ {
#nullable enable
/// <summary> /// <summary>
/// сервис пользователей телеметрии /// сервис пользователей телеметрии
/// </summary> /// </summary>
public interface ITelemetryUserService public interface ITelemetryUserService
{ {
/// <summary>
/// get user by ids
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="idUser"></param>
/// <returns></returns>
TelemetryUserDto? GetOrDefault(int idTelemetry, int idUser);
/// <summary>
/// get users by id telemetry and predicate
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="predicate"></param>
/// <returns></returns>
IEnumerable<TelemetryUserDto> GetUsers(int idTelemetry, Func<TelemetryUserDto, bool>? predicate = default);
/// <summary> /// <summary>
/// получает и сохраняет/обновляет список пользователей панели оператора /// получает и сохраняет/обновляет список пользователей панели оператора
/// </summary> /// </summary>
@ -19,4 +37,5 @@ namespace AsbCloudApp.Services
/// <returns></returns> /// <returns></returns>
Task UpsertAsync(string uid, IEnumerable<TelemetryUserDto> dtos, CancellationToken token = default); Task UpsertAsync(string uid, IEnumerable<TelemetryUserDto> dtos, CancellationToken token = default);
} }
#nullable disable
} }

View File

@ -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<IEnumerable<T>> GetOrCreateBasicAsync<T>(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<T>().ToArrayAsync(token);
return entities.AsEnumerable();
});
return cache;
}
public static IEnumerable<T> GetOrCreateBasic<T>(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<T>().ToArray();
return entities.AsEnumerable();
});
return cache;
}
}
}

View File

@ -8,7 +8,6 @@ using AsbCloudDb.Model;
using AsbCloudDb.Model.Subsystems; using AsbCloudDb.Model.Subsystems;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.DailyReport; using AsbCloudInfrastructure.Services.DailyReport;
using AsbCloudInfrastructure.Services.DetectOperations; using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services.DrillingProgram; using AsbCloudInfrastructure.Services.DrillingProgram;
@ -16,10 +15,10 @@ using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudInfrastructure.Services.Subsystems; using AsbCloudInfrastructure.Services.Subsystems;
using AsbCloudInfrastructure.Services.WellOperationService; using AsbCloudInfrastructure.Services.WellOperationService;
using AsbCloudInfrastructure.Validators; using AsbCloudInfrastructure.Validators;
using DocumentFormat.OpenXml.Spreadsheet;
using FluentValidation.AspNetCore; using FluentValidation.AspNetCore;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
@ -94,6 +93,7 @@ namespace AsbCloudInfrastructure
// TODO: переместить FluentValidation в описание моделей // TODO: переместить FluentValidation в описание моделей
services.AddFluentValidationClientsideAdapters(); services.AddFluentValidationClientsideAdapters();
services.AddMemoryCache();
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>()); services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
services.AddScoped<IEmailService, EmailService>(); services.AddScoped<IEmailService, EmailService>();
@ -101,7 +101,6 @@ namespace AsbCloudInfrastructure
services.AddHostedService<SubsystemOperationTimeBackgroundService>(); services.AddHostedService<SubsystemOperationTimeBackgroundService>();
services.AddHostedService<LimitingParameterBackgroundService>(); services.AddHostedService<LimitingParameterBackgroundService>();
services.AddSingleton(new WitsInfoService()); services.AddSingleton(new WitsInfoService());
services.AddSingleton(new CacheDb());
services.AddSingleton(new InstantDataRepository()); services.AddSingleton(new InstantDataRepository());
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration)); services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration));
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration)); services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration));
@ -143,21 +142,25 @@ namespace AsbCloudInfrastructure
services.AddTransient<ICrudService<TelemetryDto>, CrudServiceBase<TelemetryDto, Telemetry>>(s => services.AddTransient<ICrudService<TelemetryDto>, CrudServiceBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheServiceBase<TelemetryDto, Telemetry>( new CrudCacheServiceBase<TelemetryDto, Telemetry>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(),
dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService
services.AddTransient<ICrudService<DrillParamsDto>, DrillParamsService>(); services.AddTransient<ICrudService<DrillParamsDto>, DrillParamsService>();
services.AddTransient<ICrudService<DepositDto>, CrudCacheServiceBase<DepositDto, Deposit>>(s => services.AddTransient<ICrudService<DepositDto>, CrudCacheServiceBase<DepositDto, Deposit>>(s =>
new CrudCacheServiceBase<DepositDto, Deposit>( new CrudCacheServiceBase<DepositDto, Deposit>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(),
dbSet => dbSet.Include(d => d.Clusters))); dbSet => dbSet.Include(d => d.Clusters)));
services.AddTransient<ICrudService<CompanyDto>, CrudCacheServiceBase<CompanyDto, Company>>(s => services.AddTransient<ICrudService<CompanyDto>, CrudCacheServiceBase<CompanyDto, Company>>(s =>
new CrudCacheServiceBase<CompanyDto, Company>( new CrudCacheServiceBase<CompanyDto, Company>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(),
dbSet => dbSet.Include(c => c.CompanyType))); dbSet => dbSet.Include(c => c.CompanyType)));
services.AddTransient<ICrudService<CompanyTypeDto>, CrudCacheServiceBase<CompanyTypeDto, CompanyType>>(); services.AddTransient<ICrudService<CompanyTypeDto>, CrudCacheServiceBase<CompanyTypeDto, CompanyType>>();
services.AddTransient<ICrudService<ClusterDto>, CrudCacheServiceBase<ClusterDto, Cluster>>(s => services.AddTransient<ICrudService<ClusterDto>, CrudCacheServiceBase<ClusterDto, Cluster>>(s =>
new CrudCacheServiceBase<ClusterDto, Cluster>( new CrudCacheServiceBase<ClusterDto, Cluster>(
s.GetService<IAsbCloudDbContext>(), s.GetService<IAsbCloudDbContext>(),
s.GetService<IMemoryCache>(),
dbSet => dbSet dbSet => dbSet
.Include(c => c.Wells) .Include(c => c.Wells)
.Include(c => c.Deposit))); // может быть включен в сервис ClusterService .Include(c => c.Deposit))); // может быть включен в сервис ClusterService

View File

@ -1,6 +1,6 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -21,16 +21,21 @@ namespace AsbCloudInfrastructure.Repository
{ {
protected string CacheTag = typeof(TDto).Name; protected string CacheTag = typeof(TDto).Name;
protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5); protected TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
private readonly IMemoryCache memoryCache;
protected int KeySelector(TEntity entity) => entity.Id; protected int KeySelector(TEntity entity) => entity.Id;
public CrudCacheServiceBase(IAsbCloudDbContext dbContext) public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
: base(dbContext) { } : base(dbContext)
{
this.memoryCache = memoryCache;
}
public CrudCacheServiceBase(IAsbCloudDbContext dbContext, ISet<string> includes) public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(dbContext, includes) { } : base(dbContext, makeQuery)
{
public CrudCacheServiceBase(IAsbCloudDbContext dbContext, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery) this.memoryCache = memoryCache;
: base(dbContext, makeQuery) { } }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<int> InsertAsync(TDto newItem, CancellationToken token) public override async Task<int> InsertAsync(TDto newItem, CancellationToken token)
@ -103,16 +108,33 @@ namespace AsbCloudInfrastructure.Repository
} }
protected virtual Task<IEnumerable<TDto>> GetCacheAsync(CancellationToken token) protected virtual Task<IEnumerable<TDto>> 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<TDto> GetCache() protected virtual IEnumerable<TDto> 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() protected virtual void DropCache()
=> dbSet.DropCache(CacheTag); => memoryCache.Remove(CacheTag);
} }
#nullable disable #nullable disable
} }

View File

@ -31,20 +31,6 @@ namespace AsbCloudInfrastructure.Repository
GetQuery = () => dbSet; GetQuery = () => dbSet;
} }
public CrudServiceBase(IAsbCloudDbContext dbContext, ISet<string> includes)
{
this.dbContext = dbContext;
dbSet = dbContext.Set<TEntity>();
GetQuery = () =>
{
IQueryable<TEntity> query = dbSet;
foreach (var include in includes)
query = query.Include(include);
return query;
};
}
public CrudServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery) public CrudServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
{ {
dbContext = context; dbContext = context;

View File

@ -1,6 +1,7 @@
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -14,14 +15,11 @@ namespace AsbCloudInfrastructure.Repository
where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated where TDto : AsbCloudApp.Data.IId, AsbCloudApp.Data.IWellRelated
where TEntity : class, IId, IWellRelated where TEntity : class, IId, IWellRelated
{ {
public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context) public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, IMemoryCache memoryCache)
: base(context) { } : base(context, memoryCache) { }
public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext dbContext, ISet<string> includes) public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(dbContext, includes) { } : base(context, memoryCache, makeQuery) { }
public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(context, makeQuery) { }
public async Task<IEnumerable<TDto>?> GetByIdWellAsync(int idWell, CancellationToken token) public async Task<IEnumerable<TDto>?> GetByIdWellAsync(int idWell, CancellationToken token)
{ {

View File

@ -17,9 +17,6 @@ namespace AsbCloudInfrastructure.Repository
public CrudWellRelatedServiceBase(IAsbCloudDbContext context) public CrudWellRelatedServiceBase(IAsbCloudDbContext context)
: base(context) { } : base(context) { }
public CrudWellRelatedServiceBase(IAsbCloudDbContext dbContext, ISet<string> includes)
: base(dbContext, includes) { }
public CrudWellRelatedServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery) public CrudWellRelatedServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
: base(context, makeQuery) { } : base(context, makeQuery) { }

View File

@ -2,6 +2,7 @@
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
namespace AsbCloudInfrastructure.Repository namespace AsbCloudInfrastructure.Repository
@ -10,8 +11,8 @@ namespace AsbCloudInfrastructure.Repository
{ {
private readonly IWellService wellService; private readonly IWellService wellService;
public SetpointsRequestRepository(IAsbCloudDbContext dbContext, IWellService wellService) public SetpointsRequestRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, IWellService wellService)
: base(dbContext, q => q.Include(s => s.Author) : base(dbContext, memoryCache, q => q.Include(s => s.Author)
.Include(s => s.Well)) .Include(s => s.Well))
{ {
this.wellService = wellService; this.wellService = wellService;

View File

@ -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<string, CacheTableDataStore> cache =
new ConcurrentDictionary<string, CacheTableDataStore>();
public CacheTable<TEntity> GetCachedTable<TEntity>(DbContext context, params string[] includes)
where TEntity : class
=> GetCachedTable<TEntity>(context, new SortedSet<string>(includes));
public CacheTable<TEntity> GetCachedTable<TEntity>(DbContext context, ISet<string> includes = null)
where TEntity : class
{
var cacheItem = GetCacheTableDataStore<TEntity>();
var tableCache = new CacheTable<TEntity>(context, cacheItem, includes);
return tableCache;
}
public CacheTable<TEntity> GetCachedTable<TEntity>(DbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet)
where TEntity : class
{
var cacheItem = GetCacheTableDataStore<TEntity>();
var tableCache = new CacheTable<TEntity>(context, cacheItem, configureDbSet);
return tableCache;
}
private CacheTableDataStore GetCacheTableDataStore<TEntity>()
where TEntity : class
{
var nameOfTEntity = typeof(TEntity).FullName;
var cacheItem = cache.GetOrAdd(nameOfTEntity, (nameOfTEntity) => new CacheTableDataStore
{
NameOfTEntity = nameOfTEntity,
Entities = new List<TEntity>(),
});
return cacheItem;
}
public void DropAll() => cache.Clear();
public void Drop<TEntity>() => cache.Remove(typeof(TEntity).FullName, out _);
}
}

View File

@ -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<TEntity> : IEnumerable<TEntity>
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<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet;
private readonly List<TEntity> cached;
private readonly DbContext context;
private readonly DbSet<TEntity> dbSet;
internal CacheTable(DbContext context, CacheTableDataStore data, ISet<string> includes = null)
{
this.context = context;
this.data = data;
dbSet = context.Set<TEntity>();
if (includes?.Any() == true)
configureDbSet = (DbSet<TEntity> dbSet) =>
{
IQueryable<TEntity> result = dbSet;
foreach (var include in includes)
result = result.Include(include);
return result;
};
cached = (List<TEntity>)data.Entities;
if ((cached.Count == 0) || data.IsObsolete)
Refresh(false);
}
internal CacheTable(DbContext context, CacheTableDataStore data,
Func<DbSet<TEntity>, IQueryable<TEntity>> configureDbSet = null)
{
this.context = context;
this.data = data;
this.configureDbSet = configureDbSet;
dbSet = context.Set<TEntity>();
cached = (List<TEntity>)data.Entities;
if ((cached.Count == 0) || data.IsObsolete)
Refresh(false);
}
public TEntity this[int index]
{
get => cached.ElementAt(index);
}
/// <summary>
/// Runs action like atomic operation.
/// wasFree is action argument indicates that semaphore was free.
/// It may be needed to avoid multiple operations like Refresh().
/// </summary>
/// <param name="action">(wasFree) => {...}</param>
/// <returns>default if semaphoreTimeout. Or result of func(..)</returns>
private static T Sync<T>(Func<bool, T> 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;
}
/// <summary>
/// Runs action like atomic operation.
/// wasFree is action argument indicates that semaphore was free.
/// It may be needed to avoid multiple operations like Refresh().
/// </summary>
/// <param name="action">(wasFree) => {...}</param>
/// <returns>default if semaphoreTimeout. Or result of func(..)</returns>
private static async Task<T> SyncAsync<T>(Func<bool, CancellationToken, Task<T>> 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<TEntity> 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<int> InternalRefreshAsync(bool force, CancellationToken token = default)
{
if (force || data.LastResreshDate + minPeriodRefresh < DateTime.Now)
{
cached.Clear();
IQueryable<TEntity> 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<int> RefreshAsync(bool force, CancellationToken token = default)
{
return SyncAsync(
async (wasFree, token) =>
{
return wasFree ? await InternalRefreshAsync(force, token) : 0;
}, token);
}
public bool Contains(Func<TEntity, bool> predicate)
=> FirstOrDefault(predicate) != default;
public async Task<bool> ContainsAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
=> await FirstOrDefaultAsync(predicate, token) != default;
public TEntity GetOrCreate(Func<TEntity, bool> predicate, Func<TEntity> 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<TEntity> FirstOrDefaultAsync(CancellationToken token = default)
{
var result = cached.FirstOrDefault();
if (result != default)
return result;
await RefreshAsync(false, token);
return cached.FirstOrDefault();
}
public TEntity FirstOrDefault(Func<TEntity, bool> predicate)
{
var result = cached.FirstOrDefault(predicate);
if (result != default)
return result;
Refresh(false);
return cached.FirstOrDefault(predicate);
}
public async Task<TEntity> FirstOrDefaultAsync(Func<TEntity, bool> predicate, CancellationToken token = default)
{
var result = cached.FirstOrDefault(predicate);
if (result != default)
return result;
await RefreshAsync(false, token);
return cached.FirstOrDefault(predicate);
}
public IEnumerable<TEntity> Where(Func<TEntity, bool> 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<IEnumerable<TEntity>> WhereAsync(CancellationToken token = default) =>
WhereAsync(default, token);
public async Task<IEnumerable<TEntity>> WhereAsync(Func<TEntity, bool> 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<int> 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<TEntity> 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<int> UpsertAsync(IEnumerable<TEntity> entities, CancellationToken token = default)
{
if (!entities.Any())
return Task.FromResult(0);
return SyncAsync(async (wasFree, token) =>
{
var upsertedEntries = new List<TEntity>(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<TEntity, bool> predicate)
=> Sync(_ =>
{
dbSet.RemoveRange(dbSet.Where(predicate));
var affected = context.SaveChanges();
if (affected > 0)
InternalRefresh(true);
return affected;
});
public Task<int> RemoveAsync(Func<TEntity, bool> 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<TEntity> 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<TEntity> Insert(IEnumerable<TEntity> newEntities)
{
if (newEntities is null)
return null;
var count = newEntities.Count();
if (count == 0)
return null;
return Sync(_ =>
{
var entries = new List<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity>>(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<IEnumerable<TEntity>> InsertAsync(IEnumerable<TEntity> 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<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<TEntity>>(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<TEntity> GetEnumerator() => Where().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -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<string> 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);
}
}

View File

@ -2,6 +2,7 @@
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -11,10 +12,8 @@ namespace AsbCloudInfrastructure.Services
{ {
public class FileCategoryService : CrudCacheServiceBase<FileCategoryDto, FileCategory>, IFileCategoryService public class FileCategoryService : CrudCacheServiceBase<FileCategoryDto, FileCategory>, IFileCategoryService
{ {
public FileCategoryService(IAsbCloudDbContext context) public FileCategoryService(IAsbCloudDbContext context, IMemoryCache memoryCache)
: base(context) : base(context, memoryCache) { }
{
}
//TODO: Перенести в сервис "дело скважины" //TODO: Перенести в сервис "дело скважины"
public async Task<IEnumerable<FileCategoryDto>> GetWellCaseCategoriesAsync(CancellationToken token) public async Task<IEnumerable<FileCategoryDto>> GetWellCaseCategoriesAsync(CancellationToken token)

View File

@ -3,6 +3,7 @@ using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -30,9 +31,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
private readonly SetpointsRequestRepository setpointsRepository; private readonly SetpointsRequestRepository setpointsRepository;
private readonly ITelemetryService telemetryService; 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; this.telemetryService = telemetryService;
} }

View File

@ -1,7 +1,6 @@
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,18 +18,15 @@ namespace AsbCloudInfrastructure.Services.SAUB
{ {
protected readonly IAsbCloudDbContext db; protected readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService; private readonly ITelemetryService telemetryService;
protected readonly CacheTable<TelemetryUser> cacheTelemetryUsers;
private readonly TelemetryDataCache<TDto> telemetryDataCache; private readonly TelemetryDataCache<TDto> telemetryDataCache;
public TelemetryDataBaseService( public TelemetryDataBaseService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
TelemetryDataCache<TDto> telemetryDataCache, TelemetryDataCache<TDto> telemetryDataCache)
CacheDb cacheDb)
{ {
this.db = db; this.db = db;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
cacheTelemetryUsers = cacheDb.GetCachedTable<TelemetryUser>((AsbCloudDbContext)db);
this.telemetryDataCache = telemetryDataCache; this.telemetryDataCache = telemetryDataCache;
} }

View File

@ -1,32 +1,33 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using DocumentFormat.OpenXml.Drawing.Charts;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB namespace AsbCloudInfrastructure.Services.SAUB
{ {
#nullable enable
public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub> public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub>
{ {
private readonly ITelemetryUserService telemetryUserService;
public TelemetryDataSaubService( public TelemetryDataSaubService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache, ITelemetryUserService telemetryUserService,
CacheDb cacheDb) TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache, cacheDb) : base(db, telemetryService, telemetryDataCache)
{ } {
this.telemetryUserService = telemetryUserService;
}
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset) public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
{ {
var entity = src.Adapt<TelemetryDataSaub>(); var entity = src.Adapt<TelemetryDataSaub>();
var telemetryUser = cacheTelemetryUsers? var telemetryUser = telemetryUserService
.FirstOrDefault(u => u.IdTelemetry == src.IdTelemetry && (u.Name == src.User || u.Surname == src.User)); .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
entity.IdUser = telemetryUser?.IdUser; .FirstOrDefault();
entity.IdUser = telemetryUser?.Id;
entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset); entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
return entity; return entity;
} }
@ -34,11 +35,11 @@ namespace AsbCloudInfrastructure.Services.SAUB
public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset) public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
{ {
var dto = src.Adapt<TelemetryDataSaubDto>(); var dto = src.Adapt<TelemetryDataSaubDto>();
var telemetryUser = cacheTelemetryUsers? var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser??int.MinValue);
.FirstOrDefault(u => u.IdTelemetry == src.IdTelemetry && u.IdUser == src.IdUser);
dto.User = telemetryUser?.MakeDisplayName(); dto.User = telemetryUser?.MakeDisplayName();
dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset); dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
return dto; return dto;
} }
} }
#nullable disable
} }

View File

@ -1,10 +1,7 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster; using Mapster;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB namespace AsbCloudInfrastructure.Services.SAUB
{ {
@ -13,9 +10,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryDataSpinService( public TelemetryDataSpinService(
IAsbCloudDbContext db, IAsbCloudDbContext db,
ITelemetryService telemetryService, ITelemetryService telemetryService,
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache, TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
CacheDb cacheDb) : base(db, telemetryService, telemetryDataCache)
: base(db, telemetryService, telemetryDataCache, cacheDb)
{ } { }
public override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset) public override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset)

View File

@ -17,7 +17,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
#nullable enable #nullable enable
public class TelemetryService : ITelemetryService public class TelemetryService : ITelemetryService
{ {
private const string telemetryCacheTag = "telemetryCache"; private const string CacheTag = "TelemetryCache";
private static readonly TimeSpan telemetryCacheObsolescence = TimeSpan.FromMinutes(5); private static readonly TimeSpan telemetryCacheObsolescence = TimeSpan.FromMinutes(5);
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
@ -39,15 +39,15 @@ namespace AsbCloudInfrastructure.Services.SAUB
private IEnumerable<Telemetry> GetTelemetryCache() private IEnumerable<Telemetry> GetTelemetryCache()
{ {
var cache = db.Telemetries var cache = db.Set<Telemetry>()
.Include(t => t.Well) .Include(t => t.Well)
.FromCache(telemetryCacheTag, telemetryCacheObsolescence); .FromCache(CacheTag, telemetryCacheObsolescence);
return cache; return cache;
} }
private void DropTelemetryCache() private void DropTelemetryCache()
{ {
db.Telemetries.DropCache(telemetryCacheTag); db.Telemetries.DropCache(CacheTag);
} }
public DateTime GetLastTelemetryDate(int idTelemetry) public DateTime GetLastTelemetryDate(int idTelemetry)

View File

@ -1,8 +1,8 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -39,14 +39,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
private readonly ConcurrentDictionary<string, TrackerStat> telemetriesStats; private readonly ConcurrentDictionary<string, TrackerStat> telemetriesStats;
public TelemetryTracker(CacheDb cacheDb, IConfiguration configuration) public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache)
{ {
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>() var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(configuration.GetConnectionString("DefaultConnection")) .UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
.Options; .Options;
var db = new AsbCloudDbContext(contextOptions); var db = new AsbCloudDbContext(contextOptions);
var cacheTelemetry = cacheDb.GetCachedTable<Telemetry>(db); var cacheTelemetry = memoryCache.GetOrCreateBasic<Telemetry>(db);
var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count()); var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count());
foreach (var telemetry in cacheTelemetry) foreach (var telemetry in cacheTelemetry)
{ {

View File

@ -3,7 +3,9 @@ using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb; using AsbCloudDb;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache; using Mapster;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -11,15 +13,49 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB namespace AsbCloudInfrastructure.Services.SAUB
{ {
#nullable enable
public class TelemetryUserService : ITelemetryUserService public class TelemetryUserService : ITelemetryUserService
{ {
private const string CacheTag = "TelemetryUserCacheTag";
private readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService; 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.db = db;
this.telemetryService = telemetryService; 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<TelemetryUserDto> GetUsers(int idTelemetry, Func<TelemetryUserDto, bool>? 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<TelemetryUserDto> dtos, CancellationToken token = default) public async Task UpsertAsync(string uid, IEnumerable<TelemetryUserDto> dtos, CancellationToken token = default)
@ -29,17 +65,37 @@ namespace AsbCloudInfrastructure.Services.SAUB
var telemetryId = telemetryService.GetOrCreateTelemetryIdByUid(uid); var telemetryId = telemetryService.GetOrCreateTelemetryIdByUid(uid);
var entities = dtos.Distinct(new TelemetryUserDtoComparer()).Select(dto => new TelemetryUser var entities = dtos.Distinct(new TelemetryUserDtoComparer()).Select(dto => {
{ var entity = dto.Adapt<TelemetryUser>();
IdUser = dto.Id, entity.IdUser = dto.Id;
IdTelemetry = telemetryId, entity.IdTelemetry = telemetryId;
Level = dto.Level, return entity;
Name = dto.Name,
Patronymic = dto.Patronymic,
Surname = dto.Surname,
}); });
var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryUsers, entities, token); var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryUsers, entities, token);
db.TelemetryUsers.DropCache(); DropCache();
}
private IEnumerable<TelemetryUser> GetCache()
{
var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => {
cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
cacheEntry.SlidingExpiration = CacheOlescence;
var entities = db.Set<TelemetryUser>().ToArray();
return entities;
});
return cache;
}
private void DropCache()
=> memoryCache.Remove(CacheTag);
private static TelemetryUserDto Convert(TelemetryUser entity)
{
var dto = entity.Adapt<TelemetryUserDto>();
dto.Id = entity.IdUser;
return dto;
} }
} }
#nullable disable
} }

View File

@ -6,6 +6,7 @@ using AsbCloudDb.Model.Subsystems;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -17,7 +18,7 @@ namespace AsbCloudInfrastructure.Services.Subsystems
internal class SubsystemService : CrudCacheServiceBase<SubsystemDto, Subsystem>, ISubsystemService internal class SubsystemService : CrudCacheServiceBase<SubsystemDto, Subsystem>, ISubsystemService
{ {
private readonly IWellService wellService; 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; this.wellService = wellService;
} }

View File

@ -121,7 +121,7 @@ namespace AsbCloudInfrastructure.Services
var result = new WellCaseDto var result = new WellCaseDto
{ {
IdWell = idWell, IdWell = idWell,
PermissionToSetPubliher = userRepository.HasPermission(idUser, "WellFinalDocument.editPublisher"), PermissionToSetPubliher = userRepository.HasPermission(idUser, "WellFinalDocuments.editPublisher"),
WellFinalDocuments = docs, WellFinalDocuments = docs,
}; };
return result; return result;

View File

@ -1,9 +1,9 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -15,18 +15,14 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
public class OperationsStatService : IOperationsStatService public class OperationsStatService : IOperationsStatService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly CacheTable<WellSectionType> cacheSectionsTypes;
private readonly CacheTable<WellType> cacheWellType;
private readonly CacheTable<Cluster> cacheCluster;
public OperationsStatService(IAsbCloudDbContext db, CacheDb cache, IWellService wellService) public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
{ {
this.db = db; this.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService; this.wellService = wellService;
cacheSectionsTypes = cache.GetCachedTable<WellSectionType>((DbContext)db);
cacheWellType = cache.GetCachedTable<WellType>((DbContext)db);
cacheCluster = cache.GetCachedTable<Cluster>((DbContext)db);
} }
public async Task<StatClusterDto> GetStatClusterAsync(int idCluster, int idCompany, CancellationToken token = default) public async Task<StatClusterDto> 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 statsWells = await GetWellsStatAsync(wells, token).ConfigureAwait(false);
var cluster = await cacheCluster.FirstOrDefaultAsync(c => c.Id == idCluster, token); var cluster = (await memoryCache
.GetOrCreateBasicAsync<Cluster>(db, token))
.FirstOrDefault(c => c.Id == idCluster);
var statClusterDto = new StatClusterDto var statClusterDto = new StatClusterDto
{ {
Id = idCluster, Id = idCluster,
@ -133,7 +131,9 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
private async Task<StatWellDto> CalcWellStatAsync(Well well, CancellationToken token = default) private async Task<StatWellDto> CalcWellStatAsync(Well well, CancellationToken token = default)
{ {
var wellType = await cacheWellType.FirstOrDefaultAsync(t => t.Id == well.IdWellType, token); var wellType = (await memoryCache
.GetOrCreateBasicAsync<WellType>(db, token))
.FirstOrDefault(t => t.Id == well.IdWellType);
var statWellDto = new StatWellDto() var statWellDto = new StatWellDto()
{ {
Id = well.Id, Id = well.Id,
@ -169,7 +169,8 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
.Select(o => o.IdWellSectionType) .Select(o => o.IdWellSectionType)
.Distinct(); .Distinct();
var sectionTypes = cacheSectionsTypes var sectionTypes = memoryCache
.GetOrCreateBasic<WellSectionType>(db)
.Where(s => sectionTypeIds.Contains(s.Id)) .Where(s => sectionTypeIds.Contains(s.Id))
.ToDictionary(s => s.Id); .ToDictionary(s => s.Id);

View File

@ -1,9 +1,9 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -16,9 +16,8 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
public class WellOperationService : IWellOperationService public class WellOperationService : IWellOperationService
{ {
private readonly IAsbCloudDbContext db; private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly CacheTable<WellOperationCategory> cachedOperationCategories;
private readonly CacheTable<WellSectionType> cachedSectionTypes;
private Dictionary<int, DateTimeOffset?>? firstOperationsCache = null; private Dictionary<int, DateTimeOffset?>? firstOperationsCache = null;
@ -33,22 +32,25 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
public const int idOperationTypeFact = 1; public const int idOperationTypeFact = 1;
public const int idOperationTypeRepair = 1031; 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.db = db;
this.memoryCache = memoryCache;
this.wellService = wellService; this.wellService = wellService;
cachedOperationCategories = cache.GetCachedTable<WellOperationCategory>((DbContext)db);
cachedSectionTypes = cache.GetCachedTable<WellSectionType>((DbContext)db);
} }
public IDictionary<int, string> GetSectionTypes() public IDictionary<int, string> GetSectionTypes()
=> cachedSectionTypes.ToDictionary(s => s.Id, s => s.Caption); => memoryCache
.GetOrCreateBasic<WellSectionType>(db)
.ToDictionary(s => s.Id, s => s.Caption);
public IEnumerable<WellOperationCategoryDto> GetCategories() public IEnumerable<WellOperationCategoryDto> GetCategories()
{ {
var operationTypes = cachedOperationCategories var operationCategories = memoryCache
.Distinct().OrderBy(o => o.Name); .GetOrCreateBasic<WellOperationCategory>(db)
var result = operationTypes.Adapt<IEnumerable<WellOperationCategoryDto>>(); .Distinct()
.OrderBy(o => o.Name);
var result = operationCategories.Adapt<IEnumerable<WellOperationCategoryDto>>();
return result; return result;
} }

View File

@ -4,9 +4,9 @@ using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.EfCache; using AsbCloudInfrastructure.EfCache;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services.Cache;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -34,16 +34,16 @@ namespace AsbCloudInfrastructure.Services
.Include(w => w.Telemetry) .Include(w => w.Telemetry)
.Include(w => w.WellType) .Include(w => w.WellType)
.Include(w => w.RelationCompaniesWells) .Include(w => w.RelationCompaniesWells)
.ThenInclude(r => r.Company); .ThenInclude(r => r.Company);
public WellService(IAsbCloudDbContext db, CacheDb cacheDb, ITelemetryService telemetryService, ITimezoneService timezoneService) public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService)
: base(db, MakeQueryWell) : base(db, memoryCache, MakeQueryWell)
{ {
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
this.timezoneService = timezoneService; this.timezoneService = timezoneService;
this.wellOperationService = new WellOperationService.WellOperationService(db, cacheDb, this); this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this);
companyTypesService = new CrudCacheServiceBase<CompanyTypeDto, CompanyType>(dbContext); companyTypesService = new CrudCacheServiceBase<CompanyTypeDto, CompanyType>(dbContext, memoryCache);
} }
private IEnumerable<RelationCompanyWell> GetCacheRelationCompanyWell() private IEnumerable<RelationCompanyWell> GetCacheRelationCompanyWell()
@ -167,13 +167,6 @@ namespace AsbCloudInfrastructure.Services
return dtos; return dtos;
} }
private IEnumerable<CompanyDto> 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) public string GetStateText(int state)
{ {
return state switch return state switch

View File

@ -22,7 +22,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
protected override ICrudService<DepositDto> MakeService() protected override ICrudService<DepositDto> MakeService()
{ {
var dbContext = TestHelpter.MakeRealTestContext(); var dbContext = TestHelpter.MakeRealTestContext();
return new CrudCacheServiceBase<DepositDto, Deposit>(dbContext); return new CrudCacheServiceBase<DepositDto, Deposit>(dbContext, TestHelpter.MemoryCache);
} }
} }
} }

View File

@ -3,8 +3,6 @@ using AsbCloudApp.Requests;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository; using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.DetectOperations; using AsbCloudInfrastructure.Services.DetectOperations;
using Moq; using Moq;
using System; using System;
@ -19,7 +17,6 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public class DetectedOperationServiceTest public class DetectedOperationServiceTest
{ {
private readonly AsbCloudDbContext context; private readonly AsbCloudDbContext context;
private readonly CacheDb cacheDb;
private readonly DetectedOperationService service; private readonly DetectedOperationService service;
private readonly DetectedOperationRequest request; private readonly DetectedOperationRequest request;
private Deposit deposit = new Deposit { Id = 1, Caption = "Депозит 1" }; private Deposit deposit = new Deposit { Id = 1, Caption = "Депозит 1" };

View File

@ -1,10 +1,5 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.Cache;
using Moq; using Moq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

View File

@ -1,7 +1,6 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.SAUB;
using Moq; using Moq;
using System.Collections.Generic; using System.Collections.Generic;
@ -14,13 +13,11 @@ namespace AsbCloudWebApi.Tests.ServicesTests;
public class EventServiceTest public class EventServiceTest
{ {
private readonly AsbCloudDbContext context; private readonly AsbCloudDbContext context;
private readonly CacheDb cacheDb;
private readonly EventService service; private readonly EventService service;
public EventServiceTest() public EventServiceTest()
{ {
context = TestHelpter.MakeRealTestContext(); context = TestHelpter.MakeRealTestContext();
cacheDb = new CacheDb();
var telemetryTracker = new Mock<ITelemetryTracker>(); var telemetryTracker = new Mock<ITelemetryTracker>();
var imezoneServiceMock = new Mock<ITimezoneService>(); var imezoneServiceMock = new Mock<ITimezoneService>();
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object); var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object);

View File

@ -1,7 +1,5 @@
using AsbCloudApp.Data; using AsbCloudDb.Model;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.Cache;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -19,7 +17,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
context = TestHelpter.MakeRealTestContext(); context = TestHelpter.MakeRealTestContext();
context.SaveChanges(); context.SaveChanges();
service = new FileCategoryService(context); service = new FileCategoryService(context, TestHelpter.MemoryCache);
} }
~FileCategoryServiceTest() ~FileCategoryServiceTest()

View File

@ -2,7 +2,6 @@
using AsbCloudApp.Data.SAUB; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.SAUB;
using Moq; using Moq;
using System; using System;
@ -20,9 +19,9 @@ namespace AsbCloudWebApi.Tests.ServicesTests
private readonly Mock<ITimezoneService> timezoneService; private readonly Mock<ITimezoneService> timezoneService;
private readonly SimpleTimezoneDto timezone; private readonly SimpleTimezoneDto timezone;
private readonly AsbCloudDbContext context; private readonly AsbCloudDbContext context;
private readonly CacheDb cacheDb;
private readonly TelemetryService telemetryService; private readonly TelemetryService telemetryService;
private readonly TelemetryUserService telemetryUserService;
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
private readonly DateTime drillingStartDate; private readonly DateTime drillingStartDate;
private readonly string uuid; private readonly string uuid;
public TelemetryDataSaubServiceTest() public TelemetryDataSaubServiceTest()
@ -41,9 +40,9 @@ namespace AsbCloudWebApi.Tests.ServicesTests
.Returns(timezone); .Returns(timezone);
context = TestHelpter.MakeRealTestContext(); context = TestHelpter.MakeRealTestContext();
cacheDb = new CacheDb();
telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object); telemetryService = new TelemetryService(context, telemetryTracker.Object, timezoneService.Object);
telemetryUserService = new TelemetryUserService(context, telemetryService, TestHelpter.MemoryCache);
telemetryDataSaubCache = TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(context, out Task _);
var info = new TelemetryInfoDto var info = new TelemetryInfoDto
{ {
TimeZoneOffsetTotalHours = timezone.Hours, TimeZoneOffsetTotalHours = timezone.Hours,
@ -64,7 +63,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
public async Task UpdateDataAsync() public async Task UpdateDataAsync()
{ {
// Arrange // 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 now = DateTimeOffset.UtcNow.ToOffset(TimeSpan.FromHours(timezone.Hours)).DateTime;
var tuser = "Завулон"; var tuser = "Завулон";

View File

@ -1,5 +1,6 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Moq; using Moq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -10,16 +11,14 @@ namespace AsbCloudWebApi.Tests
{ {
// Попробовать когда-нибудь https://github.com/MichalJankowskii/Moq.EntityFrameworkCore // Попробовать когда-нибудь https://github.com/MichalJankowskii/Moq.EntityFrameworkCore
public static IMemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions());
public static AsbCloudDbContext MakeRealTestContext() public static AsbCloudDbContext MakeRealTestContext()
{ {
var options = new DbContextOptionsBuilder<AsbCloudDbContext>() var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
//.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") .UseNpgsql("Host=localhost;Database=tests;Username=postgres;Password=q;Persist Security Info=True;Include Error Detail=True")
.Options; .Options;
var context = new AsbCloudDbContext(options); var context = new AsbCloudDbContext(options);
//context.Database.EnsureDeleted();
context.Database.EnsureCreated(); context.Database.EnsureCreated();
return context; return context;
} }

View File

@ -1,5 +1,4 @@
using AsbCloudApp.Data; using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services; using AsbCloudApp.Services;
using AsbCloudWebApi.SignalR; using AsbCloudWebApi.SignalR;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@ -1,9 +1,9 @@
using AsbCloudDb.Model; using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services;
using AsbCloudInfrastructure.Services.Cache;
using AsbCloudInfrastructure.Services.SAUB; using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudInfrastructure.Services.WellOperationService; using AsbCloudInfrastructure.Services.WellOperationService;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using System.Collections.Generic; using System.Collections.Generic;
@ -41,19 +41,20 @@ namespace ConsoleApp1
internal static class ServiceFactory internal static class ServiceFactory
{ {
private static DbContextOptions<AsbCloudDbContext> options = new DbContextOptionsBuilder<AsbCloudDbContext>() private static readonly DbContextOptions<AsbCloudDbContext> options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True") .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True")
.Options; .Options;
static CacheDb CacheDb { get; } = new CacheDb();
static ConfigurationService ConfigurationService { get; } = new ConfigurationService(); static ConfigurationService ConfigurationService { get; } = new ConfigurationService();
static TimezoneService TimezoneService { get; } = new TimezoneService(); static TimezoneService TimezoneService { get; } = new TimezoneService();
public static AsbCloudDbContext Context { get; } = MakeContext(); public static AsbCloudDbContext Context { get; } = MakeContext();
public static AsbCloudDbContext MakeContext() public static AsbCloudDbContext MakeContext()
=> MakeContext(options); => MakeContext(options);
public static IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions());
public static AsbCloudDbContext MakeContext(DbContextOptions<AsbCloudDbContext> options) public static AsbCloudDbContext MakeContext(DbContextOptions<AsbCloudDbContext> options)
=> new AsbCloudDbContext(options); => new (options);
public static AsbCloudDbContext MakeContext(string cusomConnectionString) public static AsbCloudDbContext MakeContext(string cusomConnectionString)
=> MakeContext(new DbContextOptionsBuilder<AsbCloudDbContext>().UseNpgsql(cusomConnectionString).Options); => MakeContext(new DbContextOptionsBuilder<AsbCloudDbContext>().UseNpgsql(cusomConnectionString).Options);
@ -62,21 +63,21 @@ namespace ConsoleApp1
=> AsbCloudInfrastructure.DependencyInjection.MapsterSetup(); => AsbCloudInfrastructure.DependencyInjection.MapsterSetup();
public static TelemetryTracker MakeTelemetryTracker() public static TelemetryTracker MakeTelemetryTracker()
=> new TelemetryTracker(CacheDb, ConfigurationService); => new (ConfigurationService, memoryCache);
public static TelemetryService MakeTelemetryService() public static TelemetryService MakeTelemetryService()
=> new TelemetryService(Context, MakeTelemetryTracker(), TimezoneService); => new (Context, MakeTelemetryTracker(), TimezoneService);
public static WellService MakeWellService() public static WellService MakeWellService()
=> new WellService(Context, CacheDb, MakeTelemetryService(), TimezoneService); => new (Context, memoryCache, MakeTelemetryService(), TimezoneService);
public static WellOperationService MakeWellOperationsService() public static WellOperationService MakeWellOperationsService()
=> new WellOperationService(Context, CacheDb, MakeWellService()); => new (Context, memoryCache, MakeWellService());
public static OperationsStatService MakeOperationsStatService() public static OperationsStatService MakeOperationsStatService()
=> new OperationsStatService(Context, CacheDb, MakeWellService()); => new (Context, memoryCache, MakeWellService());
public static ScheduleReportService MakeScheduleReportService() public static ScheduleReportService MakeScheduleReportService()
=> new ScheduleReportService(MakeOperationsStatService(), MakeWellService()); => new(MakeOperationsStatService(), MakeWellService());
} }
} }