using AsbCloudApp.Data; using AsbCloudApp.Data.SAUB; 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.Services.SAUB { #nullable enable public class TelemetryService : ITelemetryService { private readonly IAsbCloudDbContext db; private readonly IMemoryCache memoryCache; private readonly ITelemetryTracker telemetryTracker; private readonly ITimezoneService timezoneService; public ITimezoneService TimeZoneService => timezoneService; public ITelemetryTracker TelemetryTracker => telemetryTracker; public TelemetryService( IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryTracker telemetryTracker, ITimezoneService timezoneService) { this.db = db; this.memoryCache = memoryCache; this.telemetryTracker = telemetryTracker; this.timezoneService = timezoneService; } private IEnumerable GetTelemetryCache() => memoryCache.GetOrCreateBasic( db.Set() .Include(t => t.Well)); private void DropTelemetryCache() { memoryCache.DropBasic(); } public DateTime GetLastTelemetryDate(int idTelemetry) { var telemetry = GetTelemetryCache().FirstOrDefault(t => t.Id == idTelemetry); if (telemetry is null) throw new Exception($"Telemetry id:{idTelemetry} does not exist"); var uid = telemetry.RemoteUid; var timzone = GetTimezone(idTelemetry); var lastTelemetryDate = telemetryTracker.GetLastTelemetryDateByUid(uid); return lastTelemetryDate.ToRemoteDateTime(timzone.Hours); } public DatesRangeDto GetDatesRange(int idTelemetry) { var telemetry = GetTelemetryCache().FirstOrDefault(t => t.Id == idTelemetry); if (telemetry is null) throw new Exception($"Telemetry id:{idTelemetry} does not exist"); var dto = TelemetryTracker.GetTelemetryDateRangeByUid(telemetry.RemoteUid); if (dto is null) throw new Exception($"Telemetry id:{idTelemetry} has no data"); var timezone = GetTimezone(idTelemetry); dto.From = dto.From.ToTimeZoneOffsetHours(timezone.Hours); dto.To = dto.To.ToTimeZoneOffsetHours(timezone.Hours); return dto; } public int GetOrCreateTelemetryIdByUid(string uid) => GetOrCreateTelemetryByUid(uid).Id; public int? GetIdWellByTelemetryUid(string uid) => GetWellByTelemetryUid(uid)?.Id; public async Task UpdateInfoAsync(string uid, TelemetryInfoDto info, CancellationToken token) { var telemetry = GetOrCreateTelemetryByUid(uid); telemetry.Info = info.Adapt(); if (!string.IsNullOrEmpty(info.TimeZoneId) && telemetry.TimeZone?.IsOverride != true) telemetry.TimeZone = new SimpleTimezone() { Hours = info.TimeZoneOffsetTotalHours, TimezoneId = info.TimeZoneId }; db.Telemetries.Upsert(telemetry); await db.SaveChangesAsync(token); DropTelemetryCache(); } public SimpleTimezoneDto GetTimezone(int idTelemetry) { var telemetry = GetTelemetryCache().FirstOrDefault(t => t.Id == idTelemetry); if (telemetry is null) throw new Exception($"Telemetry id: {idTelemetry} does not exist."); if (telemetry.TimeZone is not null) return telemetry.TimeZone.Adapt(); if (telemetry.Info is not null) { telemetry.TimeZone = new SimpleTimezone { Hours = telemetry.Info.TimeZoneOffsetTotalHours, IsOverride = false, TimezoneId = telemetry.Info.TimeZoneId, }; db.Telemetries.Upsert(telemetry); db.SaveChanges(); DropTelemetryCache(); return telemetry.TimeZone.Adapt(); } if (telemetry.Well?.Timezone is not null) { telemetry.TimeZone = telemetry.Well.Timezone; db.Telemetries.Upsert(telemetry); db.SaveChanges(); DropTelemetryCache(); return telemetry.TimeZone.Adapt(); } throw new Exception($"Telemetry id: {idTelemetry} can't find timezone."); } public int? GetOrDefaultIdTelemetryByIdWell(int idWell) { var telemetry = GetTelemetryCache() .FirstOrDefault(t => t.Well?.Id == idWell); return telemetry?.Id; } private Well? GetWellByTelemetryUid(string uid) { var telemetry = GetOrDefaultTelemetryByUid(uid); return telemetry?.Well; } private Telemetry? GetOrDefaultTelemetryByUid(string uid) { var telemetry = GetTelemetryCache().FirstOrDefault(t => t.RemoteUid == uid); return telemetry; } private Telemetry GetOrCreateTelemetryByUid(string uid) { var telemetry = GetOrDefaultTelemetryByUid(uid); if (telemetry is null) { var newTelemetry = new Telemetry { RemoteUid = uid, TimeZone = new SimpleTimezone { Hours = 5, IsOverride = false, TimezoneId = "default", } }; var entry = db.Telemetries.Add(newTelemetry); db.SaveChanges(); DropTelemetryCache(); return entry.Entity; } return telemetry; } public async Task MergeAsync(int from, int to, CancellationToken token) { if (from == to) return -2; var stopwath = new System.Diagnostics.Stopwatch(); stopwath.Start(); var transaction = await db.Database.BeginTransactionAsync(token).ConfigureAwait(false); try { var affected = 0; var wellFrom = await db.Wells.FirstOrDefaultAsync(w => w.IdTelemetry == from, token) .ConfigureAwait(false); var wellTo = await db.Wells.FirstOrDefaultAsync(w => w.IdTelemetry == to, token) .ConfigureAwait(false); if (wellTo is not null && wellFrom is not null) return -2; if (wellTo is null && wellFrom is not null) { wellFrom.IdTelemetry = to; affected += await db.SaveChangesAsync(token); } affected += await MergeEventsAndMessagesAndUsersAsync(from, to, token); affected += await MergeDataAsync(from, to, token); affected += await MergeDataAsync(from, to, token); affected += await db.Database.ExecuteSqlRawAsync($"DELETE FROM t_telemetry_analysis WHERE id_telemetry = {from} OR id_telemetry = {to};", token) .ConfigureAwait(false); affected += await db.Database.ExecuteSqlRawAsync($"DELETE FROM t_telemetry WHERE id = {from};", token) .ConfigureAwait(false); await transaction.CommitAsync(token).ConfigureAwait(false); stopwath.Stop(); Console.WriteLine($"Successfully committed in {1d * stopwath.ElapsedMilliseconds / 1000d: #0.00} sec. Affected {affected} rows."); DropTelemetryCache(); return affected; } catch (Exception ex) { System.Diagnostics.Trace.WriteLine($"Merge() Fail. Rollback. Reason is:{ex.Message}"); await transaction.RollbackAsync(CancellationToken.None); return -1; } } private async Task MergeEventsAndMessagesAndUsersAsync(int from, int to, CancellationToken token) { var messagesFrom = await db.TelemetryMessages .Where(d => d.IdTelemetry == from) .ToListAsync(token) .ConfigureAwait(false); var usersFromQuery = db.TelemetryUsers .Where(d => d.IdTelemetry == from); var usersFrom = await usersFromQuery .ToListAsync(token) .ConfigureAwait(false); var usersTo = await db.TelemetryUsers .Where(d => d.IdTelemetry == to) .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); var usersToNextId = usersTo.Max(u => u.IdUser) + 100; messagesFrom.ForEach(m => m.IdTelemetry = to); foreach (var userFrom in usersFrom) { var userTo = usersTo .FirstOrDefault(u => u.IdUser == userFrom.IdUser); if (userTo is null || userTo.Name != userFrom.Name || userTo.Surname != userFrom.Surname || userTo.Patronymic != userFrom.Patronymic) { messagesFrom .Where(m => m.IdTelemetryUser == userFrom.IdUser) .ToList() .ForEach(m => m.IdTelemetryUser = usersToNextId); userFrom.IdUser = usersToNextId; userFrom.IdTelemetry = to; usersToNextId++; } } var eventsFromQuery = db.TelemetryEvents .Where(d => d.IdTelemetry == from); var eventsFrom = await eventsFromQuery .ToListAsync(token) .ConfigureAwait(false); var eventsTo = await db.TelemetryEvents .Where(d => d.IdTelemetry == to) .AsNoTracking() .ToListAsync(token) .ConfigureAwait(false); var eventsToNextId = eventsTo.Max(e => e.IdEvent) + 1; foreach (var eventFrom in eventsFrom) { var eventTo = eventsTo .FirstOrDefault(e => e.IdEvent == eventFrom.IdEvent); if (eventTo is null || eventTo.IdCategory != eventFrom.IdCategory || eventTo.MessageTemplate != eventFrom.MessageTemplate) { messagesFrom .Where(m => m.IdEvent == eventFrom.IdEvent) .ToList() .ForEach(m => m.IdEvent = eventsToNextId); eventFrom.IdEvent = eventsToNextId; eventFrom.IdTelemetry = to; eventsToNextId++; } } await db.Database.ExecuteSqlRawAsync($"ALTER TABLE t_telemetry_user DISABLE TRIGGER ALL;", token).ConfigureAwait(false); await db.Database.ExecuteSqlRawAsync($"ALTER TABLE t_telemetry_event DISABLE TRIGGER ALL;", token).ConfigureAwait(false); await db.Database.ExecuteSqlRawAsync($"ALTER TABLE t_telemetry_message DISABLE TRIGGER ALL;", token).ConfigureAwait(false); var affected = await db.SaveChangesAsync(token).ConfigureAwait(false); await db.Database.ExecuteSqlRawAsync($"ALTER TABLE t_telemetry_user ENABLE TRIGGER ALL;", token).ConfigureAwait(false); await db.Database.ExecuteSqlRawAsync($"ALTER TABLE t_telemetry_event ENABLE TRIGGER ALL;", token).ConfigureAwait(false); await db.Database.ExecuteSqlRawAsync($"ALTER TABLE t_telemetry_message ENABLE TRIGGER ALL;", token).ConfigureAwait(false); db.TelemetryUsers.RemoveRange(usersFromQuery); db.TelemetryEvents.RemoveRange(eventsFromQuery); affected += await db.SaveChangesAsync(token).ConfigureAwait(false); return affected; } private async Task MergeDataAsync(int from, int to, CancellationToken token) where TEntity : class, AsbCloudDb.Model.ITelemetryData { const string IdTelemetryColumnName = "\"id_telemetry\""; var dbSet = db.Set(); var tableName = dbSet.GetTableName(); var columns = dbSet.GetColumnsNames().ToList(); var index = columns.FindIndex(c => c.ToLower() == IdTelemetryColumnName.ToLower()); if (index < 0) return -5; columns[index] = $"{to} as {IdTelemetryColumnName}"; var columnsString = string.Join(',', columns); var sql = $"INSERT INTO {tableName} " + $"select {columnsString} " + $"from {tableName} " + $"where {IdTelemetryColumnName} = {from};" + $"delete from {tableName} where {IdTelemetryColumnName} = {from};"; var affected = await db.Database.ExecuteSqlRawAsync(sql, token) .ConfigureAwait(false); return affected; } } #nullable disable }