DD.WellWorkover.Cloud/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
2024-05-13 11:08:51 +05:00

444 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<WellOperationDto, WellOperation>,
IWellOperationRepository
{
private const string cacheKeyWellOperations = "FirstAndLastFactWellsOperations";
private readonly IMemoryCache memoryCache;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IWellService wellService;
private Lazy<IDictionary<int, WellOperationCategoryDto>> LazyWellCategories { get; }
private Lazy<IDictionary<int, WellSectionTypeDto>> 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<WellSectionTypeDto> GetSectionTypes() =>
memoryCache
.GetOrCreateBasic(dbContext.WellSectionTypes)
.OrderBy(s => s.Order)
.Select(s => s.Adapt<WellSectionTypeDto>());
public async Task<IEnumerable<WellOperationDto>> GetAsync(WellOperationRequest request, CancellationToken token)
{
var (items, _) = await GetWithDaysAndNpvAsync(request, token);
return items;
}
public async Task<PaginationContainer<WellOperationDto>> GetPageAsync(WellOperationRequest request, CancellationToken token)
{
request.Skip = request.Skip ?? 0;
request.Take = request.Take ?? 32;
var (items, count) = await GetWithDaysAndNpvAsync(request, token);
var paginationContainer = new PaginationContainer<WellOperationDto>
{
Skip = request.Skip!.Value,
Take = request.Take!.Value,
Count = count,
Items = items
};
return paginationContainer;
}
public async Task<IEnumerable<WellGroupOpertionDto>> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token)
{
var query = BuildQuery(request);
var entities = await query
.Select(o => new
{
o.IdCategory,
DurationMinutes = o.DurationHours * 60,
DurationDepth = o.DepthEnd - o.DepthStart
})
.ToArrayAsync(token);
var parentRelationDictionary = wellOperationCategoryRepository.Get(true)
.ToDictionary(c => c.Id, c => new
{
c.Name,
c.IdParent
});
var dtos = entities
.GroupBy(o => o.IdCategory)
.Select(g => new WellGroupOpertionDto
{
IdCategory = g.Key,
Category = parentRelationDictionary[g.Key].Name,
Count = g.Count(),
MinutesAverage = g.Average(o => o.DurationMinutes),
MinutesMin = g.Min(o => o.DurationMinutes),
MinutesMax = g.Max(o => o.DurationMinutes),
TotalMinutes = g.Sum(o => o.DurationMinutes),
DeltaDepth = g.Sum(o => o.DurationDepth),
IdParent = parentRelationDictionary[g.Key].IdParent
});
while (dtos.All(x => x.IdParent != null))
{
dtos = dtos
.GroupBy(o => o.IdParent!)
.Select(g =>
{
var idCategory = g.Key ?? int.MinValue;
var category = parentRelationDictionary.GetValueOrDefault(idCategory);
var newDto = new WellGroupOpertionDto
{
IdCategory = idCategory,
Category = category?.Name ?? "unknown",
Count = g.Sum(o => o.Count),
DeltaDepth = g.Sum(o => o.DeltaDepth),
TotalMinutes = g.Sum(o => o.TotalMinutes),
Items = g.ToList(),
IdParent = category?.IdParent,
};
return newDto;
});
}
return dtos;
}
public async Task<int> InsertRangeAsync(IEnumerable<WellOperationDto> 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<int> UpdateRangeAsync(IEnumerable<WellOperationDto> 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<WellOperationDto> 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), "Все операции должны принадлежать одной скважине");
}
private async Task<IEnumerable<WellOperation>> GetByIdsWells(IEnumerable<int> idsWells, CancellationToken token)
{
var query = GetQuery()
.Where(e => idsWells.Contains(e.IdWell))
.OrderBy(e => e.DateStart);
var entities = await query.ToArrayAsync(token);
return entities;
}
private async Task<(IEnumerable<WellOperationDto> items, int count)> GetWithDaysAndNpvAsync(WellOperationRequest request, CancellationToken token)
{
var entities = await GetByIdsWells(request.IdsWell, token);
var groupedByWellAndType = entities
.GroupBy(e => new { e.IdWell, e.IdType });
var result = new List<WellOperationDto>();
var count = 0;
foreach (var wellOperationsWithType in groupedByWellAndType)
{
var firstWellOperation = wellOperationsWithType
.OrderBy(e => e.DateStart)
.FirstOrDefault()!;
var operationsWithNpt = wellOperationsWithType
.Where(o => WellOperationCategory.NonProductiveTimeSubIds.Contains(o.IdCategory));
IEnumerable<WellOperation> filteredWellOperations = FilterByRequest(wellOperationsWithType.AsQueryable(), request);
count += filteredWellOperations.Count();
if (request.Skip != null)
filteredWellOperations = filteredWellOperations.Skip((int)request.Skip);
if (request.Take != null)
filteredWellOperations = filteredWellOperations.Take((int)request.Take);
var dtos = filteredWellOperations
.Select(entity =>
{
var dto = Convert(entity);
dto.Day = (entity.DateStart - firstWellOperation.DateStart).TotalDays;
dto.NptHours = operationsWithNpt
.Where(o => o.DateStart <= entity.DateStart)
.Sum(e => e.DurationHours);
return dto;
});
result.AddRange(dtos);
}
return (result, count);
}
private static IQueryable<WellOperation> FilterByRequest(IQueryable<WellOperation> 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;
}
private IQueryable<WellOperation> BuildQuery(WellOperationRequest request)
{
var query = GetQuery()
.Where(e => request.IdsWell.Contains(e.IdWell))
.OrderBy(e => e.DateStart)
.AsQueryable();
query = FilterByRequest(query, request);
return query;
}
public async Task<IEnumerable<SectionByOperationsDto>> GetSectionsAsync(IEnumerable<int> idsWells, CancellationToken token)
{
const string keyCacheSections = "OperationsBySectionSummarties";
var cache = await memoryCache.GetOrCreateAsync(keyCacheSections, async (entry) =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
var query = dbContext.Set<WellOperation>()
.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<DatesRangeDto?> 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 (WellOperationDto First, WellOperationDto Last)? GetFirstAndLastFact(int idWell)
{
var cachedDictionary = memoryCache.GetOrCreate(cacheKeyWellOperations, (entry) =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
var query = dbContext.Set<WellOperation>()
.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<int> DeleteAsync(int id, CancellationToken token)
{
var result = await base.DeleteAsync(id, token);
if (result > 0)
memoryCache.Remove(cacheKeyWellOperations);
return result;
}
public override async Task<int> DeleteRangeAsync(IEnumerable<int> ids, CancellationToken token)
{
var result = await base.DeleteRangeAsync(ids, token);
if (result > 0)
memoryCache.Remove(cacheKeyWellOperations);
return result;
}
protected override WellOperation Convert(WellOperationDto src)
{
var entity = src.Adapt<WellOperation>();
entity.DateStart = src.DateStart.UtcDateTime;
return entity;
}
protected override WellOperationDto Convert(WellOperation src)
{
//TODO: пока такое получение TimeZone скважины, нужно исправить на Lazy
//Хоть мы и тянем данные из кэша, но от получения TimeZone в этом методе нужно избавиться, пока так
var timeZoneOffset = wellService.GetTimezone(src.IdWell).Offset;
var dto = src.Adapt<WellOperationDto>();
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;
}
}