using AsbCloudApp.Services; using AsbCloudDb; using AsbCloudDb.Model; using AsbCloudInfrastructure.Services.Cache; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.SAUB { public abstract class TelemetryDataBaseService : ITelemetryDataService where TDto : AsbCloudApp.Data.ITelemetryData where TModel : class, AsbCloudDb.Model.ITelemetryData { protected readonly IAsbCloudDbContext db; private readonly ITelemetryService telemetryService; protected readonly CacheTable cacheTelemetry; protected readonly CacheTable cacheTelemetryUsers; protected readonly CacheTable cacheWells; public TelemetryDataBaseService( IAsbCloudDbContext db, ITelemetryService telemetryService, CacheDb cacheDb) { this.db = db; this.telemetryService = telemetryService; cacheTelemetry = cacheDb.GetCachedTable((AsbCloudDbContext)db); cacheTelemetryUsers = cacheDb.GetCachedTable((AsbCloudDbContext)db); cacheWells = cacheDb.GetCachedTable((AsbCloudDbContext)db); } 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 idTelemetry = telemetryService.GetOrCreateTelemetryIdByUid(uid); var timezone = telemetryService.GetTimezone(idTelemetry); var entities = dtosList.Select(dto => { var entity = Convert(dto, timezone.Hours); entity.IdTelemetry = idTelemetry; return entity; }); var entityMaxDate = entities.Max(e => e.DateTime); telemetryService.SaveRequestDate(uid, entityMaxDate); var dbset = db.Set(); var stopwatch = Stopwatch.StartNew(); 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 {idTelemetry}, " + $"count: {entities.Count()}, " + $"dataDate: {entities.FirstOrDefault()?.DateTime}, " + $"dbSaveDurationTime:{stopwatch.ElapsedMilliseconds}ms. " + $"Message: {ex.Message}"); return 0; } } public virtual async Task> GetAsync(int idWell, DateTime dateBegin = default, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) { var well = cacheWells.FirstOrDefault(w => w.Id == idWell); if (well?.IdTelemetry is null) return default; var idTelemetry = well?.IdTelemetry ?? default; var timezone = telemetryService.GetTimezone(idTelemetry); var filterByDateEnd = dateBegin != default; DateTimeOffset dateBeginUtc; if (dateBegin == default) { dateBeginUtc = telemetryService.GetLastTelemetryDate(idTelemetry, true); if (dateBeginUtc != default) dateBeginUtc = dateBeginUtc.AddSeconds(-intervalSec); } else { dateBeginUtc = dateBegin.ToUtcDateTimeOffset(timezone.Hours); } if (dateBeginUtc == default) dateBeginUtc = DateTime.UtcNow.AddSeconds(-intervalSec); var dateEnd = dateBeginUtc.AddSeconds(intervalSec); var dbSet = db.Set(); var query = dbSet .Where(d => d.IdTelemetry == idTelemetry && d.DateTime >= dateBeginUtc); if (filterByDateEnd) query = query.Where(d => d.DateTime <= dateEnd); var fullDataCount = await query.CountAsync(token) .ConfigureAwait(false); if (fullDataCount == 0) return default; 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 .OrderBy(d => d.DateTime) .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); var dtos = entities.Select(e => Convert(e, timezone.Hours)); return dtos; } public abstract TDto Convert(TModel src, double timezoneOffset); public abstract TModel Convert(TDto src, double timezoneOffset); private static double AssumeTimezoneOffset(DateTime nearToCurrentDate) { var offset = 5d; if (nearToCurrentDate.Kind == DateTimeKind.Unspecified) { var now = DateTime.UtcNow; var minutes = 60 * (now.Hour - nearToCurrentDate.Hour) + now.Minute - nearToCurrentDate.Minute; var minutesPositive = (1440_0000 + minutes) % 1440; //60*24 var halfsHours = Math.Round(1d * minutesPositive / 30d); // quarters are ignored var hours = halfsHours / 2; offset = hours < 12 ? hours : 24 - hours; } if (nearToCurrentDate.Kind == DateTimeKind.Local) offset = TimeZoneInfo.Local.BaseUtcOffset.TotalHours; return offset; } } }