forked from ddrilling/AsbCloudServer
Fix telemetryDataDateRange timezone offset
This commit is contained in:
parent
07be69a368
commit
a213c27fbf
@ -49,7 +49,7 @@ namespace AsbCloudApp.Repositories
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="idTelemetry"></param>
|
/// <param name="idTelemetry"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
DatesRangeDto? GetOrDefaultCachedDateRange(int idTelemetry);
|
DatesRangeDto? GetOrDefaultCachedDataDateRange(int idTelemetry);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить диапазон дат телеметрии.
|
/// Получить диапазон дат телеметрии.
|
||||||
@ -57,7 +57,7 @@ namespace AsbCloudApp.Repositories
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="idTelemetry"></param>
|
/// <param name="idTelemetry"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
DatesRangeDto? GetOrDefaultDataDateRange(int idTelemetry);
|
DatesRangeDto? GetOrDefaultWellDataDateRange(int idTelemetry);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получение первой и последней записи телеметрии.
|
/// Получение первой и последней записи телеметрии.
|
||||||
|
@ -12,6 +12,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AsbCloudApp.Requests;
|
using AsbCloudApp.Requests;
|
||||||
|
using Mapster;
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services.SAUB
|
namespace AsbCloudInfrastructure.Services.SAUB
|
||||||
{
|
{
|
||||||
@ -67,10 +68,10 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
});
|
});
|
||||||
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
var dbset = db.Set<TEntity>();
|
var dbSet = db.Set<TEntity>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await db.Database.ExecInsertOrUpdateAsync(dbset, entities, token).ConfigureAwait(false);
|
return await db.Database.ExecInsertOrUpdateAsync(dbSet, entities, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -101,7 +102,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
DateTimeOffset dateBeginUtc;
|
DateTimeOffset dateBeginUtc;
|
||||||
if (dateBegin == default)
|
if (dateBegin == default)
|
||||||
{
|
{
|
||||||
var dateRange = telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id);
|
var dateRange = telemetryDataCache.GetOrDefaultWellDataDateRange(telemetry.Id);
|
||||||
dateBeginUtc = (dateRange?.To.ToUniversalTime() ?? DateTimeOffset.UtcNow)
|
dateBeginUtc = (dateRange?.To.ToUniversalTime() ?? DateTimeOffset.UtcNow)
|
||||||
.AddSeconds(-intervalSec);
|
.AddSeconds(-intervalSec);
|
||||||
}
|
}
|
||||||
@ -226,17 +227,17 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
|
|
||||||
if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12))
|
if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12))
|
||||||
{
|
{
|
||||||
// пробуем обойтись кешем
|
// пробуем обойтись кэшем
|
||||||
var cechedRange = telemetryDataCache.GetOrDefaultCachedDateRange(telemetry.Id);
|
var cachedRange = telemetryDataCache.GetOrDefaultCachedDataDateRange(telemetry.Id);
|
||||||
if (cechedRange?.From <= geDate)
|
if (cachedRange is not null)
|
||||||
{
|
{
|
||||||
var datesRange = new DatesRangeDto
|
var datesRange = new DatesRangeDto {From = cachedRange.From, To = cachedRange.To };
|
||||||
{
|
if (geDate >= cachedRange.From)
|
||||||
From = geDate.DateTime,
|
datesRange.From = geDate.ToOffset(cachedRange.From.Offset);
|
||||||
To = cechedRange.To
|
|
||||||
};
|
if (leDate.HasValue && leDate <= cachedRange.To)
|
||||||
if (leDate.HasValue && leDate > geDate)
|
datesRange.To = leDate.Value.ToOffset(cachedRange.To.Offset);
|
||||||
datesRange.To = leDate.Value.Date;
|
|
||||||
return datesRange;
|
return datesRange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,7 +249,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
if(leDate.HasValue)
|
if(leDate.HasValue)
|
||||||
query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime());
|
query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime());
|
||||||
|
|
||||||
var gquery = query
|
var groupQuery = query
|
||||||
.GroupBy(entity => entity.IdTelemetry)
|
.GroupBy(entity => entity.IdTelemetry)
|
||||||
.Select(group => new
|
.Select(group => new
|
||||||
{
|
{
|
||||||
@ -256,14 +257,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
MaxDate = group.Max(entity => entity.DateTime),
|
MaxDate = group.Max(entity => entity.DateTime),
|
||||||
});
|
});
|
||||||
|
|
||||||
var result = await gquery.FirstOrDefaultAsync(token);
|
var result = await groupQuery.FirstOrDefaultAsync(token);
|
||||||
if (result is null)
|
if (result is null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var range = new DatesRangeDto
|
var range = new DatesRangeDto
|
||||||
{
|
{
|
||||||
From = result.MinDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime,
|
From = result.MinDate.ToOffset(telemetry.TimeZone!.Offset),
|
||||||
To = result.MaxDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime,
|
To = result.MaxDate.ToOffset(telemetry.TimeZone!.Offset),
|
||||||
};
|
};
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
@ -274,7 +275,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
if (telemetry is null)
|
if (telemetry is null)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
return telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id);
|
return telemetryDataCache.GetOrDefaultWellDataDateRange(telemetry.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract TDto Convert(TEntity src, double timezoneOffset);
|
protected abstract TDto Convert(TEntity src, double timezoneOffset);
|
||||||
|
@ -13,347 +13,350 @@ using AsbCloudApp.Data;
|
|||||||
using AsbCloudApp.Requests;
|
using AsbCloudApp.Requests;
|
||||||
using AsbCloudApp.Repositories;
|
using AsbCloudApp.Repositories;
|
||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services.SAUB
|
namespace AsbCloudInfrastructure.Services.SAUB;
|
||||||
|
|
||||||
|
public class TelemetryDataCache<TDto> : ITelemetryDataCache<TDto> where TDto : AsbCloudApp.Data.ITelemetryData
|
||||||
{
|
{
|
||||||
public class TelemetryDataCache<TDto> : ITelemetryDataCache<TDto> where TDto : AsbCloudApp.Data.ITelemetryData
|
class TelemetryDataCacheItem
|
||||||
{
|
{
|
||||||
class TelemetryDataCacheItem
|
public TDto FirstByDate { get; init; } = default!;
|
||||||
|
public CyclicArray<TDto> LastData { get; init; } = null!;
|
||||||
|
public double TimezoneHours { get; init; } = 5;
|
||||||
|
public TimeSpan TimezoneOffset => TimeSpan.FromHours(TimezoneHours);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int activeWellCapacity = 12 * 60 * 60;
|
||||||
|
private const int doneWellCapacity = 65 * 60;
|
||||||
|
|
||||||
|
// key == idTelemetry
|
||||||
|
private readonly ConcurrentDictionary<int, TelemetryDataCacheItem> caches;
|
||||||
|
private bool isLoading = false;
|
||||||
|
|
||||||
|
private TelemetryDataCache()
|
||||||
|
{
|
||||||
|
caches = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TelemetryDataCache<TDto>? instance;
|
||||||
|
|
||||||
|
public static TelemetryDataCache<TDto> GetInstance<TEntity>(IServiceProvider provider)
|
||||||
|
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
||||||
|
{
|
||||||
|
if (instance is null)
|
||||||
{
|
{
|
||||||
public TDto FirstByDate { get; init; } = default!;
|
instance = new TelemetryDataCache<TDto>();
|
||||||
public CyclicArray<TDto> LastData { get; init; } = null!;
|
var worker = provider.GetRequiredService<BackgroundWorker>();
|
||||||
public double TimezoneHours { get; init; } = 5;
|
var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}";
|
||||||
|
var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) =>
|
||||||
|
{
|
||||||
|
var db = provider.GetRequiredService<IAsbCloudDbContext>();
|
||||||
|
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
|
||||||
|
});
|
||||||
|
work.Timeout = TimeSpan.FromMinutes(15);
|
||||||
|
worker.Enqueue(work);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавить новые элементы в кеш
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idTelemetry"></param>
|
||||||
|
/// <param name="range"></param>
|
||||||
|
public void AddRange(int idTelemetry, IEnumerable<TDto> range)
|
||||||
|
{
|
||||||
|
if (!range.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
range = range.OrderBy(x => x.DateTime);
|
||||||
|
|
||||||
|
foreach (var item in range)
|
||||||
|
item.IdTelemetry = idTelemetry;
|
||||||
|
|
||||||
|
TelemetryDataCacheItem cacheItem;
|
||||||
|
if (isLoading)
|
||||||
|
{
|
||||||
|
if (caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? localCacheItem))
|
||||||
|
cacheItem = localCacheItem;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cacheItem = caches.GetOrAdd(idTelemetry, _ => new TelemetryDataCacheItem()
|
||||||
|
{
|
||||||
|
FirstByDate = range.ElementAt(0),
|
||||||
|
LastData = new CyclicArray<TDto>(activeWellCapacity)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private const int activeWellCapacity = 12 * 60 * 60;
|
cacheItem.LastData.AddRange(range);
|
||||||
private const int doneWellCapacity = 65 * 60;
|
}
|
||||||
|
|
||||||
// key == idTelemetry
|
/// <summary>
|
||||||
private readonly ConcurrentDictionary<int, TelemetryDataCacheItem> caches;
|
/// Получить данные из кеша. <br/>
|
||||||
private bool isLoading = false;
|
/// Если dateBegin меньше минимального элемента в кеше, то вернется null.
|
||||||
|
/// Даже если intervalSec частично перекрыт данными из кеша.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idTelemetry"></param>
|
||||||
|
/// <param name="dateBegin"></param>
|
||||||
|
/// <param name="intervalSec"></param>
|
||||||
|
/// <param name="approxPointsCount">кол-во элементов до которых эти данные прореживаются</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600d, int approxPointsCount = 1024)
|
||||||
|
{
|
||||||
|
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||||
|
return null;
|
||||||
|
|
||||||
private TelemetryDataCache()
|
var cacheLastData = cacheItem.LastData;
|
||||||
|
|
||||||
|
if (!cacheLastData.Any() || cacheLastData[0].DateTime > dateBegin)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var dateEnd = dateBegin.AddSeconds(intervalSec);
|
||||||
|
var items = cacheLastData
|
||||||
|
.Where(i => i.DateTime >= dateBegin && i.DateTime <= dateEnd);
|
||||||
|
|
||||||
|
var ratio = items.Count() / approxPointsCount;
|
||||||
|
if (ratio > 1)
|
||||||
|
items = items
|
||||||
|
.Where((_, index) => index % ratio == 0);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<TelemetryDataStatDto> GetStat()
|
||||||
|
{
|
||||||
|
var result = caches.Select(cacheItem => new TelemetryDataStatDto
|
||||||
{
|
{
|
||||||
caches = new();
|
IdTelemetry = cacheItem.Key,
|
||||||
}
|
DateFirst = cacheItem.Value.FirstByDate.DateTime,
|
||||||
|
DateLast = cacheItem.Value.LastData[^1].DateTime,
|
||||||
|
TimezoneOffsetHours = cacheItem.Value.TimezoneHours,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static TelemetryDataCache<TDto>? instance;
|
public virtual TDto? GetLastOrDefault(int idTelemetry)
|
||||||
|
{
|
||||||
|
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||||
|
return default;
|
||||||
|
|
||||||
public static TelemetryDataCache<TDto> GetInstance<TEntity>(IServiceProvider provider)
|
return cacheItem.LastData.LastOrDefault();
|
||||||
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
}
|
||||||
{
|
|
||||||
if (instance is null)
|
|
||||||
{
|
|
||||||
instance = new TelemetryDataCache<TDto>();
|
|
||||||
var worker = provider.GetRequiredService<BackgroundWorker>();
|
|
||||||
var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}";
|
|
||||||
var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) =>
|
|
||||||
{
|
|
||||||
var db = provider.GetRequiredService<IAsbCloudDbContext>();
|
|
||||||
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
|
|
||||||
});
|
|
||||||
work.Timeout = TimeSpan.FromMinutes(15);
|
|
||||||
worker.Enqueue(work);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public DatesRangeDto? GetOrDefaultWellDataDateRange(int idTelemetry)
|
||||||
/// Добавить новые элементы в кеш
|
{
|
||||||
/// </summary>
|
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||||
/// <param name="idTelemetry"></param>
|
return null;
|
||||||
/// <param name="range"></param>
|
|
||||||
public void AddRange(int idTelemetry, IEnumerable<TDto> range)
|
if (!cacheItem.LastData.Any())
|
||||||
{
|
return null;
|
||||||
if (!range.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
range = range.OrderBy(x => x.DateTime);
|
var to = FromDate(cacheItem.FirstByDate.DateTime, cacheItem.TimezoneOffset);
|
||||||
|
var from = FromDate(cacheItem.LastData[^1].DateTime, cacheItem.TimezoneOffset);
|
||||||
|
|
||||||
foreach (var item in range)
|
return new DatesRangeDto { From = from, To = to };
|
||||||
item.IdTelemetry = idTelemetry;
|
}
|
||||||
|
|
||||||
TelemetryDataCacheItem cacheItem;
|
public DatesRangeDto? GetOrDefaultCachedDataDateRange(int idTelemetry)
|
||||||
if (isLoading)
|
{
|
||||||
{
|
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||||
if (caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? localCacheItem))
|
return null;
|
||||||
cacheItem = localCacheItem;
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cacheItem = caches.GetOrAdd(idTelemetry, _ => new TelemetryDataCacheItem()
|
|
||||||
{
|
|
||||||
FirstByDate = range.ElementAt(0),
|
|
||||||
LastData = new CyclicArray<TDto>(activeWellCapacity)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheItem.LastData.AddRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
if (cacheItem.LastData.Count < 2)
|
||||||
/// Получить данные из кеша. <br/>
|
return null;
|
||||||
/// Если dateBegin меньше минимального элемента в кеше, то вернется null.
|
|
||||||
/// Даже если intervalSec частично перекрыт данными из кеша.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="idTelemetry"></param>
|
|
||||||
/// <param name="dateBegin"></param>
|
|
||||||
/// <param name="intervalSec"></param>
|
|
||||||
/// <param name="approxPointsCount">кол-во элементов до которых эти данные прореживаются</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600d, int approxPointsCount = 1024)
|
|
||||||
{
|
|
||||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var cacheLastData = cacheItem.LastData;
|
var to = FromDate(cacheItem.LastData[^1].DateTime, cacheItem.TimezoneOffset);
|
||||||
|
var from = FromDate(cacheItem.LastData[0].DateTime, cacheItem.TimezoneOffset);
|
||||||
|
|
||||||
if (!cacheLastData.Any() || cacheLastData[0].DateTime > dateBegin)
|
return new DatesRangeDto { From = from, To = to };
|
||||||
return null;
|
}
|
||||||
|
|
||||||
var dateEnd = dateBegin.AddSeconds(intervalSec);
|
public (TDto First, TDto Last)? GetOrDefaultFirstLast(int idTelemetry)
|
||||||
var items = cacheLastData
|
{
|
||||||
.Where(i => i.DateTime >= dateBegin && i.DateTime <= dateEnd);
|
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||||
|
return null;
|
||||||
|
|
||||||
var ratio = items.Count() / approxPointsCount;
|
if (!cacheItem.LastData.Any())
|
||||||
if (ratio > 1)
|
return null;
|
||||||
items = items
|
|
||||||
.Where((_, index) => index % ratio == 0);
|
|
||||||
|
|
||||||
return items;
|
var last = cacheItem.LastData[^1];
|
||||||
}
|
var first = cacheItem.FirstByDate;
|
||||||
|
return (first, last);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<TelemetryDataStatDto> GetStat()
|
private async Task InitializeCacheFromDBAsync<TEntity>(IAsbCloudDbContext db, Action<string, double?> onProgress, CancellationToken token)
|
||||||
{
|
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
||||||
var result = caches.Select(cacheItem => new TelemetryDataStatDto
|
{
|
||||||
{
|
var defaultTimeout = db.Database.GetCommandTimeout();
|
||||||
IdTelemetry = cacheItem.Key,
|
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
|
||||||
DateFirst = cacheItem.Value.FirstByDate.DateTime,
|
|
||||||
DateLast = cacheItem.Value.LastData[^1].DateTime,
|
|
||||||
TimezoneOffsetHours = cacheItem.Value.TimezoneHours,
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual TDto? GetLastOrDefault(int idTelemetry)
|
if (isLoading)
|
||||||
{
|
throw new Exception("Multiple cache loading detected.");
|
||||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
|
||||||
return default;
|
|
||||||
|
|
||||||
return cacheItem.LastData.LastOrDefault();
|
try
|
||||||
}
|
{
|
||||||
|
isLoading = true;
|
||||||
public DatesRangeDto? GetOrDefaultDataDateRange(int idTelemetry)
|
|
||||||
{
|
Well[] wells = await db.Set<Well>()
|
||||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
.Include(well => well.Telemetry)
|
||||||
return null;
|
.Include(well => well.Cluster)
|
||||||
|
.Where(well => well.IdTelemetry != null)
|
||||||
if (!cacheItem.LastData.Any())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var from = DateTime.SpecifyKind(cacheItem.FirstByDate.DateTime, DateTimeKind.Unspecified);
|
|
||||||
var to = DateTime.SpecifyKind(cacheItem.LastData[^1].DateTime, DateTimeKind.Unspecified);
|
|
||||||
|
|
||||||
return new DatesRangeDto
|
|
||||||
{
|
|
||||||
From = new DateTimeOffset(from, TimeSpan.FromHours(cacheItem.TimezoneHours)),
|
|
||||||
To = new DateTimeOffset(to, TimeSpan.FromHours(cacheItem.TimezoneHours))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatesRangeDto? GetOrDefaultCachedDateRange(int idTelemetry)
|
|
||||||
{
|
|
||||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (cacheItem.LastData.Count < 2)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var to = cacheItem.LastData[^1].DateTime;
|
|
||||||
var from = cacheItem.LastData[0].DateTime;
|
|
||||||
|
|
||||||
return new DatesRangeDto { From = from, To = to };
|
|
||||||
}
|
|
||||||
|
|
||||||
public (TDto First, TDto Last)? GetOrDefaultFirstLast(int idTelemetry)
|
|
||||||
{
|
|
||||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!cacheItem.LastData.Any())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var last = cacheItem.LastData[^1];
|
|
||||||
var first = cacheItem.FirstByDate;
|
|
||||||
return (first, last);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeCacheFromDBAsync<TEntity>(IAsbCloudDbContext db, Action<string, double?> onProgress, CancellationToken token)
|
|
||||||
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
|
||||||
{
|
|
||||||
var defaultTimeout = db.Database.GetCommandTimeout();
|
|
||||||
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
|
|
||||||
|
|
||||||
if (isLoading)
|
|
||||||
throw new Exception("Multiple cache loading detected.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
isLoading = true;
|
|
||||||
|
|
||||||
Well[] wells = await db.Set<Well>()
|
|
||||||
.Include(well => well.Telemetry)
|
|
||||||
.Include(well => well.Cluster)
|
|
||||||
.Where(well => well.IdTelemetry != null)
|
|
||||||
.ToArrayAsync(token);
|
|
||||||
|
|
||||||
var count = wells.Length;
|
|
||||||
var i = 0d;
|
|
||||||
foreach (Well well in wells)
|
|
||||||
{
|
|
||||||
var capacity = well.IdState == 1
|
|
||||||
? activeWellCapacity
|
|
||||||
: doneWellCapacity;
|
|
||||||
|
|
||||||
var idTelemetry = well.IdTelemetry!.Value;
|
|
||||||
var hoursOffset = well.Timezone.Hours;
|
|
||||||
|
|
||||||
onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++ / count);
|
|
||||||
var cacheItem = await GetOrDefaultCacheDataFromDbAsync<TEntity>(db, idTelemetry, capacity, hoursOffset, token);
|
|
||||||
if (cacheItem is not null)
|
|
||||||
caches.TryAdd(idTelemetry, cacheItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
isLoading = false;
|
|
||||||
db.Database.SetCommandTimeout(defaultTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<TelemetryDataCacheItem?> GetOrDefaultCacheDataFromDbAsync<TEntity>(IAsbCloudDbContext db, int idTelemetry, int capacity, double hoursOffset, CancellationToken token)
|
|
||||||
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
|
||||||
{
|
|
||||||
var query = db.Set<TEntity>()
|
|
||||||
.Where(i => i.IdTelemetry == idTelemetry);
|
|
||||||
|
|
||||||
var firstDbEntity = await query
|
|
||||||
.OrderBy(i => i.DateTime)
|
|
||||||
.FirstOrDefaultAsync(token);
|
|
||||||
|
|
||||||
if (firstDbEntity is null)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
var first = firstDbEntity.Adapt<TDto>();
|
|
||||||
first.DateTime = firstDbEntity.DateTime.ToRemoteDateTime(hoursOffset);
|
|
||||||
|
|
||||||
var entities = await query
|
|
||||||
.OrderByDescending(i => i.DateTime)
|
|
||||||
.Take(capacity)
|
|
||||||
.ToArrayAsync(token);
|
.ToArrayAsync(token);
|
||||||
|
|
||||||
var dtos = entities
|
var count = wells.Length;
|
||||||
.AsEnumerable()
|
var i = 0d;
|
||||||
.Reverse()
|
foreach (Well well in wells)
|
||||||
.Select(entity =>
|
|
||||||
{
|
|
||||||
var dto = entity.Adapt<TDto>();
|
|
||||||
dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset);
|
|
||||||
return dto;
|
|
||||||
});
|
|
||||||
|
|
||||||
var cacheItem = new CyclicArray<TDto>(capacity);
|
|
||||||
cacheItem.AddRange(dtos);
|
|
||||||
|
|
||||||
var item = new TelemetryDataCacheItem
|
|
||||||
{
|
{
|
||||||
FirstByDate = first,
|
var capacity = well.IdState == 1
|
||||||
LastData = cacheItem,
|
? activeWellCapacity
|
||||||
TimezoneHours = hoursOffset,
|
: doneWellCapacity;
|
||||||
};
|
|
||||||
return item;
|
var idTelemetry = well.IdTelemetry!.Value;
|
||||||
|
var hoursOffset = well.Timezone.Hours;
|
||||||
|
|
||||||
|
onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++ / count);
|
||||||
|
var cacheItem = await GetOrDefaultCacheDataFromDbAsync<TEntity>(db, idTelemetry, capacity, hoursOffset, token);
|
||||||
|
if (cacheItem is not null)
|
||||||
|
caches.TryAdd(idTelemetry, cacheItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, TelemetryDataRequest request)
|
|
||||||
{
|
{
|
||||||
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
isLoading = false;
|
||||||
return null;
|
db.Database.SetCommandTimeout(defaultTimeout);
|
||||||
|
|
||||||
IEnumerable<TDto> data = cacheItem.LastData;
|
|
||||||
|
|
||||||
if (!data.Any())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (request.GeDate.HasValue)
|
|
||||||
{
|
|
||||||
var geDate = request.GeDate.Value.ToRemoteDateTime(cacheItem.TimezoneHours);
|
|
||||||
if (data.First().DateTime > geDate)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
data = data.Where(d => d.DateTime >= geDate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (request.Order == 0)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.LeDate.HasValue)
|
|
||||||
{
|
|
||||||
var leDate = request.LeDate.Value.ToRemoteDateTime(cacheItem.TimezoneHours);
|
|
||||||
data = data.Where(d => d.DateTime <= request.LeDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Divider > 1)
|
|
||||||
data = data.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: // Поздние вперед
|
|
||||||
data = data
|
|
||||||
.OrderByDescending(d => d.DateTime)
|
|
||||||
.Skip(request.Skip)
|
|
||||||
.Take(request.Take)
|
|
||||||
.OrderBy(d => d.DateTime);
|
|
||||||
break;
|
|
||||||
default: // Ранние вперед
|
|
||||||
data = data
|
|
||||||
.OrderBy(d => d.DateTime)
|
|
||||||
.Skip(request.Skip)
|
|
||||||
.Take(request.Take);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<int> GetIds(TelemetryDataRequest request)
|
|
||||||
{
|
|
||||||
var data = caches.Where(i => i.Value.LastData.Count > 0);
|
|
||||||
|
|
||||||
if (request.GeDate.HasValue)
|
|
||||||
{
|
|
||||||
data = data
|
|
||||||
.Where(item => {
|
|
||||||
var lastItem = item.Value.LastData.Last();
|
|
||||||
var geDate = request.GeDate.Value.ToOffset(TimeSpan.FromHours(item.Value.TimezoneHours));
|
|
||||||
return lastItem.DateTime >= geDate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.LeDate.HasValue)
|
|
||||||
{
|
|
||||||
data = data
|
|
||||||
.Where(item => {
|
|
||||||
var firstItem = item.Value.LastData.First();
|
|
||||||
var leDate = request.LeDate.Value.ToOffset(TimeSpan.FromHours(item.Value.TimezoneHours));
|
|
||||||
return firstItem.DateTime <= leDate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var telemetryIds = data.Select(item => item.Key);
|
|
||||||
return telemetryIds;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<TelemetryDataCacheItem?> GetOrDefaultCacheDataFromDbAsync<TEntity>(IAsbCloudDbContext db, int idTelemetry, int capacity, double hoursOffset, CancellationToken token)
|
||||||
|
where TEntity : class, AsbCloudDb.Model.ITelemetryData
|
||||||
|
{
|
||||||
|
var query = db.Set<TEntity>()
|
||||||
|
.Where(i => i.IdTelemetry == idTelemetry);
|
||||||
|
|
||||||
|
var firstDbEntity = await query
|
||||||
|
.OrderBy(i => i.DateTime)
|
||||||
|
.FirstOrDefaultAsync(token);
|
||||||
|
|
||||||
|
if (firstDbEntity is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var first = firstDbEntity.Adapt<TDto>();
|
||||||
|
first.DateTime = firstDbEntity.DateTime.ToRemoteDateTime(hoursOffset);
|
||||||
|
|
||||||
|
var entities = await query
|
||||||
|
.OrderByDescending(i => i.DateTime)
|
||||||
|
.Take(capacity)
|
||||||
|
.ToArrayAsync(token);
|
||||||
|
|
||||||
|
var dtos = entities
|
||||||
|
.AsEnumerable()
|
||||||
|
.Reverse()
|
||||||
|
.Select(entity =>
|
||||||
|
{
|
||||||
|
var dto = entity.Adapt<TDto>();
|
||||||
|
dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset);
|
||||||
|
return dto;
|
||||||
|
});
|
||||||
|
|
||||||
|
var cacheItem = new CyclicArray<TDto>(capacity);
|
||||||
|
cacheItem.AddRange(dtos);
|
||||||
|
|
||||||
|
var item = new TelemetryDataCacheItem
|
||||||
|
{
|
||||||
|
FirstByDate = first,
|
||||||
|
LastData = cacheItem,
|
||||||
|
TimezoneHours = hoursOffset,
|
||||||
|
};
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, TelemetryDataRequest request)
|
||||||
|
{
|
||||||
|
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
IEnumerable<TDto> data = cacheItem.LastData;
|
||||||
|
|
||||||
|
if (!data.Any())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (request.GeDate.HasValue)
|
||||||
|
{
|
||||||
|
var geDate = request.GeDate.Value.ToRemoteDateTime(cacheItem.TimezoneHours);
|
||||||
|
if (data.First().DateTime > geDate)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
data = data.Where(d => d.DateTime >= geDate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (request.Order == 0)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.LeDate.HasValue)
|
||||||
|
{
|
||||||
|
var leDate = request.LeDate.Value.ToRemoteDateTime(cacheItem.TimezoneHours);
|
||||||
|
data = data.Where(d => d.DateTime <= request.LeDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Divider > 1)
|
||||||
|
data = data.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: // Поздние вперед
|
||||||
|
data = data
|
||||||
|
.OrderByDescending(d => d.DateTime)
|
||||||
|
.Skip(request.Skip)
|
||||||
|
.Take(request.Take)
|
||||||
|
.OrderBy(d => d.DateTime);
|
||||||
|
break;
|
||||||
|
default: // Ранние вперед
|
||||||
|
data = data
|
||||||
|
.OrderBy(d => d.DateTime)
|
||||||
|
.Skip(request.Skip)
|
||||||
|
.Take(request.Take);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<int> GetIds(TelemetryDataRequest request)
|
||||||
|
{
|
||||||
|
var data = caches.Where(i => i.Value.LastData.Count > 0);
|
||||||
|
|
||||||
|
if (request.GeDate.HasValue)
|
||||||
|
{
|
||||||
|
data = data
|
||||||
|
.Where(item => {
|
||||||
|
var lastItem = item.Value.LastData.Last();
|
||||||
|
var geDate = request.GeDate.Value.ToOffset(TimeSpan.FromHours(item.Value.TimezoneHours));
|
||||||
|
return lastItem.DateTime >= geDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.LeDate.HasValue)
|
||||||
|
{
|
||||||
|
data = data
|
||||||
|
.Where(item => {
|
||||||
|
var firstItem = item.Value.LastData.First();
|
||||||
|
var leDate = request.LeDate.Value.ToOffset(TimeSpan.FromHours(item.Value.TimezoneHours));
|
||||||
|
return firstItem.DateTime <= leDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var telemetryIds = data.Select(item => item.Key);
|
||||||
|
return telemetryIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTimeOffset FromDate(DateTime dateTime, TimeSpan timezoneOffset)
|
||||||
|
{
|
||||||
|
var dateTimeNoKind = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
|
||||||
|
var dateTimeOffset = new DateTimeOffset(dateTimeNoKind, timezoneOffset);
|
||||||
|
return dateTimeOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
|
|||||||
|
|
||||||
public DatesRangeDto GetDatesRange(int idTelemetry)
|
public DatesRangeDto GetDatesRange(int idTelemetry)
|
||||||
{
|
{
|
||||||
var cacheDataRange = dataSaubCache.GetOrDefaultDataDateRange(idTelemetry)
|
var cacheDataRange = dataSaubCache.GetOrDefaultWellDataDateRange(idTelemetry)
|
||||||
?? new ();
|
?? new ();
|
||||||
return cacheDataRange;
|
return cacheDataRange;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user