diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs index e094cc4..154b96f 100644 --- a/Persistence.API/Controllers/TimeSeriesController.cs +++ b/Persistence.API/Controllers/TimeSeriesController.cs @@ -21,13 +21,16 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi GetDatesRangeAsync(CancellationToken token) { - var result = await this.timeSeriesDataRepository.GetDatesRangeAsync(token); - return Ok(result); + //var result = await this.timeSeriesDataCashedRepository.GetDatesRangeAsync(token); + //return Ok(result); + + return null; } [HttpPost] diff --git a/Persistence.Repository/CyclicArray.cs b/Persistence.Repository/CyclicArray.cs new file mode 100644 index 0000000..fa6d074 --- /dev/null +++ b/Persistence.Repository/CyclicArray.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Persistence.Repository; +/// +/// Цикличный массив +/// +/// +public class CyclicArray : IEnumerable +{ + readonly T[] array; + int used, current = -1; + + /// + /// constructor + /// + /// + public CyclicArray(int capacity) + { + array = new T[capacity]; + } + + /// + /// Количество элементов в массиве + /// + public int Count => used; + + /// + /// Добавить новый элемент
+ /// Если capacity достигнуто, то вытеснит самый первый элемент + ///
+ /// + public void Add(T item) + { + current = (++current) % array.Length; + array[current] = item; + if (used < array.Length) + used++; + UpdatedInvoke(current, item); + } + + /// + /// Добавить новые элементы.
+ /// Если capacity достигнуто, то вытеснит самые первые элементы.
+ /// Не вызывает Updated! + ///
+ /// + public void AddRange(IEnumerable items) + { + var capacity = array.Length; + var newItems = items.TakeLast(capacity).ToArray(); + if (newItems.Length == capacity) + { + Array.Copy(newItems, array, capacity); + current = capacity - 1; + } + else + { + current = (++current) % capacity; + var countToEndOfArray = capacity - current; + if (newItems.Length <= countToEndOfArray) + { + Array.Copy(newItems, 0, array, current, newItems.Length); + current += newItems.Length - 1; + } + else + { + var firstStepLength = countToEndOfArray; + Array.Copy(newItems, 0, array, current, firstStepLength); + var secondStepCount = newItems.Length - firstStepLength; + Array.Copy(newItems, firstStepLength, array, 0, secondStepCount); + current = secondStepCount - 1; + } + } + + if (used < capacity) + { + used += newItems.Length; + used = used > capacity ? capacity : used; + } + } + + /// + /// Индекс + /// + /// + /// + public T this[int index] + { + get + { + if (used == 0) + throw new IndexOutOfRangeException(); + + var i = (current + 1 + index) % used; + return array[i]; + } + set + { + var devider = used > 0 ? used : array.Length; + var i = (current + 1 + index) % devider; + array[i] = value; + UpdatedInvoke(current, value); + } + } + + /// + /// событие на изменение элемента в массиве + /// + public event EventHandler<(int index, T value)>? Updated; + private void UpdatedInvoke(int index, T value) + { + Updated?.Invoke(this, (index, value)); + } + + /// + /// Агрегирование значения по всему массиву + /// + /// + /// + /// + /// + public Tout Aggregate(Func func, Tout startValue) + { + Tout result = startValue; + for (int i = 0; i < used; i++) + result = func(this[i], result); + return result; + } + + /// + public IEnumerator GetEnumerator() + => new CyclycListEnumerator(array, current, used); + + /// + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + class CyclycListEnumerator : IEnumerator + { + private readonly Te[] array; + private readonly int used; + private readonly int first; + private int current = -1; + + public CyclycListEnumerator(Te[] array, int first, int used) + { + this.array = new Te[array.Length]; + array.CopyTo(this.array, 0); + this.used = used; + this.first = first; + } + + public Te Current + { + get + { + if (IsCurrentOk()) + { + var i = (current + first + 1) % used; + return array[i]; + } + else + return default!; + } + } + + object? IEnumerator.Current => Current; + + public void Dispose() {; } + + private bool IsCurrentOk() => current >= 0 && current < used; + + public bool MoveNext() + { + if (current < used) + current++; + return IsCurrentOk(); + } + + public void Reset() + { + current = -1; + } + } + + /// + /// Очистить весь массив + /// + public void Clear() + { + used = 0; + current = -1; + } +} diff --git a/Persistence.Repository/DependencyInjection.cs b/Persistence.Repository/DependencyInjection.cs index 21aa538..8e2f759 100644 --- a/Persistence.Repository/DependencyInjection.cs +++ b/Persistence.Repository/DependencyInjection.cs @@ -1,11 +1,8 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Persistence.Repositories; +using Microsoft.Extensions.DependencyInjection; using Persistence.Database.Model; +using Persistence.Repositories; using Persistence.Repository.Data; using Persistence.Repository.Repositories; -using Persistence.Database; namespace Persistence.Repository; public static class DependencyInjection @@ -18,7 +15,7 @@ public static class DependencyInjection { MapsterSetup(); - services.AddTransient, TimeSeriesDataRepository>(); + services.AddTransient, TimeSeriesDataCachedRepository>(); return services; } diff --git a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs new file mode 100644 index 0000000..765c3ce --- /dev/null +++ b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs @@ -0,0 +1,66 @@ +using Mapster; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using Persistence.Database.Model; +using Persistence.Models; + +namespace Persistence.Repository.Repositories; + +public class Test { + +} + +public class TimeSeriesDataCachedRepository : TimeSeriesDataRepository + where TEntity : class, ITimestampedData, new() + where TDto : class, ITimeSeriesAbstractDto, new() +{ + public static TDto FirstByDate { get; set; } = null!; + public static CyclicArray LastData { get; set; } = null!; + + public TimeSeriesDataCachedRepository(DbContext db) : base(db) + { + //Task.Run(async () => { + // //FirstByDate = await base.GetFirstAsync(1, CancellationToken.None); + // //LastData = await base.GetLastAsync(3600, CancellationToken.None); + + //}).wait(); + } + + public override async Task> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + { + var cacheLastData = LastData; + + if (cacheLastData[0].Date > dateBegin) + { + var dtos = await base.GetAsync(dateBegin, dateEnd, token); + + dtos = dtos.OrderBy(x => x.Date); + + FirstByDate = dtos.ElementAt(0); + LastData.AddRange(dtos); + + return dtos; + } + + var items = cacheLastData + .Where(i => i.Date >= dateBegin && i.Date <= dateEnd); + + return items; + } + + public override async Task InsertRange(IEnumerable dtos, CancellationToken token) + { + var result = await base.InsertRange(dtos, token); + if (result > 0) + { + + dtos = dtos.OrderBy(x => x.Date); + + FirstByDate = dtos.ElementAt(0); + LastData.AddRange(dtos); + } + + return result; + } +} + diff --git a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs index df8c520..6ad643c 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs @@ -18,7 +18,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository protected virtual IQueryable GetQueryReadOnly() => this.db.Set(); - public async Task> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + public virtual async Task> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) { var query = GetQueryReadOnly(); var entities = await query.ToArrayAsync(token); @@ -27,7 +27,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository return dtos; } - public async Task GetDatesRangeAsync(CancellationToken token) + public virtual async Task GetDatesRangeAsync(CancellationToken token) { var query = GetQueryReadOnly(); var minDate = await query.MinAsync(o => o.Date, token); @@ -40,7 +40,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository }; } - public async Task> GetGtDate(DateTimeOffset date, CancellationToken token) + public virtual async Task> GetGtDate(DateTimeOffset date, CancellationToken token) { var query = this.db.Set().Where(e => e.Date > date); var entities = await query.ToArrayAsync(token); @@ -50,7 +50,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository return dtos; } - public async Task InsertRange(IEnumerable dtos, CancellationToken token) + public virtual async Task InsertRange(IEnumerable dtos, CancellationToken token) { var entities = dtos.Select(d => d.Adapt());