diff --git a/DD.Persistence.Client/Clients/Mapping/Abstractions/ITimestampedMappingClient.cs b/DD.Persistence.Client/Clients/Mapping/Abstractions/ITimestampedMappingClient.cs index 29908ff..6be0e58 100644 --- a/DD.Persistence.Client/Clients/Mapping/Abstractions/ITimestampedMappingClient.cs +++ b/DD.Persistence.Client/Clients/Mapping/Abstractions/ITimestampedMappingClient.cs @@ -3,12 +3,12 @@ namespace DD.Persistence.Client.Clients.Mapping.Abstractions; /// -/// +/// Маппинг - обертка для клиента по работе с данными /// public interface ITimestampedMappingClient : ITimestampedValuesClient { /// - /// Получить данные с фильтрацией для нескольких систем. Значение фильтра null - отключен + /// Получить данные с преобразованием к заданному типу /// /// /// @@ -17,17 +17,29 @@ public interface ITimestampedMappingClient : ITimestampedValuesClient /// /// /// - Task> Get(Guid discriminatorId, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token); - Task> Gett(IEnumerable discriminatorIds, string? filterTree, DateTimeOffset? timestampBegin, IEnumerable? columnNames, int skip, int take, CancellationToken token); + Task> GetMapped(Guid discriminatorId, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token); + + /// + /// Получить набор данных, преобразованных к соответствующим типам из заданного конфига + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task>> GetMultiMapped(IEnumerable discriminatorIds, DateTimeOffset? timestampBegin, string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token); /// - /// + /// Получить данные с конца с преобразованием к заданному типу /// /// /// /// /// /// - Task> GetLast(Guid idDiscriminator, int take, CancellationToken token); + Task> GetLastMapped(Guid idDiscriminator, int take, CancellationToken token); } diff --git a/DD.Persistence.Client/Clients/Mapping/Clients/SetpointMappingClient.cs b/DD.Persistence.Client/Clients/Mapping/Clients/SetpointMappingClient.cs index fed7187..010d2c0 100644 --- a/DD.Persistence.Client/Clients/Mapping/Clients/SetpointMappingClient.cs +++ b/DD.Persistence.Client/Clients/Mapping/Clients/SetpointMappingClient.cs @@ -5,16 +5,11 @@ using DD.Persistence.Models.Common; using System.Text.Json; namespace DD.Persistence.Client.Clients.Mapping.Clients; -internal class SetpointMappingClient(ISetpointClient setpointClient, Dictionary mappingConfigs) : ISetpointMappingClient + +/// +public class SetpointMappingClient(ISetpointClient setpointClient, Dictionary mappingConfigs) : ISetpointMappingClient { - public async Task Add(Guid setpointKey, object newValue, CancellationToken token) - => await setpointClient.Add(setpointKey, newValue, token); - - public void Dispose() - { - setpointClient.Dispose(); - } - + /// public async Task> GetCurrent(IEnumerable setpointKeys, CancellationToken token) => (await setpointClient.GetCurrent(setpointKeys, token)) .Select(x => new SetpointValueDto @@ -23,15 +18,16 @@ internal class SetpointMappingClient(ISetpointClient setpointClient, Dictionary< Value = DeserializeValue(x.Key, (JsonElement)x.Value) }); + /// public async Task> GetCurrentDictionary(IEnumerable setpointConfigs, CancellationToken token) { - return (await setpointClient.GetCurrent(setpointConfigs, token)) + var result = (await setpointClient.GetCurrent(setpointConfigs, token)) .ToDictionary(x => x.Key, x => DeserializeValue(x.Key, (JsonElement)x.Value)); + + return result; } - public async Task GetDatesRangeAsync(CancellationToken token) - => await setpointClient.GetDatesRangeAsync(token); - + /// public async Task> GetHistory(IEnumerable setpointKeys, DateTimeOffset historyMoment, CancellationToken token) { var result = await setpointClient.GetHistory(setpointKeys, historyMoment, token); @@ -42,6 +38,7 @@ internal class SetpointMappingClient(ISetpointClient setpointClient, Dictionary< return result; } + /// public async Task>> GetLog(IEnumerable setpointKeys, CancellationToken token) { var result = await setpointClient.GetLog(setpointKeys, token); @@ -52,6 +49,7 @@ internal class SetpointMappingClient(ISetpointClient setpointClient, Dictionary< return result; } + /// public async Task> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token) { var res = await setpointClient.GetPart(dateBegin, take, token); @@ -61,7 +59,19 @@ internal class SetpointMappingClient(ISetpointClient setpointClient, Dictionary< return res; } + /// + public async Task Add(Guid setpointKey, object newValue, CancellationToken token) + => await setpointClient.Add(setpointKey, newValue, token); + /// + public async Task GetDatesRangeAsync(CancellationToken token) + => await setpointClient.GetDatesRangeAsync(token); + + /// + public void Dispose() + { + setpointClient.Dispose(); + } private object DeserializeValue(Guid key, JsonElement value) { @@ -72,8 +82,10 @@ internal class SetpointMappingClient(ISetpointClient setpointClient, Dictionary< } private void DeserializeList(IEnumerable? result) { + if (result is null) + return; + foreach (var log in result) log.Value = DeserializeValue(log.Key, (JsonElement)log.Value); - } } diff --git a/DD.Persistence.Client/Clients/Mapping/Clients/TimestampedMappingClient.cs b/DD.Persistence.Client/Clients/Mapping/Clients/TimestampedMappingClient.cs index d9ada9c..4a7a87b 100644 --- a/DD.Persistence.Client/Clients/Mapping/Clients/TimestampedMappingClient.cs +++ b/DD.Persistence.Client/Clients/Mapping/Clients/TimestampedMappingClient.cs @@ -3,93 +3,123 @@ using DD.Persistence.Client.Clients.Mapping.Abstractions; using DD.Persistence.Models; using DD.Persistence.Models.Common; using System.Collections.Concurrent; -using System.Reflection; namespace DD.Persistence.Client.Clients.Mapping.Clients; + +/// public class TimestampedMappingClient(ITimestampedValuesClient client, Dictionary? mappingConfigs) : ITimestampedMappingClient { - /// - private readonly ConcurrentDictionary mapperCache = new(); + private readonly ConcurrentDictionary mapperCache = new(); + /// + public async Task> GetMapped(Guid discriminatorId, DateTimeOffset? geTimestamp, + string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token) + { + var data = await Get([discriminatorId], geTimestamp, filterTree, columnNames, skip, take, token); + var mapper = GetMapper(discriminatorId); + + var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType(); + + return mappedDtos; + } + + /// + public async Task> GetLastMapped(Guid idDiscriminator, int take, CancellationToken token) + { + var data = await GetLast(idDiscriminator, take, token); + var mapper = GetMapper(idDiscriminator); + + var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType(); + + return mappedDtos; + } + + /// + public async Task>> GetMultiMapped(IEnumerable discriminatorIds, DateTimeOffset? geTimestamp, + string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token) + { + var data = await client.Get(discriminatorIds, geTimestamp, filterTree, columnNames, skip, take, token); + + var result = discriminatorIds + .ToDictionary(discriminatorId => discriminatorId, discriminatorId => + { + if (mappingConfigs is null) + throw new ArgumentNullException(nameof(mappingConfigs)); + + if (!mappingConfigs.TryGetValue(discriminatorId, out var type)) + throw new InvalidCastException(); + + var mapper = GetMapper(discriminatorId, type); + + var mappedDtos = data + .Where(e => e.DiscriminatorId == discriminatorId) + .Select(mapper.DeserializeTimeStampedData); + + return mappedDtos; + }); + + //var genericMapperType = typeof(TimestampedSetMapper<>).MakeGenericType(type); + + //var ttype = typeof(TimestampedSetMapper); + //object b = 0; + + //var mapper = MapperMethodInfo + // .MakeGenericMethod([type]) + // .Invoke(this, [discriminatorId]); + + //var deserializeMethod = genericMapperType + // .GetMethod(nameof(TimestampedSetMapper.DeserializeTimeStampedData))!; + // ToDo: приводим к Dictionary + + return result; + } + + /// public async Task AddRange(Guid discriminatorId, IEnumerable dtos, CancellationToken token) => await client.AddRange(discriminatorId, dtos, token); + /// public async Task Count(Guid discriminatorId, CancellationToken token) => await client.Count(discriminatorId, token); + /// + public async Task> Get(IEnumerable discriminatorIds, DateTimeOffset? timestampBegin, + string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token) + => await client.Get(discriminatorIds, timestampBegin, filterTree, columnNames, skip, take, token); + + /// + public async Task GetDatesRange(Guid discriminatorId, CancellationToken token) + => await client.GetDatesRange(discriminatorId, token); + + /// + public async Task> GetFirst(Guid discriminatorId, int take, CancellationToken token) + => await client.GetFirst(discriminatorId, take, token); + + /// + public async Task> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token) + => await client.GetGtDate(discriminatorId, timestampBegin, token); + + /// + public async Task> GetLast(Guid discriminatorId, int take, CancellationToken token) + => await client.GetLast(discriminatorId, take, token); + + /// + public async Task> GetResampledData(Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default) + => await client.GetResampledData(discriminatorId, timestampBegin, intervalSec, approxPointsCount, token); + + /// public void Dispose() { client.Dispose(); } - /// - public async Task> Get(Guid discriminatorId, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token) + private TimestampedSetMapper GetMapper(Guid idDiscriminator, Type type) { - var data = await Get([discriminatorId], geTimestamp, filterTree, columnNames, skip, take, token); - var mapper = GetMapper(discriminatorId); - - return data.Select(mapper.DeserializeTimeStampedData); + return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, type)); } - /// - public async Task> GetLast(Guid idDiscriminator, int take, CancellationToken token) + private TimestampedSetMapper GetMapper(Guid idDiscriminator) { - var data = await GetLast(idDiscriminator, take, token); - var mapper = GetMapper(idDiscriminator); - - return data.Select(mapper.DeserializeTimeStampedData); + return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, typeof(T))); } - - public async Task> Gett(IEnumerable discriminatorIds, string? filterTree, DateTimeOffset? timestampBegin, IEnumerable? columnNames, int skip, int take, CancellationToken token) - { - var data = await client.Get(discriminatorIds, timestampBegin, filterTree, columnNames, skip, take, token); - - // ToDo: рефакторинг - foreach (var discriminatorId in discriminatorIds) - { - if (mappingConfigs.TryGetValue(discriminatorId, out var type)) - { - var genericType = typeof(TimestampedSetMapper<>).MakeGenericType(type); - - var mapper = - typeof(TimestampedMappingClient) - .GetMethod(nameof(GetMapper))! - .MakeGenericMethod([type]) - .Invoke(this, [discriminatorId]); - var mapperInstance = Activator.CreateInstance(genericType, mapper); // ToDo: возможно не нужно - - var deserializeMethod = genericType - .GetMethod("DeserializeTimeStampedData")!; - - var d = data.Select(e => deserializeMethod.Invoke(mapperInstance, [e])); - // ToDo: приводим к Dictionary - } - } - - return new Dictionary(); - } - - /// - private TimestampedSetMapper GetMapper(Guid idDiscriminator) - { - return (TimestampedSetMapper)mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator)); - } - - public async Task> Get(IEnumerable discriminatorIds, DateTimeOffset? timestampBegin, string? filterTree, IEnumerable? columnNames, int skip, int take, CancellationToken token) - => await client.Get(discriminatorIds, timestampBegin, filterTree, columnNames, skip, take, token); - - public async Task GetDatesRange(Guid discriminatorId, CancellationToken token) - => await client.GetDatesRange(discriminatorId, token); - - public async Task> GetFirst(Guid discriminatorId, int take, CancellationToken token) - => await client.GetFirst(discriminatorId, take, token); - - public async Task> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token) - => await client.GetGtDate(discriminatorId, timestampBegin, token); - - public async Task> GetLast(Guid discriminatorId, int take, CancellationToken token) - => await client.GetLast(discriminatorId, take, token); - - public async Task> GetResampledData(Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default) - => await client.GetResampledData(discriminatorId, timestampBegin, intervalSec, approxPointsCount, token); } diff --git a/DD.Persistence.Client/Clients/Mapping/TimestampedSetMapper.cs b/DD.Persistence.Client/Clients/Mapping/TimestampedSetMapper.cs index 4e88602..1911d09 100644 --- a/DD.Persistence.Client/Clients/Mapping/TimestampedSetMapper.cs +++ b/DD.Persistence.Client/Clients/Mapping/TimestampedSetMapper.cs @@ -6,60 +6,53 @@ using System.Text.Json; namespace DD.Persistence.Client.Clients.Mapping; -internal abstract class TimestampedSetMapperBase +internal class TimestampedSetMapper { - public abstract object Map(TimestampedValuesDto data); - -} -internal class TimestampedSetMapper : TimestampedSetMapperBase -{ - private readonly Type entityType = typeof(T); + private readonly Type entityType; public Guid IdDiscriminator { get; } private readonly ConcurrentDictionary PropertyCache = new(); - public TimestampedSetMapper(Guid idDiscriminator) + public TimestampedSetMapper(Guid idDiscriminator, Type entityType) { IdDiscriminator = idDiscriminator; + this.entityType = entityType; } - public override object Map(TimestampedValuesDto data) + public object DeserializeTimeStampedData(TimestampedValuesDto data) { - return DeserializeTimeStampedData(data)!; - } - - public T DeserializeTimeStampedData(TimestampedValuesDto data) - { - if (entityType.IsValueType) return MapStruct(data); - else - return MapClass(data); + + return MapClass(data); } - private T MapClass(TimestampedValuesDto data) + private object MapClass(TimestampedValuesDto data) { - var entity = (T)RuntimeHelpers.GetUninitializedObject(typeof(T)); + var entity = RuntimeHelpers.GetUninitializedObject(entityType); foreach (var (propertyName, value) in data.Values) { if (value is JsonElement jsonElement) SetPropertyValueFromJson(ref entity, propertyName, jsonElement); } - SetPropertyValue(ref entity, "Timestamp", data.Timestamp); + SetPropertyValue(ref entity, nameof(TimestampedValuesDto.Timestamp), data.Timestamp); + SetPropertyValue(ref entity, nameof(TimestampedValuesDto.DiscriminatorId), data.DiscriminatorId); + return entity; } - private T MapStruct(TimestampedValuesDto data) + private object MapStruct(TimestampedValuesDto data) { - var entity = Activator.CreateInstance(); + var entity = Activator.CreateInstance(entityType); object boxedEntity = entity!; foreach (var (propertyName, value) in data.Values) { if (value is JsonElement jsonElement) SetPropertyValueForStructFromJson(ref boxedEntity, propertyName, jsonElement); } - SetPropertyValueForStruct(ref boxedEntity, "Timestamp", data.Timestamp); + SetPropertyValueForStruct(ref boxedEntity, nameof(TimestampedValuesDto.Timestamp), data.Timestamp); + SetPropertyValueForStruct(ref boxedEntity, nameof(TimestampedValuesDto.DiscriminatorId), data.DiscriminatorId); - return (T)boxedEntity; + return boxedEntity; } private void SetPropertyValueForStructFromJson(ref object entity, string propertyName, JsonElement element) @@ -73,9 +66,7 @@ internal class TimestampedSetMapper : TimestampedSetMapperBase var value = element.Deserialize(property.PropertyType); property.SetValue(entity, value); } - catch (Exception ex) - { - } + catch (Exception) { } } private void SetPropertyValueForStruct(ref object entity, string propertyName, object value) { @@ -88,13 +79,11 @@ internal class TimestampedSetMapper : TimestampedSetMapperBase var convertedValue = Convert.ChangeType(value, property.PropertyType); property.SetValue(entity, convertedValue); } - catch (Exception ex) - { - } + catch (Exception) { } } - private void SetPropertyValueFromJson(ref T entity, string propertyName, JsonElement jsonElement) + private void SetPropertyValueFromJson(ref object entity, string propertyName, JsonElement jsonElement) { var property = GetPropertyInfo(propertyName); @@ -106,13 +95,10 @@ internal class TimestampedSetMapper : TimestampedSetMapperBase var value = jsonElement.Deserialize(property.PropertyType); property.SetValue(entity, value); } - catch (Exception ex) - { - - } + catch (Exception) { } } - private void SetPropertyValue(ref T entity, string propertyName, object value) + private void SetPropertyValue(ref object entity, string propertyName, object value) { var property = GetPropertyInfo(propertyName); if (property is null) @@ -123,9 +109,7 @@ internal class TimestampedSetMapper : TimestampedSetMapperBase var convertedValue = Convert.ChangeType(value, property.PropertyType); property.SetValue(entity, convertedValue); } - catch (Exception ex) - { - } + catch (Exception) { } } private PropertyInfo? GetPropertyInfo(string propertyName) diff --git a/DD.Persistence.Models/TimestampedValuesDto.cs b/DD.Persistence.Models/TimestampedValuesDto.cs index 13b592e..dbb04f9 100644 --- a/DD.Persistence.Models/TimestampedValuesDto.cs +++ b/DD.Persistence.Models/TimestampedValuesDto.cs @@ -7,6 +7,11 @@ namespace DD.Persistence.Models; /// public class TimestampedValuesDto : ITimestampAbstractDto { + /// + /// Дискриминатор + /// + public Guid DiscriminatorId { get; set; } + /// /// Временная отметка /// diff --git a/DD.Persistence.Test/MappingClientsTest.cs b/DD.Persistence.Test/MappingClientsTest.cs index 967ce45..fac46e4 100644 --- a/DD.Persistence.Test/MappingClientsTest.cs +++ b/DD.Persistence.Test/MappingClientsTest.cs @@ -1,29 +1,24 @@ using DD.Persistence.Client.Clients.Interfaces; -using DD.Persistence.Client.Clients.Mapping.Abstractions; using DD.Persistence.Client.Clients.Mapping.Clients; -using DD.Persistence.Repositories; -using DD.Persistence.Services.Interfaces; +using DD.Persistence.Models; using NSubstitute; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text.Json; namespace DD.Persistence.Test; + +public record FirstTestDto(Guid DiscriminatorId, DateTimeOffset Timestamp, int Id, string? Value); + +public record SecondTestDto(Guid DiscriminatorId, DateTimeOffset Timestamp, int Id, double Capacity); + public class MappingClientsTest { private readonly ITimestampedValuesClient timestampedValuesClient = Substitute.For(); private readonly TimestampedMappingClient timestampedMappingClient; - public record TestDto - { - public int Id { get; set; } - public string? Value { get; set; } - } private readonly Dictionary mappingConfigs = new Dictionary() { - { Guid.NewGuid(), typeof(TestDto) } + { Guid.NewGuid(), typeof(FirstTestDto) }, + { Guid.NewGuid(), typeof(SecondTestDto) } }; public MappingClientsTest() @@ -32,9 +27,59 @@ public class MappingClientsTest } [Fact] - public async void Test() + public async Task GetMultiMapped() { + // Arrange var discriminatorIds = mappingConfigs.Keys; - var result = await timestampedMappingClient.Gett(discriminatorIds, null, null, null, 0, 1, CancellationToken.None); + var firstDiscriminatorId = discriminatorIds.First(); + var secondDiscriminatorId = discriminatorIds.Last(); + var getResult = new[] + { + new TimestampedValuesDto() + { + DiscriminatorId = firstDiscriminatorId, + Timestamp = DateTime.UtcNow, + Values = new Dictionary + { + { nameof(FirstTestDto.Id), JsonDocument.Parse(JsonSerializer.Serialize(1)).RootElement }, + { nameof(FirstTestDto.Value), JsonDocument.Parse(JsonSerializer.Serialize("string1")).RootElement} + } + }, + new TimestampedValuesDto() + { + DiscriminatorId = secondDiscriminatorId, + Timestamp = DateTime.UtcNow, + Values = new Dictionary + { + { nameof(SecondTestDto.Id), JsonDocument.Parse(JsonSerializer.Serialize(1)).RootElement }, + { nameof(SecondTestDto.Capacity), JsonDocument.Parse(JsonSerializer.Serialize(0.1)).RootElement} + } + } + }; + timestampedValuesClient + .Get(discriminatorIds, null, null, null, 0, 1, CancellationToken.None) + .ReturnsForAnyArgs(getResult); + + // Act + var result = await timestampedMappingClient.GetMultiMapped(discriminatorIds, null, null, null, 0, 1, CancellationToken.None); + + // Assert + Assert.NotNull(result); + Assert.NotEmpty(result); + Assert.Equal(getResult.Count(), result.Count()); + + var firstActualDto = (FirstTestDto) result[firstDiscriminatorId].First(); + Assert.NotNull(firstActualDto); + + var actualId = firstActualDto.Id.ToString(); + var expectedId = getResult[0].Values[nameof(FirstTestDto.Id)].ToString(); + Assert.Equal(expectedId, actualId); + + var secondActualDto = (SecondTestDto) result[secondDiscriminatorId].First(); + Assert.NotNull(secondActualDto); + + actualId = secondActualDto.Id.ToString(); + expectedId = getResult[1].Values[nameof(SecondTestDto.Id)].ToString(); + Assert.Equal(expectedId, actualId); } } diff --git a/DD.Persistence/Services/TimestampedValuesService.cs b/DD.Persistence/Services/TimestampedValuesService.cs index f18bcd9..d94b9f4 100644 --- a/DD.Persistence/Services/TimestampedValuesService.cs +++ b/DD.Persistence/Services/TimestampedValuesService.cs @@ -122,6 +122,7 @@ public class TimestampedValuesService : ITimestampedValuesService { var dto = new TimestampedValuesDto() { + DiscriminatorId = keyValuePair.Key, Timestamp = Timestamp.ToUniversalTime(), Values = dataScheme .ToDictionary(k => k.PropertyName, v => Values[v.Index])