diff --git a/AsbCloudApp/Repositories/IGtrRepository.cs b/AsbCloudApp/Repositories/IGtrRepository.cs index 7aad6629..0d597dde 100644 --- a/AsbCloudApp/Repositories/IGtrRepository.cs +++ b/AsbCloudApp/Repositories/IGtrRepository.cs @@ -38,8 +38,14 @@ namespace AsbCloudApp.Repositories /// /// /// - /// /// - Task> GetLastDataByRecordIdAsync(int idWell, int idRecord, CancellationToken token = default); + IEnumerable GetLastDataByRecordId(int idWell, int idRecord); + + /// + /// Последние полученные параметры + /// + /// + /// + IEnumerable GetLastData(int idWell); } } diff --git a/AsbCloudInfrastructure/LinqExtensions.cs b/AsbCloudInfrastructure/LinqExtensions.cs new file mode 100644 index 00000000..cf182051 --- /dev/null +++ b/AsbCloudInfrastructure/LinqExtensions.cs @@ -0,0 +1,39 @@ +// Ignore Spelling: Linq + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudInfrastructure +{ + public static class LinqExtensions + { + public static TProp? MaxOrDefault(this IEnumerable enumerable, Func getter) + where TProp : struct + { + var value = MaxByOrDefault(enumerable, getter); + if (value is null) + return null; + return getter(value); + } + + public static TObj? MaxByOrDefault(this IEnumerable enumerable, Func getter) + { + return enumerable.OrderByDescending(getter).FirstOrDefault(); + } + + public static TProp? MinOrDefault(this IEnumerable enumerable, Func getter) + where TProp : struct + { + var value = MinByOrDefault(enumerable, getter); + if (value is null) + return null; + return getter(value); + } + + public static TObj? MinByOrDefault(this IEnumerable enumerable, Func getter) + { + return enumerable.OrderBy(getter).FirstOrDefault(); + } + } +} diff --git a/AsbCloudInfrastructure/Repository/GtrWitsRepository.cs b/AsbCloudInfrastructure/Repository/GtrWitsRepository.cs index a0af9bf7..12b97ea7 100644 --- a/AsbCloudInfrastructure/Repository/GtrWitsRepository.cs +++ b/AsbCloudInfrastructure/Repository/GtrWitsRepository.cs @@ -5,6 +5,7 @@ using AsbCloudDb.Model; using AsbCloudDb.Model.GTR; using Microsoft.EntityFrameworkCore; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -16,12 +17,12 @@ namespace AsbCloudInfrastructure.Repository { private readonly IAsbCloudDbContext db; private readonly ITelemetryService telemetryService; + private static ConcurrentDictionary> cache = new(); public GtrWitsRepository( IAsbCloudDbContext db, ITelemetryService telemetryService) { - this.db = db; this.telemetryService = telemetryService; } @@ -69,62 +70,21 @@ namespace AsbCloudInfrastructure.Repository return dtos; } - public async Task> GetLastDataByRecordIdAsync(int idWell, int idRecord, CancellationToken token = default) + public IEnumerable GetLastDataByRecordId(int idWell, int idRecord) + { + var result = GetLastData(idWell) + .Where(item => item.IdRecord == idRecord); + return result; + } + + public IEnumerable GetLastData(int idWell) { var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); if (telemetry is null) return Enumerable.Empty(); - var timezone = telemetryService.GetTimezone(telemetry.Id); - - var witsRequest = new WitsRequest() - { - IdTelemetry = telemetry.Id, - TimezoneHours = timezone.Hours, - IdRecord = idRecord, - }; - - var recordAllInt = await GetGroupedItemsOrDefaultAsync(witsRequest, token); - var recordAllFloat = await GetGroupedItemsOrDefaultAsync(witsRequest, token); - var recordAllString = await GetGroupedItemsOrDefaultAsync(witsRequest, token); - - var dtos = recordAllFloat.Union(recordAllInt).Union(recordAllString); - return dtos; - } - - private async Task> GetGroupedItemsOrDefaultAsync(WitsRequest request, CancellationToken token) - where TEntity : WitsItemBase - where TValue : notnull - { - var query = BuildQuery(request); - var groupedQuery = query.GroupBy(g => new - { - g.IdRecord, - g.IdTelemetry, - g.IdItem - }) - .Select(g => new - { - g.Key.IdRecord, - g.Key.IdItem, - Data = g.OrderByDescending(i => i.DateTime) - .FirstOrDefault() - }); - - var groupedEntities = await groupedQuery - .ToArrayAsync(token) - .ConfigureAwait(false); - - var dtos = groupedEntities - .Select(e => new WitsItemRecordDto() - { - IdRecord = e.IdRecord, - IdItem = e.IdItem, - Date = e.Data!.DateTime.ToRemoteDateTime(request.TimezoneHours), - Value = new JsonValue(e.Data!.Value) - }); - - return dtos; + var lastData = cache.GetValueOrDefault(telemetry.Id); + return lastData?.Values ?? Enumerable.Empty(); } private async Task> GetItemsOrDefaultAsync( @@ -187,29 +147,92 @@ namespace AsbCloudInfrastructure.Repository { var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours; - foreach (var dto in dtos) + var cacheTelemetryItems = cache.GetValueOrDefault(idTelemetry); + + foreach (var record in dtos) { - foreach (var item in dto.Items) + var dateTime = record.Date.ToUtcDateTimeOffset(timezoneHours); + foreach (var item in record.Items) { - var dateTime = dto.Date.ToUtcDateTimeOffset(timezoneHours); + if (cacheTelemetryItems?.TryGetValue((record.Id, item.Key), out var cacheItem) == true) + if (Math.Abs((dateTime - cacheItem.Date).TotalSeconds) < 1) + continue; + if (item.Value.Value is string valueString) { - var entity = MakeEntity(dto.Id, item.Key, idTelemetry, dateTime, valueString); + var entity = MakeEntity(record.Id, item.Key, idTelemetry, dateTime, valueString); db.WitsItemString.Add(entity); } if (item.Value.Value is float valueFloat) { - var entity = MakeEntity(dto.Id, item.Key, idTelemetry, dateTime, valueFloat); + var entity = MakeEntity(record.Id, item.Key, idTelemetry, dateTime, valueFloat); db.WitsItemFloat.Add(entity); } if (item.Value.Value is int valueInt) { - var entity = MakeEntity(dto.Id, item.Key, idTelemetry, dateTime, valueInt); + var entity = MakeEntity(record.Id, item.Key, idTelemetry, dateTime, valueInt); db.WitsItemInt.Add(entity); } } } - await db.SaveChangesAsync(token); + + try + { + await db.SaveChangesAsync(token); + } + catch(DbUpdateException ex) + { + var isRelational = ex.Source == "Microsoft.EntityFrameworkCore.Relational" && ex.Entries.Any(); + if (!isRelational) + throw; + } + + cache.AddOrUpdate(idTelemetry, + (_) => MakeNewCache(dtos), + (_, oldItemsDictionary) => { + foreach (var record in dtos) + foreach (var item in record.Items) + { + oldItemsDictionary.AddOrUpdate( + (record.Id, item.Key), + (_) => new WitsItemRecordDto + { + IdRecord = record.Id, + IdItem = item.Key, + Date = record.Date, + Value = item.Value + }, + (_, _) => new WitsItemRecordDto + { + IdRecord = record.Id, + IdItem = item.Key, + Date = record.Date, + Value = item.Value + }); + } + return oldItemsDictionary; + }); + } + + private static ConcurrentDictionary<(int, int), WitsItemRecordDto> MakeNewCache(IEnumerable dtos) + { + var items = dtos.SelectMany(record => + record.Items.Select( + item => new WitsItemRecordDto { + IdItem = item.Key, + IdRecord = record.Id, + Date = record.Date, + Value = item.Value, + })); + + var groups = items + .GroupBy(item => (item.IdRecord, item.IdItem)); + + var pairs = groups.Select(group => new KeyValuePair<(int, int), WitsItemRecordDto>( + group.Key, + group.OrderByDescending(item => item.Date).First())); + + return new ConcurrentDictionary<(int, int), WitsItemRecordDto>(pairs); } private static TEntity MakeEntity(int idRecord, int idItem, int idTelemetry, DateTimeOffset dateTime, TValue value) @@ -224,6 +247,18 @@ namespace AsbCloudInfrastructure.Repository Value = value, }; + private static TEntity MakeEntity(WitsItemRecordDto dto, int idTelemetry, DateTimeOffset dateTime) + where TEntity : WitsItemBase, new() + where TValue : notnull + => new TEntity() + { + IdRecord = dto.IdRecord, + IdItem = dto.IdItem, + IdTelemetry = idTelemetry, + DateTime = dateTime, + Value = (TValue)dto.Value.Value, + }; + private class WitsRequest { public int IdTelemetry { get; set; } diff --git a/AsbCloudWebApi/Controllers/SAUB/GtrWitsController.cs b/AsbCloudWebApi/Controllers/SAUB/GtrWitsController.cs index 1209ea65..a6cf23a2 100644 --- a/AsbCloudWebApi/Controllers/SAUB/GtrWitsController.cs +++ b/AsbCloudWebApi/Controllers/SAUB/GtrWitsController.cs @@ -77,7 +77,7 @@ namespace AsbCloudWebApi.Controllers.SAUB /// [HttpGet("{idWell}/{idRecord}")] [Permission] - public async Task>> GetLastDataByRecordIdAsync(int idWell, int idRecord, CancellationToken token = default) + public async Task>> GetLastDataByRecordIdAsync(int idWell, int idRecord, CancellationToken token) { int? idCompany = User.GetCompanyId(); @@ -90,7 +90,7 @@ namespace AsbCloudWebApi.Controllers.SAUB if (!isCompanyOwnsWell) return Forbid(); - var content = await gtrRepository.GetLastDataByRecordIdAsync(idWell, idRecord, token).ConfigureAwait(false); + var content = gtrRepository.GetLastDataByRecordId(idWell, idRecord); return Ok(content); }