using AsbCloudApp.Data; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudDb; using AsbCloudDb.Model; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Requests; namespace AsbCloudInfrastructure.Services.SAUB { public abstract class TelemetryDataBaseService : ITelemetryDataService where TDto : AsbCloudApp.Data.ITelemetryData where TEntity : class, AsbCloudDb.Model.ITelemetryData { protected readonly IAsbCloudDbContext db; protected readonly ITelemetryService telemetryService; protected readonly ITelemetryDataCache telemetryDataCache; protected TelemetryDataBaseService( IAsbCloudDbContext db, ITelemetryService telemetryService, ITelemetryDataCache telemetryDataCache) { this.db = db; this.telemetryService = telemetryService; this.telemetryDataCache = telemetryDataCache; } /// public virtual async Task UpdateDataAsync(string uid, IEnumerable dtos, CancellationToken token = default) { if (dtos == default || !dtos.Any()) return 0; var dtosList = dtos.OrderBy(d => d.DateTime).ToList(); var dtoMinDate = dtosList.First().DateTime; var dtoMaxDate = dtosList.Last().DateTime; if (dtosList.Count > 1) { var duplicates = new List(8); for (int i = 1; i < dtosList.Count; i++) if (dtosList[i].DateTime - dtosList[i - 1].DateTime < TimeSpan.FromMilliseconds(100)) duplicates.Add(dtosList[i - 1]); foreach (var duplicate in duplicates) dtosList.Remove(duplicate); } var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid); var timezone = telemetryService.GetTimezone(telemetry.Id); telemetryDataCache.AddRange(telemetry.Id, dtos); var entities = dtosList.Select(dto => { var entity = Convert(dto, timezone.Hours); entity.IdTelemetry = telemetry.Id; return entity; }); var stopwatch = Stopwatch.StartNew(); var dbset = db.Set(); try { return await db.Database.ExecInsertOrUpdateAsync(dbset, entities, token).ConfigureAwait(false); } catch (Exception ex) { stopwatch.Stop(); Trace.WriteLine($"Fail to save data telemetry " + $"uid: {uid}, " + $"idTelemetry {telemetry.Id}, " + $"count: {entities.Count()}, " + $"dataDate: {entities.FirstOrDefault()?.DateTime}, " + $"dbSaveDurationTime:{stopwatch.ElapsedMilliseconds}ms. " + $"Message: {ex.Message}"); return 0; } } /// public virtual async Task> GetByWellAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) { var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); if (telemetry is null) return Enumerable.Empty(); var timezone = telemetryService.GetTimezone(telemetry.Id); var filterByDateEnd = dateBegin != default; DateTimeOffset dateBeginUtc; if (dateBegin == default) { var dateRange = telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id); dateBeginUtc = (dateRange?.To ?? DateTimeOffset.UtcNow) .AddSeconds(-intervalSec); } else { dateBeginUtc = dateBegin.ToUtcDateTimeOffset(timezone.Hours); } var cacheData = telemetryDataCache.GetOrDefault(telemetry.Id, dateBeginUtc.ToRemoteDateTime(timezone.Hours), intervalSec, approxPointsCount); if (cacheData is not null) return cacheData; var dateEnd = dateBeginUtc.AddSeconds(intervalSec); var dbSet = db.Set(); var query = dbSet .Where(d => d.IdTelemetry == telemetry.Id && d.DateTime >= dateBeginUtc); if (filterByDateEnd) query = query.Where(d => d.DateTime <= dateEnd); var fullDataCount = await query.CountAsync(token) .ConfigureAwait(false); if (fullDataCount == 0) return Enumerable.Empty(); if (fullDataCount > 1.75 * approxPointsCount) { var m = (int)Math.Round(1d * fullDataCount / approxPointsCount); if (m > 1) query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % m == 0); } var entities = await query .AsNoTracking() .ToArrayAsync(token); var dtos = entities.Select(e => Convert(e, timezone.Hours)); return dtos; } /// public virtual async Task> GetByWellAsync(int idWell, TelemetryDataRequest request, CancellationToken token) { var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); if (telemetry is null) return Enumerable.Empty(); return await GetByTelemetryAsync(telemetry.Id, request, token); } public async Task> GetByTelemetryAsync(int idTelemetry, TelemetryDataRequest request, CancellationToken token) { var timezone = telemetryService.GetTimezone(idTelemetry); var cache = telemetryDataCache.GetOrDefault(idTelemetry, request); if(cache is not null) return cache; var query = BuildQuery(idTelemetry, request); var entities = await query .AsNoTracking() .ToArrayAsync(token); var dtos = entities.Select(e => Convert(e, timezone.Hours)); return dtos; } private IQueryable BuildQuery(int idTelemetry, TelemetryDataRequest request) { var dbSet = db.Set(); var query = dbSet .Where(d => d.IdTelemetry == idTelemetry); if (request.GeDate.HasValue) { var geDate = request.GeDate.Value.UtcDateTime; query = query.Where(d => d.DateTime >= geDate); } if (request.LeDate.HasValue) { var leDate = request.LeDate.Value.UtcDateTime; query = query.Where(d => d.DateTime <= leDate); } if (request.Divider > 1) query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % request.Divider == 0); switch (request.Order) { case 1:// Поздние вперед query = query .OrderByDescending(d => d.DateTime) .Skip(request.Skip) .Take(request.Take) .OrderBy(d => d.DateTime); break; default:// Ранние вперед query = query .OrderBy(d => d.DateTime) .Skip(request.Skip) .Take(request.Take); break; } return query; } /// public async Task GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token) { var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell) ?? throw new ArgumentInvalidException(nameof(idWell), $"По скважине id:{idWell} нет телеметрии"); if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12)) { // пробуем обойтись кешем var cechedRange = telemetryDataCache.GetOrDefaultCachedDateRange(telemetry.Id); if (cechedRange?.From <= geDate) { var datesRange = new DatesRangeDto { From = geDate.DateTime, To = cechedRange.To }; if (leDate.HasValue && leDate > geDate) datesRange.To = leDate.Value.Date; return datesRange; } } var query = db.Set() .Where(entity => entity.IdTelemetry == telemetry.Id) .Where(entity => entity.DateTime >= geDate.ToUniversalTime()); if(leDate.HasValue) query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime()); var gquery = query .GroupBy(entity => entity.IdTelemetry) .Select(group => new { MinDate = group.Min(entity => entity.DateTime), MaxDate = group.Max(entity => entity.DateTime), }); var result = await gquery.FirstOrDefaultAsync(token); if (result is null) return null; var range = new DatesRangeDto { From = result.MinDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime, To = result.MaxDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime, }; return range; } public DatesRangeDto? GetRange(int idWell) { var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell); if (telemetry is null) return default; return telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id); } protected abstract TDto Convert(TEntity src, double timezoneOffset); protected abstract TEntity Convert(TDto src, double timezoneOffset); } }