From 88c928cd5d2edeb7a578c533243bd657f2570757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 20 Mar 2024 10:52:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20WellOperationRepository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IWellOperationCategoryRepository.cs | 4 +- .../Repositories/IWellOperationRepository.cs | 90 +- AsbCloudApp/Requests/WellOperationRequest.cs | 140 +-- .../Services/IDetectedOperationService.cs | 2 +- .../Services/IOperationsStatService.cs | 1 + .../IWellCompositeOperationService.cs | 6 +- .../Repository/CrudRepositoryBase.cs | 28 + .../WellOperationCategoryRepository.cs | 5 +- .../Repository/WellOperationRepository.cs | 1028 ++++++----------- 9 files changed, 438 insertions(+), 866 deletions(-) diff --git a/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs b/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs index 4e18c231..ecf915cd 100644 --- a/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs @@ -1,5 +1,5 @@ -using AsbCloudApp.Data; -using System.Collections.Generic; +using System.Collections.Generic; +using AsbCloudApp.Data.WellOperation; namespace AsbCloudApp.Repositories { diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs index dd39ce52..ffc69970 100644 --- a/AsbCloudApp/Repositories/IWellOperationRepository.cs +++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; namespace AsbCloudApp.Repositories { @@ -17,24 +18,8 @@ namespace AsbCloudApp.Repositories /// /// IEnumerable GetSectionTypes(); - - /// - /// список плановых операций для сопоставления - /// - /// - /// - /// - /// - Task GetOperationsPlanAsync(int idWell, DateTime? currentDate, CancellationToken token); - - /// - /// дата/время первой операции по скважине - /// - /// - /// - DateTimeOffset? FirstOperationDate(int idWell); - - /// + + /// /// Получить страницу списка операций /// /// @@ -42,15 +27,7 @@ namespace AsbCloudApp.Repositories /// Task> GetAsync(WellOperationRequest request, CancellationToken token); - /// - /// Получить список операций по запросу - /// - /// - /// - /// - Task> GetAsync(WellsOperationRequest request, CancellationToken token); - - /// + /// /// Получить страницу списка операций /// /// @@ -58,39 +35,30 @@ namespace AsbCloudApp.Repositories /// Task> GetPageAsync(WellOperationRequest request, CancellationToken token); - /// - /// Получить операцию по id - /// - /// - /// - /// - Task GetOrDefaultAsync(int id, CancellationToken token); - - /// + /// /// Получить статистику операции по скважине с группировкой по категориям /// /// /// /// - Task> GetGroupOperationsStatAsync( - WellOperationRequest request, - CancellationToken token); + Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token); + + /// + /// Добавить несколько операций + /// + /// + /// + /// + /// + Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token); /// - /// Добавить несколько операций за один раз - /// - /// - /// - /// - Task InsertRangeAsync(IEnumerable wellOperationDtos, CancellationToken token); - - /// - /// Обновить существующую операцию + /// Обновить несколько существующую операций /// /// /// /// - Task UpdateAsync(WellOperationDto dto, CancellationToken token); + Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token); /// /// Удалить операции по id @@ -98,7 +66,7 @@ namespace AsbCloudApp.Repositories /// /// /// - Task DeleteAsync(IEnumerable ids, CancellationToken token); + Task DeleteRangeAsync(IEnumerable ids, CancellationToken token); /// /// Получить секции скважин из операций ГГД. Секцие поделены на плановые и фактические. @@ -115,24 +83,6 @@ namespace AsbCloudApp.Repositories /// /// /// - Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken); - - /// - /// Удаление полных дубликатов операций по всем скважинам - /// - /// - /// - /// - Task RemoveDuplicates(Action onProgressCallback, CancellationToken token); - - /// - /// Усечение пересекающейся последующей операции по дате и глубине забоя - /// - /// Фильтр по дате. Если хоть одна операция попадет в в фильтр, то будет обработана вся скважина, а не только эта операция - /// Фильтр по дате. Если хоть одна операция попадет в в фильтр, то будет обработана вся скважина, а не только эта операция - /// - /// - /// - Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, Action onProgressCallback, CancellationToken token); - } + Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken); + } } \ No newline at end of file diff --git a/AsbCloudApp/Requests/WellOperationRequest.cs b/AsbCloudApp/Requests/WellOperationRequest.cs index 1d190018..3164c2d1 100644 --- a/AsbCloudApp/Requests/WellOperationRequest.cs +++ b/AsbCloudApp/Requests/WellOperationRequest.cs @@ -3,110 +3,50 @@ using System.Collections.Generic; namespace AsbCloudApp.Requests { - /// - /// параметры для запроса списка операций - /// - public class WellOperationRequestBase : RequestBase - { - /// - /// фильтр по дате начала операции - /// - public DateTime? GeDate { get; set; } + public class WellOperationRequest : RequestBase + { + /// + /// Идентификаторы скважин + /// + public IEnumerable? IdsWell { get; set; } - /// - /// фильтр по дате окончания операции - /// - public DateTime? LtDate { get; set; } + /// + /// Больше или равно дате начала операции + /// + public DateTimeOffset? GeDate { get; set; } - /// - /// фильтр. Больше или равно глубины скважины на начало операции. - /// - public double? GeDepth { get; set; } + /// + /// Меньше или равно дате окончания операции + /// + public DateTimeOffset? LeDate { get; set; } - /// - /// фильтр. Меньше или равно глубины скважины на конец операции. - /// - public double? LeDepth { get; set; } + /// + /// Больше или равно глубины скважины на начало операции. + /// + public double? GeDepth { get; set; } - /// - /// фильтр по списку id категорий операции - /// - public IEnumerable? OperationCategoryIds { get; set; } + /// + /// Меньше или равно глубины скважины на конец операции. + /// + public double? LeDepth { get; set; } - /// - /// фильтр по план = 0, факт = 1 - /// - public int? OperationType { get; set; } + /// + /// Идентификаторы категорий операции + /// + public IEnumerable? OperationCategoryIds { get; set; } - /// - /// фильтр по списку id конструкций секции - /// - public IEnumerable? SectionTypeIds { get; set; } + /// + /// Тип операций + /// + /// 0 - плановая операция + /// 1 - фактическая операция + /// + /// + public int? OperationType { get; set; } - /// - /// Параметры для запроса списка операций. - /// Базовый конструктор - /// - public WellOperationRequestBase() - { } - - /// - /// Параметры для запроса списка операций. - /// Копирующий конструктор - /// - /// - public WellOperationRequestBase(WellOperationRequestBase request) - { - GeDepth = request.GeDepth; - LeDepth = request.LeDepth; - GeDate = request.GeDate; - LtDate = request.LtDate; - - OperationCategoryIds = request.OperationCategoryIds; - OperationType = request.OperationType; - SectionTypeIds = request.SectionTypeIds; - - Skip = request.Skip; - Take = request.Take; - SortFields = request.SortFields; - } - } - - /// - /// Параметры для запроса списка операций (с id скважины) - /// - public class WellOperationRequest : WellOperationRequestBase - { - /// - /// id скважины - /// - public int IdWell { get; set; } - - /// - /// ctor - /// - public WellOperationRequest() { } - - /// - /// копирующий конструктор - /// - /// - /// - public WellOperationRequest(WellOperationRequestBase request, int idWell) - :base(request) - { - IdWell = idWell; - } - } - - /// - /// Параметры для запроса списка операций (с массивом id скважин) - /// - public class WellsOperationRequest : WellOperationRequestBase - { - /// - /// ids скважин - /// - public IEnumerable IdsWell { get; set; } = null!; - } -} + /// + /// Идентификаторы конструкций секции + /// + public IEnumerable? SectionTypeIds { get; set; } + } +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs index 998032ba..208706f7 100644 --- a/AsbCloudApp/Services/IDetectedOperationService.cs +++ b/AsbCloudApp/Services/IDetectedOperationService.cs @@ -1,10 +1,10 @@ using System; -using AsbCloudApp.Data; using AsbCloudApp.Data.DetectedOperation; using AsbCloudApp.Requests; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; namespace AsbCloudApp.Services { diff --git a/AsbCloudApp/Services/IOperationsStatService.cs b/AsbCloudApp/Services/IOperationsStatService.cs index a92d0553..f5ec7c7c 100644 --- a/AsbCloudApp/Services/IOperationsStatService.cs +++ b/AsbCloudApp/Services/IOperationsStatService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; namespace AsbCloudApp.Services { diff --git a/AsbCloudApp/Services/IWellCompositeOperationService.cs b/AsbCloudApp/Services/IWellCompositeOperationService.cs index 94ba1f93..96dbcf2b 100644 --- a/AsbCloudApp/Services/IWellCompositeOperationService.cs +++ b/AsbCloudApp/Services/IWellCompositeOperationService.cs @@ -1,7 +1,7 @@ -using AsbCloudApp.Data; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperation; namespace AsbCloudApp.Services { @@ -16,6 +16,6 @@ namespace AsbCloudApp.Services /// /// /// - Task>> GetAsync(IEnumerable idsWells, CancellationToken token); + Task>> GetAsync(IEnumerable idsWells, CancellationToken token); } } diff --git a/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs b/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs index f21dd487..3cdce610 100644 --- a/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs +++ b/AsbCloudInfrastructure/Repository/CrudRepositoryBase.cs @@ -114,6 +114,26 @@ namespace AsbCloudInfrastructure.Repository entry.State = EntityState.Detached; return entry.Entity.Id; } + + public virtual async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) + { + if (!dtos.Any()) + return 0; + + var ids = dtos.Select(d => d.Id); + + var countExistingEntities = await dbSet + .Where(d => ids.Contains(d.Id)) + .CountAsync(token); + + if (ids.Count() > countExistingEntities) + return ICrudRepository.ErrorIdNotFound; + + var entities = dtos.Select(Convert); + dbContext.Set().UpdateRange(entities); + + return await dbContext.SaveChangesAsync(token); + } /// public virtual Task DeleteAsync(int id, CancellationToken token) @@ -129,6 +149,14 @@ namespace AsbCloudInfrastructure.Repository return affected; } + public virtual async Task DeleteRangeAsync(IEnumerable ids, CancellationToken token) + { + var query = dbContext.Set().Where(e => ids.Contains(e.Id)); + dbContext.Set().RemoveRange(query); + + return await dbContext.SaveChangesAsync(token); + } + protected virtual TDto Convert(TEntity src) => src.Adapt(); protected virtual TEntity Convert(TDto src) => src.Adapt(); diff --git a/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs index 84da5f3d..927a424e 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs @@ -1,11 +1,10 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Repositories; +using AsbCloudApp.Repositories; using AsbCloudDb.Model; using Mapster; using Microsoft.Extensions.Caching.Memory; -using System; using System.Collections.Generic; using System.Linq; +using AsbCloudApp.Data.WellOperation; namespace AsbCloudInfrastructure.Repository; diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 329d2502..702ca982 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -1,4 +1,11 @@ -using AsbCloudApp.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Data.WellOperation; +using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; @@ -7,693 +14,340 @@ using AsbCloudDb.Model; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace AsbCloudInfrastructure.Repository; -/// -/// репозиторий операций по скважине -/// -public class WellOperationRepository : IWellOperationRepository +public class WellOperationRepository : CrudRepositoryBase, + IWellOperationRepository { - private const string KeyCacheSections = "OperationsBySectionSummarties"; - - private readonly IAsbCloudDbContext db; - private readonly IMemoryCache memoryCache; - private readonly IWellService wellService; - private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; - - public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, IWellOperationCategoryRepository wellOperationCategoryRepository) - { - this.db = db; - this.memoryCache = memoryCache; - this.wellService = wellService; - this.wellOperationCategoryRepository = wellOperationCategoryRepository; - } - - public IEnumerable GetSectionTypes() => - memoryCache - .GetOrCreateBasic(db.Set()) - .OrderBy(s => s.Order) - .Select(s => s.Adapt()); - - public async Task GetOperationsPlanAsync(int idWell, DateTime? currentDate, CancellationToken token) - { - var timezone = wellService.GetTimezone(idWell); - var request = new WellOperationRequest() - { - IdWell = idWell, - OperationType = WellOperation.IdOperationTypePlan, - }; - - var dtos = await BuildQuery(request) - .AsNoTracking() - .ToArrayAsync(token); - - var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token); - - var result = new WellOperationPlanDto() - { - WellOperationsPlan = dtos.Select(Convert), - DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation - }; - - return result; - } - - private async Task GetDateLastAssosiatedPlanOperationAsync( - int idWell, - DateTime? lessThenDate, - double timeZoneHours, - CancellationToken token) - { - if (lessThenDate is null) - return null; - - var currentDateOffset = lessThenDate.Value.ToUtcDateTimeOffset(timeZoneHours); - var timeZoneOffset = TimeSpan.FromHours(timeZoneHours); - - var lastFactOperation = await db.WellOperations - .Where(o => o.IdWell == idWell) - .Where(o => o.IdType == WellOperation.IdOperationTypeFact) - .Where(o => o.IdPlan != null) - .Where(o => o.DateStart < currentDateOffset) - .Include(x => x.OperationPlan) - .OrderByDescending(x => x.DateStart) - .FirstOrDefaultAsync(token) - .ConfigureAwait(false); - - if (lastFactOperation is not null) - return DateTime.SpecifyKind(lastFactOperation.OperationPlan!.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified); - - return null; - } - - /// - public async Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token) - { - var cache = await memoryCache.GetOrCreateAsync(KeyCacheSections, async (entry) => - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30); - - var query = db.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(), - }); - 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; - }); - - var sections = cache.Where(s => idsWells.Contains(s.IdWell)); - return sections; - } - - public async Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken) - { - var timezone = wellService.GetTimezone(idWell); - - var query = db.WellOperations.Where(o => o.IdWell == idWell && o.IdType == idType); - - if (!await query.AnyAsync(cancellationToken)) - return null; - - var minDate = await query.MinAsync(o => o.DateStart, cancellationToken); - var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); - - return new DatesRangeDto - { - From = minDate.ToRemoteDateTime(timezone.Hours), - To = maxDate.ToRemoteDateTime(timezone.Hours) - }; - } - - /// - public DateTimeOffset? FirstOperationDate(int idWell) - { - var sections = GetSectionsAsync(new[] { idWell }, CancellationToken.None).Result; - var first = sections.FirstOrDefault(section => section.IdType == WellOperation.IdOperationTypeFact) - ?? sections.FirstOrDefault(section => section.IdType == WellOperation.IdOperationTypePlan); - - return first?.DateStart; - } - - /// - public async Task> GetAsync( - WellOperationRequest request, - CancellationToken token) - { - var query = BuildQuery(request) - .AsNoTracking(); - - var dtos = await query.ToArrayAsync(token); - - return dtos.Select(Convert); - } - - public async Task> GetAsync( - WellsOperationRequest request, - CancellationToken token) - { - var query = BuildQuery(request) - .AsNoTracking(); - - var dtos = await query.ToArrayAsync(token); - return dtos; - } - - /// - public async Task> GetPageAsync( - WellOperationRequest request, - CancellationToken token) - { - var query = BuildQuery(request); - - var result = new PaginationContainer - { - Skip = request.Skip ?? 0, - Take = request.Take ?? 32, - Count = await query.CountAsync(token), - }; - - var dtos = await query.ToArrayAsync(token); - - result.Items = dtos.Select(Convert); - - return result; - } - - /// - public async Task GetOrDefaultAsync(int id, - CancellationToken token) - { - var entity = await db.WellOperations - .Include(s => s.WellSectionType) - .Include(s => s.OperationCategory) - .FirstOrDefaultAsync(e => e.Id == id, token) - .ConfigureAwait(false); - - if (entity is null) - return null; - - var timezone = wellService.GetTimezone(entity.IdWell); - - var dto = entity.Adapt(); - dto.WellSectionTypeName = entity.WellSectionType.Caption; - dto.DateStart = entity.DateStart.ToRemoteDateTime(timezone.Hours); - dto.CategoryName = entity.OperationCategory.Name; - return dto; - } - - /// - public async Task> GetGroupOperationsStatAsync( - WellOperationRequest request, - CancellationToken token) - { - // TODO: Rename controller method - request.OperationType = WellOperation.IdOperationTypeFact; - var query = BuildQuery(request); - var entities = await query - .Select(o => new - { - o.IdCategory, - DurationMinutes = o.DurationHours * 60, - DurationDepth = o.DepthEnd - o.DepthStart - }) - .ToListAsync(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 InsertRangeAsync( - IEnumerable wellOperationDtos, - CancellationToken token) - { - var firstOperation = wellOperationDtos - .FirstOrDefault(); - if (firstOperation is null) - return 0; - - var idWell = firstOperation.IdWell; - - var timezone = wellService.GetTimezone(idWell); - foreach (var dto in wellOperationDtos) - { - var entity = dto.Adapt(); - entity.Id = default; - entity.DateStart = dto.DateStart.DateTime.ToUtcDateTimeOffset(timezone.Hours); - entity.IdWell = idWell; - entity.LastUpdateDate = DateTimeOffset.UtcNow; - db.WellOperations.Add(entity); - } - - var result = await db.SaveChangesAsync(token); - if (result > 0) - memoryCache.Remove(KeyCacheSections); - return result; - - } - - /// - public async Task UpdateAsync( - WellOperationDto dto, CancellationToken token) - { - var timezone = wellService.GetTimezone(dto.IdWell); - var entity = dto.Adapt(); - entity.DateStart = dto.DateStart.DateTime.ToUtcDateTimeOffset(timezone.Hours); - entity.LastUpdateDate = DateTimeOffset.UtcNow; - db.WellOperations.Update(entity); - - var result = await db.SaveChangesAsync(token); - if (result > 0) - memoryCache.Remove(KeyCacheSections); - return result; - } - - /// - public async Task DeleteAsync(IEnumerable ids, - CancellationToken token) - { - var query = db.WellOperations.Where(e => ids.Contains(e.Id)); - db.WellOperations.RemoveRange(query); - - var result = await db.SaveChangesAsync(token); - if (result > 0) - memoryCache.Remove(KeyCacheSections); - return result; - } - - /// - /// В результате попрежнему требуется конвертировать дату - /// - /// - /// - /// - private IQueryable BuildQuery(WellOperationRequest request) - { - var timezone = wellService.GetTimezone(request.IdWell); - var timeZoneOffset = timezone.Hours; - - var query = db.WellOperations - .Include(s => s.WellSectionType) - .Include(s => s.OperationCategory) - .Where(o => o.IdWell == request.IdWell); - - if (request.OperationType.HasValue) - query = query.Where(e => e.IdType == request.OperationType.Value); - - if (request.SectionTypeIds?.Any() == true) - query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); - - if (request.OperationCategoryIds?.Any() == true) - query = query.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); - - if (request.GeDepth.HasValue) - query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); - - if (request.LeDepth.HasValue) - query = query.Where(e => e.DepthEnd <= request.LeDepth.Value); - - if (request.GeDate.HasValue) - { - var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timeZoneOffset); - query = query.Where(e => e.DateStart >= geDateOffset); - } - - if (request.LtDate.HasValue) - { - var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timeZoneOffset); - query = query.Where(e => e.DateStart < ltDateOffset); - } - - var currentWellOperations = db.WellOperations - .Where(subOp => subOp.IdWell == request.IdWell); - - var wellOperationsWithCategoryNpt = currentWellOperations - .Where(subOp => subOp.IdType == 1) - .Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory)); - - // TODO: Вынести query.Select из метода BuildQuery - var dtos = query.Select(o => new WellOperationDto - { - Id = o.Id, - IdPlan = o.IdPlan, - IdType = o.IdType, - IdWell = o.IdWell, - IdWellSectionType = o.IdWellSectionType, - IdCategory = o.IdCategory, - IdParentCategory = o.OperationCategory.IdParent, - - CategoryName = o.OperationCategory.Name, - WellSectionTypeName = o.WellSectionType.Caption, - DateStart = o.DateStart, - DepthStart = o.DepthStart, - DepthEnd = o.DepthEnd, - DurationHours = o.DurationHours, - CategoryInfo = o.CategoryInfo, - Comment = o.Comment, - - NptHours = wellOperationsWithCategoryNpt - .Where(subOp => subOp.DateStart <= o.DateStart) - .Select(subOp => subOp.DurationHours) - .Sum(), - - Day = (o.DateStart - currentWellOperations - .Where(subOp => subOp.IdType == o.IdType) - .Where(subOp => subOp.DateStart <= o.DateStart) - .Min(subOp => subOp.DateStart)) - .TotalDays, - IdUser = o.IdUser, - LastUpdateDate = o.LastUpdateDate, - }); - - if (request.SortFields?.Any() == true) - { - dtos = dtos.SortBy(request.SortFields); - } - - dtos = dtos - .OrderBy(e => e.DateStart) - .ThenBy(e => e.DepthEnd) - .ThenBy(e => e.Id); - - if (request.Skip.HasValue) - dtos = dtos.Skip(request.Skip.Value); - - if (request.Take.HasValue) - dtos = dtos.Take(request.Take.Value); - - return dtos.AsNoTracking(); - } - - /// - /// Получение данных по запросу - /// - /// - /// - /// - private IQueryable BuildQuery(WellsOperationRequest request) - { - var query = db.WellOperations - .Where(o => request.IdsWell.Contains(o.IdWell)) - .Where(o => request.OperationType == o.IdType); - - if (request.SectionTypeIds?.Any() == true) - query = query.Where(o => request.SectionTypeIds.Contains(o.IdWellSectionType)); - - if (request.OperationCategoryIds?.Any() == true) - query = query.Where(o => request.OperationCategoryIds.Contains(o.IdCategory)); - - // TODO: Вынести query.Select из метода BuildQuery - var dtos = query.Select(o => new WellOperationDataDto - { - DepthStart = o.DepthStart, - DurationHours = o.DurationHours, - IdCategory = o.IdCategory, - IdWell = o.IdWell, - IdWellSectionType = o.IdWellSectionType, - OperationCategoryName = o.OperationCategory.Name, - WellSectionTypeCaption = o.WellSectionType.Caption, - }); - - if (request.SortFields?.Any() == true) - { - dtos = dtos.SortBy(request.SortFields); - } - - if (request.Skip.HasValue) - dtos = dtos.Skip(request.Skip.Value); - - if (request.Take.HasValue) - dtos = dtos.Take(request.Take.Value); - - return dtos.AsNoTracking(); - } - - private WellOperationDto Convert(WellOperationDto dto) - { - var timezone = wellService.GetTimezone(dto.IdWell); - var timezoneOffset = TimeSpan.FromHours(timezone.Hours); - - var dtoWithRemoteDateTime = dto.Adapt(); - - dtoWithRemoteDateTime.DateStart = dto.DateStart.ToOffset(TimeSpan.FromHours(timezoneOffset.Hours)); - dtoWithRemoteDateTime.LastUpdateDate = dto.LastUpdateDate?.ToOffset(TimeSpan.FromHours(timezoneOffset.Hours)); - - return dtoWithRemoteDateTime; - } - - public async Task RemoveDuplicates(Action onProgressCallback, CancellationToken token) - { - IQueryable dbset = db.Set(); - var query = dbset - .GroupBy(o => new { o.IdWell, o.IdType }) - .Select(g => new { g.Key.IdWell, g.Key.IdType }); - - var groups = await query - .ToArrayAsync(token); - - var count = groups.Count(); - var i = 0; - var totalRemoved = 0; - var total = 0; - foreach (var group in groups) - { - var result = await RemoveDuplicatesInGroup(group.IdWell, group.IdType, token); - totalRemoved += result.removed; - total += result.total; - var percent = i++ / count; - var message = $"RemoveDuplicates [{i} of {count}] wellId: {group.IdWell}, opType: {group.IdType}, affected: {result.removed} of {result.total}"; - onProgressCallback?.Invoke(message, percent); - Trace.TraceInformation(message); - } - var messageDone = $"RemoveDuplicates done [{i} of {count}] totalAffected: {totalRemoved} of {total}"; - Trace.TraceInformation(messageDone); - onProgressCallback?.Invoke(messageDone, 1); - return totalRemoved; - } - - private async Task<(int removed, int total)> RemoveDuplicatesInGroup(int idWell, int idType, CancellationToken token) - { - var dbset = db.Set(); - var entities = await dbset - .Where(o => o.IdWell == idWell && o.IdType == idType) - .OrderBy(o => o.DateStart) - .ToListAsync(token); - - using var entitiesEnumerator = entities.GetEnumerator(); - - if (!entitiesEnumerator.MoveNext()) - return (0, 0); - - var preEntity = entitiesEnumerator.Current; - while (entitiesEnumerator.MoveNext()) - { - var entity = entitiesEnumerator.Current; - if (preEntity.IsSame(entity)) - dbset.Remove(entity); - else - preEntity = entity; - } - var removed = await db.SaveChangesAsync(token); - return (removed, entities.Count); - } - - public async Task TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, Action onProgressCallback, CancellationToken token) - { - var leDateUtc = leDate.ToUniversalTime(); - IQueryable query = db.Set(); - if (geDate.HasValue) - { - var geDateUtc = geDate.Value.ToUniversalTime(); - query = query.Where(e => e.DateStart >= geDateUtc); - } - - var groups = await query - .GroupBy(o => new { o.IdWell, o.IdType }) - .Select(g => new{ - MaxDate = g.Max(o => o.DateStart), - g.Key.IdWell, - g.Key.IdType, - }) - .Where(g => g.MaxDate <= leDateUtc) - .ToArrayAsync(token); - - var count = groups.Count(); - var i = 0; - (int takeover, int trimmed,int total) totalResult = (0, 0, 0); - foreach (var group in groups) - { - var result = await TrimOverlapping(group.IdWell, group.IdType, token); - totalResult.takeover += result.takeover; - totalResult.trimmed += result.trimmed; - totalResult.total += result.total; - var percent = i++ / count; - var message = $"TrimOverlapping [{i} of {count}] wellId: {group.IdWell}, opType: {group.IdType}, takeover:{result.takeover}, trimmed:{result.trimmed}, of {result.total}"; - onProgressCallback?.Invoke(message, percent); - Trace.TraceInformation(message); - } - var messageDone = $"TrimOverlapping done [{i} of {count}] total takeover:{totalResult.takeover}, total trimmed:{totalResult.trimmed} of {totalResult.total}"; - Trace.TraceInformation(messageDone); - onProgressCallback?.Invoke(messageDone, 1); - return totalResult.takeover + totalResult.trimmed; - } - - private async Task<(int takeover, int trimmed, int total)> TrimOverlapping(int idWell, int idType, CancellationToken token) - { - var dbset = db.Set(); - var query = dbset - .Where(o => o.IdWell == idWell) - .Where(o => o.IdType == idType) - .OrderBy(o => o.DateStart) - .ThenBy(o => o.DepthStart); - - var entities = await query - .ToListAsync(token); - - using var entitiesEnumerator = entities.GetEnumerator(); - - if (!entitiesEnumerator.MoveNext()) - return (0, 0, 0); - - int takeover = 0; - int trimmed = 0; - var preEntity = entitiesEnumerator.Current; - while (entitiesEnumerator.MoveNext()) - { - var entity = entitiesEnumerator.Current; - var preDepth = preEntity.DepthEnd; - - if (preEntity.DepthEnd >= entity.DepthEnd) - { - dbset.Remove(entity); - takeover++; - continue; - } - - if (preEntity.DepthEnd > entity.DepthStart) - { - entity.DepthStart = preEntity.DepthEnd; - trimmed++; - } - - var preDate = preEntity.DateStart.AddHours(preEntity.DurationHours); - - if (preDate >= entity.DateStart.AddHours(entity.DurationHours)) - { - dbset.Remove(entity); - takeover++; - continue; - } - - if (preDate > entity.DateStart) - { - var entityDateEnd = entity.DateStart.AddHours(entity.DurationHours); - entity.DateStart = preDate; - entity.DurationHours = (entityDateEnd - entity.DateStart).TotalHours; - trimmed++; - } - - preEntity = entity; - } - var affected = await db.SaveChangesAsync(token); - return (takeover, trimmed, entities.Count); - } -} + private readonly IMemoryCache memoryCache; + private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; + private readonly IWellService wellService; + + public WellOperationRepository(IAsbCloudDbContext context, + IMemoryCache memoryCache, + IWellOperationCategoryRepository wellOperationCategoryRepository, + IWellService wellService) + : base(context, dbSet => dbSet.Include(e => e.WellSectionType) + .Include(e => e.OperationCategory)) + { + this.memoryCache = memoryCache; + this.wellOperationCategoryRepository = wellOperationCategoryRepository; + this.wellService = wellService; + } + + public IEnumerable GetSectionTypes() => + memoryCache + .GetOrCreateBasic(dbContext.WellSectionTypes) + .OrderBy(s => s.Order) + .Select(s => s.Adapt()); + + public async Task> GetAsync(WellOperationRequest request, CancellationToken token) + { + var entities = await BuildQuery(request) + .AsNoTracking() + .ToArrayAsync(token); + + var dtos = entities.Select(Convert); + + return dtos; + } + + public async Task> GetPageAsync(WellOperationRequest request, CancellationToken token) + { + var skip = request.Skip ?? 0; + var take = request.Take ?? 32; + + var query = BuildQuery(request); + + var entites = await query.Skip(skip) + .Take(take) + .AsNoTracking() + .ToArrayAsync(token); + + var paginationContainer = new PaginationContainer + { + Skip = skip, + Take = take, + Count = await query.CountAsync(token), + Items = entites.Select(Convert) + }; + + return paginationContainer; + } + + public async Task> 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 InsertRangeAsync(IEnumerable dtos, + bool deleteBeforeInsert, + CancellationToken token) + { + EnsureValidWellOperations(dtos); + + if (!deleteBeforeInsert) + return await InsertRangeAsync(dtos, token); + + 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); + + return await InsertRangeAsync(dtos, token); + } + + public override Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token) + { + EnsureValidWellOperations(dtos); + + return base.UpdateRangeAsync(dtos, token); + } + + 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), "Все операции должны принадлежать одной скважине"); + } + + private IQueryable BuildQuery(WellOperationRequest request) + { + var currentWellOperations = GetQuery() + .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.Id)); + + var query = GetQuery() + .Where(e => request.IdsWell != null && request.IdsWell.Contains(e.Id)) + .Select(o => new WellOperation + { + Id = o.Id, + IdPlan = o.IdPlan, + IdType = o.IdType, + IdWell = o.IdWell, + LastUpdateDate = o.LastUpdateDate, + IdWellSectionType = o.IdWellSectionType, + IdCategory = o.IdCategory, + OperationCategory = o.OperationCategory, + WellSectionType = o.WellSectionType, + DateStart = o.DateStart, + DepthStart = o.DepthStart, + DepthEnd = o.DepthEnd, + DurationHours = o.DurationHours, + CategoryInfo = o.CategoryInfo, + Comment = o.Comment, + IdUser = o.IdUser, + + NptHours = currentWellOperations + .Where(e => e.IdType == 1 && e.IdWell == o.IdWell) + .Where(e => WellOperationCategory.NonProductiveTimeSubIds.Contains(e.IdCategory)) + .Select(e => e.DurationHours) + .Sum(), + + Day = (o.DateStart - currentWellOperations + .Where(subOp => subOp.IdType == o.IdType && subOp.IdWell == o.IdWell) + .Where(subOp => subOp.DateStart <= o.DateStart) + .Min(subOp => subOp.DateStart)) + .TotalDays + }); + + if (request.OperationType.HasValue) + query = query.Where(e => e.IdType == request.OperationType.Value); + + if (request.SectionTypeIds?.Any() is true) + query = query.Where(e => request.SectionTypeIds.Contains(e.IdWellSectionType)); + + if (request.OperationCategoryIds?.Any() is true) + query = query.Where(e => request.OperationCategoryIds.Contains(e.IdCategory)); + + if (request.GeDepth.HasValue) + query = query.Where(e => e.DepthEnd >= request.GeDepth.Value); + + if (request.LeDepth.HasValue) + query = query.Where(e => e.DepthEnd <= request.LeDepth.Value); + + if (request.GeDate.HasValue) + { + var geDateUtc = request.GeDate.Value.UtcDateTime; + query = query.Where(e => e.DateStart >= geDateUtc); + } + + if (request.LeDate.HasValue) + { + var leDateUtc = request.LeDate.Value.UtcDateTime; + query = query.Where(e => e.DateStart <= leDateUtc); + } + + if (request.SortFields?.Any() is true) + query = query.SortBy(request.SortFields); + + return query; + } + + 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(), + }); + 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; + }); + + var sections = cache.Where(s => idsWells.Contains(s.IdWell)); + return sections; + } + + 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 minDate = await query.MinAsync(o => o.DateStart, cancellationToken); + var maxDate = await query.MaxAsync(o => o.DateStart, cancellationToken); + + return new DatesRangeDto + { + From = minDate.ToOffset(minDate.Offset), + To = maxDate.ToOffset(minDate.Offset) + }; + } + + protected override WellOperation Convert(WellOperationDto src) + { + var entity = src.Adapt(); + entity.LastUpdateDate = src.LastUpdateDate?.UtcDateTime; + 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(); + dto.DateStart = src.DateStart.ToOffset(timeZoneOffset); + dto.LastUpdateDate = src.LastUpdateDate?.ToOffset(timeZoneOffset); + return dto; + } +} \ No newline at end of file