forked from ddrilling/AsbCloudServer
This commit is contained in:
commit
5041b30686
@ -1,4 +1,6 @@
|
||||
namespace AsbCloudApp.Data.SAUB
|
||||
using AsbCloudDb.Model;
|
||||
|
||||
namespace AsbCloudApp.Data.SAUB
|
||||
{
|
||||
/// <summary>
|
||||
/// Пользователь панели оператора
|
||||
@ -27,5 +29,26 @@
|
||||
/// Уровень доступа
|
||||
/// </summary>
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
/// <summary>
|
||||
/// сервис пользователей телеметрии
|
||||
/// </summary>
|
||||
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>
|
||||
@ -19,4 +37,5 @@ namespace AsbCloudApp.Services
|
||||
/// <returns></returns>
|
||||
Task UpsertAsync(string uid, IEnumerable<TelemetryUserDto> dtos, CancellationToken token = default);
|
||||
}
|
||||
#nullable disable
|
||||
}
|
44
AsbCloudInfrastructure/CacheExtentions.cs
Normal file
44
AsbCloudInfrastructure/CacheExtentions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
|
||||
services.AddScoped<IEmailService, EmailService>();
|
||||
|
||||
@ -101,7 +101,6 @@ namespace AsbCloudInfrastructure
|
||||
services.AddHostedService<SubsystemOperationTimeBackgroundService>();
|
||||
services.AddHostedService<LimitingParameterBackgroundService>();
|
||||
services.AddSingleton(new WitsInfoService());
|
||||
services.AddSingleton(new CacheDb());
|
||||
services.AddSingleton(new InstantDataRepository());
|
||||
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(configuration));
|
||||
services.AddSingleton(provider=> TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(configuration));
|
||||
@ -143,21 +142,25 @@ namespace AsbCloudInfrastructure
|
||||
services.AddTransient<ICrudService<TelemetryDto>, CrudServiceBase<TelemetryDto, Telemetry>>(s =>
|
||||
new CrudCacheServiceBase<TelemetryDto, Telemetry>(
|
||||
s.GetService<IAsbCloudDbContext>(),
|
||||
s.GetService<IMemoryCache>(),
|
||||
dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService
|
||||
services.AddTransient<ICrudService<DrillParamsDto>, DrillParamsService>();
|
||||
services.AddTransient<ICrudService<DepositDto>, CrudCacheServiceBase<DepositDto, Deposit>>(s =>
|
||||
new CrudCacheServiceBase<DepositDto, Deposit>(
|
||||
s.GetService<IAsbCloudDbContext>(),
|
||||
s.GetService<IMemoryCache>(),
|
||||
dbSet => dbSet.Include(d => d.Clusters)));
|
||||
services.AddTransient<ICrudService<CompanyDto>, CrudCacheServiceBase<CompanyDto, Company>>(s =>
|
||||
new CrudCacheServiceBase<CompanyDto, Company>(
|
||||
s.GetService<IAsbCloudDbContext>(),
|
||||
s.GetService<IMemoryCache>(),
|
||||
dbSet => dbSet.Include(c => c.CompanyType)));
|
||||
|
||||
services.AddTransient<ICrudService<CompanyTypeDto>, CrudCacheServiceBase<CompanyTypeDto, CompanyType>>();
|
||||
services.AddTransient<ICrudService<ClusterDto>, CrudCacheServiceBase<ClusterDto, Cluster>>(s =>
|
||||
new CrudCacheServiceBase<ClusterDto, Cluster>(
|
||||
s.GetService<IAsbCloudDbContext>(),
|
||||
s.GetService<IMemoryCache>(),
|
||||
dbSet => dbSet
|
||||
.Include(c => c.Wells)
|
||||
.Include(c => c.Deposit))); // может быть включен в сервис ClusterService
|
||||
|
@ -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<string> includes)
|
||||
: base(dbContext, includes) { }
|
||||
|
||||
public CrudCacheServiceBase(IAsbCloudDbContext dbContext, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(dbContext, makeQuery) { }
|
||||
public CrudCacheServiceBase(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(dbContext, makeQuery)
|
||||
{
|
||||
this.memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
=> 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()
|
||||
=> 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
|
||||
}
|
@ -31,20 +31,6 @@ namespace AsbCloudInfrastructure.Repository
|
||||
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)
|
||||
{
|
||||
dbContext = context;
|
||||
|
@ -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<string> includes)
|
||||
: base(dbContext, includes) { }
|
||||
|
||||
public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(context, makeQuery) { }
|
||||
public CrudWellRelatedCacheServiceBase(IAsbCloudDbContext context, IMemoryCache memoryCache, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(context, memoryCache, makeQuery) { }
|
||||
|
||||
public async Task<IEnumerable<TDto>?> GetByIdWellAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
|
@ -17,9 +17,6 @@ namespace AsbCloudInfrastructure.Repository
|
||||
public CrudWellRelatedServiceBase(IAsbCloudDbContext context)
|
||||
: base(context) { }
|
||||
|
||||
public CrudWellRelatedServiceBase(IAsbCloudDbContext dbContext, ISet<string> includes)
|
||||
: base(dbContext, includes) { }
|
||||
|
||||
public CrudWellRelatedServiceBase(IAsbCloudDbContext context, Func<DbSet<TEntity>, IQueryable<TEntity>> makeQuery)
|
||||
: base(context, makeQuery) { }
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 _);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
@ -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<FileCategoryDto, FileCategory>, IFileCategoryService
|
||||
{
|
||||
public FileCategoryService(IAsbCloudDbContext context)
|
||||
: base(context)
|
||||
{
|
||||
}
|
||||
public FileCategoryService(IAsbCloudDbContext context, IMemoryCache memoryCache)
|
||||
: base(context, memoryCache) { }
|
||||
|
||||
//TODO: Перенести в сервис "дело скважины"
|
||||
public async Task<IEnumerable<FileCategoryDto>> GetWellCaseCategoriesAsync(CancellationToken token)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<TelemetryUser> cacheTelemetryUsers;
|
||||
private readonly TelemetryDataCache<TDto> telemetryDataCache;
|
||||
|
||||
public TelemetryDataBaseService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TDto> telemetryDataCache,
|
||||
CacheDb cacheDb)
|
||||
TelemetryDataCache<TDto> telemetryDataCache)
|
||||
{
|
||||
this.db = db;
|
||||
this.telemetryService = telemetryService;
|
||||
cacheTelemetryUsers = cacheDb.GetCachedTable<TelemetryUser>((AsbCloudDbContext)db);
|
||||
this.telemetryDataCache = telemetryDataCache;
|
||||
}
|
||||
|
||||
|
@ -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<TelemetryDataSaubDto, TelemetryDataSaub>
|
||||
{
|
||||
private readonly ITelemetryUserService telemetryUserService;
|
||||
|
||||
public TelemetryDataSaubService(
|
||||
IAsbCloudDbContext db,
|
||||
ITelemetryService telemetryService,
|
||||
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache,
|
||||
CacheDb cacheDb)
|
||||
: base(db, telemetryService, telemetryDataCache, cacheDb)
|
||||
{ }
|
||||
ITelemetryUserService telemetryUserService,
|
||||
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
|
||||
: base(db, telemetryService, telemetryDataCache)
|
||||
{
|
||||
this.telemetryUserService = telemetryUserService;
|
||||
}
|
||||
|
||||
public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
|
||||
{
|
||||
var entity = src.Adapt<TelemetryDataSaub>();
|
||||
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<TelemetryDataSaubDto>();
|
||||
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
|
||||
}
|
||||
|
@ -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<TelemetryDataSpinDto> telemetryDataCache,
|
||||
CacheDb cacheDb)
|
||||
: base(db, telemetryService, telemetryDataCache, cacheDb)
|
||||
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
|
||||
: base(db, telemetryService, telemetryDataCache)
|
||||
{ }
|
||||
|
||||
public override TelemetryDataSpin Convert(TelemetryDataSpinDto src, double timezoneOffset)
|
||||
|
@ -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<Telemetry> GetTelemetryCache()
|
||||
{
|
||||
var cache = db.Telemetries
|
||||
var cache = db.Set<Telemetry>()
|
||||
.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)
|
||||
|
@ -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<string, TrackerStat> telemetriesStats;
|
||||
|
||||
public TelemetryTracker(CacheDb cacheDb, IConfiguration configuration)
|
||||
public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache)
|
||||
{
|
||||
var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
|
||||
.Options;
|
||||
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());
|
||||
foreach (var telemetry in cacheTelemetry)
|
||||
{
|
||||
|
@ -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<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)
|
||||
@ -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<TelemetryUser>();
|
||||
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<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
|
||||
}
|
||||
|
@ -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<SubsystemDto, Subsystem>, 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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<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.memoryCache = memoryCache;
|
||||
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)
|
||||
@ -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<Cluster>(db, token))
|
||||
.FirstOrDefault(c => c.Id == idCluster);
|
||||
var statClusterDto = new StatClusterDto
|
||||
{
|
||||
Id = idCluster,
|
||||
@ -133,7 +131,9 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
|
||||
|
||||
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()
|
||||
{
|
||||
Id = well.Id,
|
||||
@ -169,7 +169,8 @@ namespace AsbCloudInfrastructure.Services.WellOperationService
|
||||
.Select(o => o.IdWellSectionType)
|
||||
.Distinct();
|
||||
|
||||
var sectionTypes = cacheSectionsTypes
|
||||
var sectionTypes = memoryCache
|
||||
.GetOrCreateBasic<WellSectionType>(db)
|
||||
.Where(s => sectionTypeIds.Contains(s.Id))
|
||||
.ToDictionary(s => s.Id);
|
||||
|
||||
|
@ -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<WellOperationCategory> cachedOperationCategories;
|
||||
private readonly CacheTable<WellSectionType> cachedSectionTypes;
|
||||
|
||||
private Dictionary<int, DateTimeOffset?>? 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<WellOperationCategory>((DbContext)db);
|
||||
cachedSectionTypes = cache.GetCachedTable<WellSectionType>((DbContext)db);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
var operationTypes = cachedOperationCategories
|
||||
.Distinct().OrderBy(o => o.Name);
|
||||
var result = operationTypes.Adapt<IEnumerable<WellOperationCategoryDto>>();
|
||||
var operationCategories = memoryCache
|
||||
.GetOrCreateBasic<WellOperationCategory>(db)
|
||||
.Distinct()
|
||||
.OrderBy(o => o.Name);
|
||||
var result = operationCategories.Adapt<IEnumerable<WellOperationCategoryDto>>();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
@ -36,14 +36,14 @@ namespace AsbCloudInfrastructure.Services
|
||||
.Include(w => w.RelationCompaniesWells)
|
||||
.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<CompanyTypeDto, CompanyType>(dbContext);
|
||||
this.wellOperationService = new WellOperationService.WellOperationService(db, memoryCache, this);
|
||||
companyTypesService = new CrudCacheServiceBase<CompanyTypeDto, CompanyType>(dbContext, memoryCache);
|
||||
}
|
||||
|
||||
private IEnumerable<RelationCompanyWell> GetCacheRelationCompanyWell()
|
||||
@ -167,13 +167,6 @@ namespace AsbCloudInfrastructure.Services
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
|
||||
protected override ICrudService<DepositDto> MakeService()
|
||||
{
|
||||
var dbContext = TestHelpter.MakeRealTestContext();
|
||||
return new CrudCacheServiceBase<DepositDto, Deposit>(dbContext);
|
||||
return new CrudCacheServiceBase<DepositDto, Deposit>(dbContext, TestHelpter.MemoryCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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" };
|
||||
|
@ -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;
|
||||
|
@ -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<ITelemetryTracker>();
|
||||
var imezoneServiceMock = new Mock<ITimezoneService>();
|
||||
var telemetryService = new TelemetryService(context, telemetryTracker.Object, imezoneServiceMock.Object);
|
||||
|
@ -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()
|
||||
|
@ -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<ITimezoneService> timezoneService;
|
||||
private readonly SimpleTimezoneDto timezone;
|
||||
private readonly AsbCloudDbContext context;
|
||||
private readonly CacheDb cacheDb;
|
||||
private readonly TelemetryService telemetryService;
|
||||
|
||||
private readonly TelemetryUserService telemetryUserService;
|
||||
private readonly TelemetryDataCache<TelemetryDataSaubDto> 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<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(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 = "Завулон";
|
||||
|
@ -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<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")
|
||||
.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<T>())
|
||||
.Returns(() => dbSetMock.Object);
|
||||
|
||||
var dbSetProperty = typeof(IAsbCloudDbContext)
|
||||
.GetProperties()
|
||||
.FirstOrDefault(p => p.PropertyType.IsPublic && p.PropertyType == typeof(DbSet<T>));
|
||||
|
||||
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<Func<IAsbCloudDbContext, DbSet<T>>>(propertyExpr, objParameterExpr);
|
||||
contextMock.SetupGet(expression).Returns(dbSetMock.Object);
|
||||
}
|
||||
|
||||
return contextMock;
|
||||
}
|
||||
|
||||
public static Mock<DbSet<T>> MakeDbSetMock<T>()
|
||||
where T : class
|
||||
=> MakeDbSetMock(new List<T>());
|
||||
|
||||
class DummyAsyncQueriable<T> : IQueryable<T>, IAsyncEnumerable<T>
|
||||
{
|
||||
var dbSetData = new List<T>();
|
||||
return MakeDbSetMock(dbSetData);
|
||||
private readonly IQueryable<T> queriable;
|
||||
|
||||
public Type ElementType => queriable.ElementType;
|
||||
|
||||
public Expression Expression => queriable.Expression;
|
||||
|
||||
public IQueryProvider Provider => queriable.Provider;
|
||||
|
||||
class QueriableAsyncEminaretor<T2> : IAsyncEnumerator<T2>
|
||||
{
|
||||
private readonly IEnumerator<T2> syncEnumerator;
|
||||
|
||||
public QueriableAsyncEminaretor(IEnumerator<T2> syncEnumerator)
|
||||
{
|
||||
this.syncEnumerator = syncEnumerator;
|
||||
}
|
||||
|
||||
public T2 Current => syncEnumerator.Current;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
syncEnumerator.Dispose();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask<bool> MoveNextAsync()
|
||||
{
|
||||
var result = syncEnumerator.MoveNext();
|
||||
return ValueTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public DummyAsyncQueriable(IEnumerable<T> dbSetData)
|
||||
{
|
||||
queriable = dbSetData.ToList().AsQueryable();
|
||||
}
|
||||
|
||||
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var enumerator = this.AsEnumerable().GetEnumerator();
|
||||
return new QueriableAsyncEminaretor<T>(enumerator);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
=> queriable.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> queriable.GetEnumerator();
|
||||
}
|
||||
|
||||
public static Mock<DbSet<T>> MakeDbSetMock<T>(IEnumerable<T> dbSetData)
|
||||
where T : class
|
||||
{
|
||||
var dbSetDataQueriable = dbSetData
|
||||
.ToList()
|
||||
.AsQueryable();
|
||||
var dbSetDataQueriable = new DummyAsyncQueriable<T>(dbSetData);
|
||||
|
||||
Mock<DbSet<T>> dbSetMock = new();
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.Provider).Returns(() => dbSetDataQueriable.Provider);
|
||||
@ -54,6 +116,10 @@ namespace AsbCloudWebApi.Tests
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.ElementType).Returns(() => dbSetDataQueriable.ElementType);
|
||||
dbSetMock.As<IQueryable<T>>().Setup(o => o.GetEnumerator()).Returns(() => dbSetDataQueriable.GetEnumerator());
|
||||
|
||||
dbSetMock.As<IAsyncEnumerable<T>>()
|
||||
.Setup(o => o.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
|
||||
.Returns(() => dbSetDataQueriable.GetAsyncEnumerator());
|
||||
|
||||
return dbSetMock;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -115,11 +115,15 @@ namespace AsbCloudWebApi.Controllers.SAUB
|
||||
|
||||
if (!isCompanyOwnsWell)
|
||||
return Forbid();
|
||||
|
||||
try
|
||||
{
|
||||
var dataDatesRange = wellService.GetDatesRange(idWell);
|
||||
|
||||
return Ok(dataDatesRange);
|
||||
}
|
||||
|
||||
catch(KeyNotFoundException)
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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")
|
||||
.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<AsbCloudDbContext> options)
|
||||
=> new AsbCloudDbContext(options);
|
||||
=> new (options);
|
||||
|
||||
public static AsbCloudDbContext MakeContext(string cusomConnectionString)
|
||||
=> MakeContext(new DbContextOptionsBuilder<AsbCloudDbContext>().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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user