using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperation; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudDb; using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; public class WellOperationRepository : CrudRepositoryBase, IWellOperationRepository { private const string cacheKeyWellOperations = "FirstAndLastFactWellsOperations"; private readonly IMemoryCache memoryCache; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; private readonly IWellService wellService; private Lazy> LazyWellCategories { get; } private Lazy> LazyWellSectionTypes { get; } public WellOperationRepository(IAsbCloudDbContext context, IMemoryCache memoryCache, IWellOperationCategoryRepository wellOperationCategoryRepository, IWellService wellService) : base(context, dbSet => dbSet) { this.memoryCache = memoryCache; this.wellOperationCategoryRepository = wellOperationCategoryRepository; this.wellService = wellService; LazyWellCategories = new(() => wellOperationCategoryRepository.Get(true, false).ToDictionary(c => c.Id)); LazyWellSectionTypes = new(() => GetSectionTypes().ToDictionary(c => c.Id)); } public IEnumerable GetSectionTypes() => memoryCache .GetOrCreateBasic(dbContext.WellSectionTypes) .OrderBy(s => s.Order) .Select(s => s.Adapt()); public async Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token) { EnsureValidWellOperations(dtos); var result = 0; if (!deleteBeforeInsert) { result = await InsertRangeAsync(dtos, token); if (result > 0) memoryCache.Remove(cacheKeyWellOperations); return result; } var idType = dtos.First().IdType; var idWell = dtos.First().IdWell; var existingOperationIds = await dbContext.WellOperations .Where(e => e.IdWell == idWell && e.IdType == idType) .Select(e => e.Id) .ToArrayAsync(token); await DeleteRangeAsync(existingOperationIds, token); result = await InsertRangeAsync(dtos, token); if (result > 0) memoryCache.Remove(cacheKeyWellOperations); return result; } public override async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) { EnsureValidWellOperations(dtos); var result = await base.UpdateRangeAsync(dtos, token); if (result > 0) memoryCache.Remove(cacheKeyWellOperations); return result; } private static void EnsureValidWellOperations(IEnumerable dtos) { if (dtos.GroupBy(d => d.IdType).Count() > 1) throw new ArgumentInvalidException(nameof(dtos), "Все операции должны быть одного типа"); if (dtos.GroupBy(d => d.IdType).Count() > 1) throw new ArgumentInvalidException(nameof(dtos), "Все операции должны принадлежать одной скважине"); } public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) { const string keyCacheSections = "OperationsBySectionSummarties"; var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); var query = dbContext.Set() .GroupBy(operation => new { operation.IdWell, operation.IdType, operation.IdWellSectionType, operation.WellSectionType.Caption, }) .Select(group => new { group.Key.IdWell, group.Key.IdType, group.Key.IdWellSectionType, group.Key.Caption, First = group .OrderBy(operation => operation.DateStart) .Select(operation => new { operation.DateStart, operation.DepthStart, }) .First(), Last = group .OrderByDescending(operation => operation.DateStart) .Select(operation => new { operation.DateStart, operation.DurationHours, operation.DepthEnd, }) .First(), }) .Where(s => idsWells.Contains(s.IdWell)); var dbData = await query.ToArrayAsync(token); var sections = dbData.Select( item => new SectionByOperationsDto { IdWell = item.IdWell, IdType = item.IdType, IdWellSectionType = item.IdWellSectionType, Caption = item.Caption, DateStart = item.First.DateStart, DepthStart = item.First.DepthStart, DateEnd = item.Last.DateStart.AddHours(item.Last.DurationHours), DepthEnd = item.Last.DepthEnd, }) .ToArray() .AsEnumerable(); entry.Value = sections; return sections; }); return cache!; } public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) { var query = dbContext.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); if (!await query.AnyAsync(cancellationToken)) return null; var timeZoneOffset = wellService.GetTimezone(idWell).Offset; var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); return new DatesRangeDto { From = minDate.ToOffset(timeZoneOffset), To = maxDate.ToOffset(timeZoneOffset) }; } public (WellOperationBaseDto First, WellOperationBaseDto Last)? GetFirstAndLastFact(int idWell) { var cachedDictionary = memoryCache.GetOrCreate(cacheKeyWellOperations, (entry) => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); var query = dbContext.Set() .Where(o => o.IdType == WellOperation.IdOperationTypeFact) .GroupBy(o => o.IdWell) .Select(group => new { IdWell = group.Key, FirstFact = group.OrderBy(o => o.DateStart).First(), LastFact = group.OrderBy(o => o.DateStart).Last(), }); var entities = query.ToArray(); var dictionary = entities.ToDictionary(s => s.IdWell, s => (Convert(s.FirstFact), Convert(s.LastFact))); entry.Value = dictionary; return dictionary; })!; var firstAndLast = cachedDictionary.GetValueOrDefault(idWell); return firstAndLast; } public override async Task DeleteAsync(int id, CancellationToken token) { var result = await base.DeleteAsync(id, token); if (result > 0) memoryCache.Remove(cacheKeyWellOperations); return result; } public override async Task DeleteRangeAsync(IEnumerable ids, CancellationToken token) { var result = await base.DeleteRangeAsync(ids, token); if (result > 0) memoryCache.Remove(cacheKeyWellOperations); return result; } protected override WellOperation Convert(WellOperationBaseDto src) { var entity = src.Adapt(); entity.DateStart = src.DateStart.UtcDateTime; return entity; } private WellOperationBaseDto Convert(WellOperation src, TimeSpan timezoneOffset) { var dto = src.Adapt(); dto.DateStart = src.DateStart.ToOffset(timezoneOffset); dto.LastUpdateDate = src.LastUpdateDate.ToOffset(timezoneOffset); dto.OperationCategoryName = LazyWellCategories.Value.TryGetValue(src.IdCategory, out WellOperationCategoryDto? category) ? category.Name : string.Empty; dto.WellSectionTypeCaption = LazyWellSectionTypes.Value.TryGetValue(src.IdWellSectionType, out WellSectionTypeDto? sectionType) ? sectionType.Caption : string.Empty; return dto; } public async Task> GetAll(WellOperationRequest request, CancellationToken token) { var timezoneOffsetDictionary = new Dictionary(); foreach (var idWell in request.IdsWell) { var offset = wellService.GetTimezone(idWell).Offset; timezoneOffsetDictionary.Add(idWell, offset); } var query = GetQuery() .Where(e => request.IdsWell.Contains(e.IdWell)) .OrderBy(e => e.DateStart) .AsQueryable(); query = FilterByRequest(query, request); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(o => Convert(o, timezoneOffsetDictionary[o.IdWell])); return dtos; } public async Task> GetAll(WellOperationRepositoryRequest request, CancellationToken token) { var timezoneOffsetDictionary = new Dictionary(); foreach (var idWell in request.IdsWell) { var offset = wellService.GetTimezone(idWell).Offset; timezoneOffsetDictionary.Add(idWell, offset); } var query = GetQuery() .Where(e => request.IdsWell.Contains(e.IdWell)) .OrderBy(e => e.DateStart) .AsQueryable(); query = FilterByRequest(query, request); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(o => Convert(o, timezoneOffsetDictionary[o.IdWell])); return dtos; } public async Task> GetAll(int idWell, CancellationToken token) { var offset = wellService.GetTimezone(idWell).Offset; var query = GetQuery() .Include(o => o.OperationCategory) .Include(o => o.WellSectionType) .Where(o => o.IdWell == idWell) .OrderBy(o => o.DateStart) .ThenBy(o => o.DepthEnd); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(o => Convert(o, offset)); return dtos; } public static IQueryable FilterByRequest(IQueryable entities, WellOperationRequest request) { if (request.OperationType.HasValue) entities = entities.Where(e => e.IdType == request.OperationType.Value); if (request.SectionTypeIds?.Any() is true) entities = entities.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); if (request.OperationCategoryIds?.Any() is true) entities = entities.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); if (request.GeDepth.HasValue) entities = entities.Where(e => e.DepthEnd >= request.GeDepth.Value); if (request.LeDepth.HasValue) entities = entities.Where(e => e.DepthEnd <= request.LeDepth.Value); if (request.GeDate.HasValue) { var geDateUtc = request.GeDate.Value.UtcDateTime; entities = entities.Where(e => e.DateStart >= geDateUtc); } if (request.LeDate.HasValue) { var leDateUtc = request.LeDate.Value.UtcDateTime; entities = entities.Where(e => e.DateStart <= leDateUtc); } if (request.SortFields?.Any() is true) entities = entities.AsQueryable().SortBy(request.SortFields); else entities = entities.AsQueryable().OrderBy(e => e.DateStart); return entities; } public static IQueryable FilterByRequest(IQueryable entities, WellOperationRepositoryRequest request) { if (request.OperationType.HasValue) entities = entities.Where(e => e.IdType == request.OperationType.Value); if (request.LeDepth.HasValue) entities = entities.Where(e => e.DepthEnd <= request.LeDepth.Value); if (request.LeDate.HasValue) { var leDateUtc = request.LeDate.Value.UtcDateTime; entities = entities.Where(e => e.DateStart <= leDateUtc); } entities = entities.AsQueryable().OrderBy(e => e.DateStart); return entities; } }