diff --git a/DD.Persistence.API/DD.Persistence.API.csproj b/DD.Persistence.API/DD.Persistence.API.csproj index 5ed1097..38dd298 100644 --- a/DD.Persistence.API/DD.Persistence.API.csproj +++ b/DD.Persistence.API/DD.Persistence.API.csproj @@ -25,7 +25,6 @@ - diff --git a/DD.Persistence.API/Startup.cs b/DD.Persistence.API/Startup.cs index 1a880c0..f19e6cf 100644 --- a/DD.Persistence.API/Startup.cs +++ b/DD.Persistence.API/Startup.cs @@ -1,6 +1,6 @@ -using DD.Persistence.Database.Model; +using DD.Persistence.Database; +using DD.Persistence.Database.Model; using DD.Persistence.Database.Postgres.Extensions; -using DD.Persistence.Repository; namespace DD.Persistence.API; @@ -14,7 +14,7 @@ public class Startup public void ConfigureServices(IServiceCollection services) { - // Add services to the container. + // AddRange services to the container. services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle diff --git a/DD.Persistence.Database.Postgres/DependencyInjection.cs b/DD.Persistence.Database.Postgres/DependencyInjection.cs index f0c8ff1..df84bb3 100644 --- a/DD.Persistence.Database.Postgres/DependencyInjection.cs +++ b/DD.Persistence.Database.Postgres/DependencyInjection.cs @@ -1,6 +1,13 @@ -using Microsoft.EntityFrameworkCore; +using DD.Persistence.Database.Entity; +using DD.Persistence.Database.Postgres.Repositories; +using DD.Persistence.Database.Postgres.RepositoriesCached; +using DD.Persistence.Models; +using DD.Persistence.Repositories; +using Mapster; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Reflection; namespace DD.Persistence.Database.Model; diff --git a/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.Designer.cs b/DD.Persistence.Database.Postgres/Migrations/20250205114037_Init.Designer.cs similarity index 90% rename from DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.Designer.cs rename to DD.Persistence.Database.Postgres/Migrations/20250205114037_Init.Designer.cs index bdeaf87..b90d452 100644 --- a/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.Designer.cs +++ b/DD.Persistence.Database.Postgres/Migrations/20250205114037_Init.Designer.cs @@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace DD.Persistence.Database.Postgres.Migrations { [DbContext(typeof(PersistencePostgresContext))] - [Migration("20250203061429_Init")] + [Migration("20250205114037_Init")] partial class Init { /// @@ -67,23 +67,6 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("change_log"); }); - modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b => - { - b.Property("DiscriminatorId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasComment("Идентификатор схемы данных"); - - b.Property("PropNames") - .IsRequired() - .HasColumnType("jsonb") - .HasComment("Наименования полей в порядке индексации"); - - b.HasKey("DiscriminatorId"); - - b.ToTable("data_scheme"); - }); - modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => { b.Property("SystemId") @@ -129,6 +112,30 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("parameter_data"); }); + modelBuilder.Entity("DD.Persistence.Database.Entity.SchemeProperty", b => + { + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Идентификатор схемы данных"); + + b.Property("Index") + .HasColumnType("integer") + .HasComment("Индекс поля"); + + b.Property("PropertyKind") + .HasColumnType("smallint") + .HasComment("Тип индексируемого поля"); + + b.Property("PropertyName") + .IsRequired() + .HasColumnType("text") + .HasComment("Наименования индексируемого поля"); + + b.HasKey("DiscriminatorId", "Index"); + + b.ToTable("scheme_property"); + }); + modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => { b.Property("Key") @@ -217,17 +224,6 @@ namespace DD.Persistence.Database.Postgres.Migrations b.Navigation("System"); }); - - modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b => - { - b.HasOne("DD.Persistence.Database.Entity.DataScheme", "DataScheme") - .WithMany() - .HasForeignKey("DiscriminatorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DataScheme"); - }); #pragma warning restore 612, 618 } } diff --git a/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.cs b/DD.Persistence.Database.Postgres/Migrations/20250205114037_Init.cs similarity index 91% rename from DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.cs rename to DD.Persistence.Database.Postgres/Migrations/20250205114037_Init.cs index df996bc..c872547 100644 --- a/DD.Persistence.Database.Postgres/Migrations/20250203061429_Init.cs +++ b/DD.Persistence.Database.Postgres/Migrations/20250205114037_Init.cs @@ -30,18 +30,6 @@ namespace DD.Persistence.Database.Postgres.Migrations table.PrimaryKey("PK_change_log", x => x.Id); }); - migrationBuilder.CreateTable( - name: "data_scheme", - columns: table => new - { - DiscriminatorId = table.Column(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"), - PropNames = table.Column(type: "jsonb", nullable: false, comment: "Наименования полей в порядке индексации") - }, - constraints: table => - { - table.PrimaryKey("PK_data_scheme", x => x.DiscriminatorId); - }); - migrationBuilder.CreateTable( name: "data_source_system", columns: table => new @@ -69,6 +57,20 @@ namespace DD.Persistence.Database.Postgres.Migrations table.PrimaryKey("PK_parameter_data", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp }); }); + migrationBuilder.CreateTable( + name: "scheme_property", + columns: table => new + { + DiscriminatorId = table.Column(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"), + Index = table.Column(type: "integer", nullable: false, comment: "Индекс поля"), + PropertyName = table.Column(type: "text", nullable: false, comment: "Наименования индексируемого поля"), + PropertyKind = table.Column(type: "smallint", nullable: false, comment: "Тип индексируемого поля") + }, + constraints: table => + { + table.PrimaryKey("PK_scheme_property", x => new { x.DiscriminatorId, x.Index }); + }); + migrationBuilder.CreateTable( name: "setpoint", columns: table => new @@ -94,12 +96,6 @@ namespace DD.Persistence.Database.Postgres.Migrations constraints: table => { table.PrimaryKey("PK_timestamped_values", x => new { x.DiscriminatorId, x.Timestamp }); - table.ForeignKey( - name: "FK_timestamped_values_data_scheme_DiscriminatorId", - column: x => x.DiscriminatorId, - principalTable: "data_scheme", - principalColumn: "DiscriminatorId", - onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -139,6 +135,9 @@ namespace DD.Persistence.Database.Postgres.Migrations migrationBuilder.DropTable( name: "parameter_data"); + migrationBuilder.DropTable( + name: "scheme_property"); + migrationBuilder.DropTable( name: "setpoint"); @@ -150,9 +149,6 @@ namespace DD.Persistence.Database.Postgres.Migrations migrationBuilder.DropTable( name: "data_source_system"); - - migrationBuilder.DropTable( - name: "data_scheme"); } } } diff --git a/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs b/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs index 5c5ad2e..e2b0921 100644 --- a/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs +++ b/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs @@ -64,23 +64,6 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("change_log"); }); - modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b => - { - b.Property("DiscriminatorId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasComment("Идентификатор схемы данных"); - - b.Property("PropNames") - .IsRequired() - .HasColumnType("jsonb") - .HasComment("Наименования полей в порядке индексации"); - - b.HasKey("DiscriminatorId"); - - b.ToTable("data_scheme"); - }); - modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => { b.Property("SystemId") @@ -126,6 +109,30 @@ namespace DD.Persistence.Database.Postgres.Migrations b.ToTable("parameter_data"); }); + modelBuilder.Entity("DD.Persistence.Database.Entity.SchemeProperty", b => + { + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Идентификатор схемы данных"); + + b.Property("Index") + .HasColumnType("integer") + .HasComment("Индекс поля"); + + b.Property("PropertyKind") + .HasColumnType("smallint") + .HasComment("Тип индексируемого поля"); + + b.Property("PropertyName") + .IsRequired() + .HasColumnType("text") + .HasComment("Наименования индексируемого поля"); + + b.HasKey("DiscriminatorId", "Index"); + + b.ToTable("scheme_property"); + }); + modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => { b.Property("Key") @@ -214,17 +221,6 @@ namespace DD.Persistence.Database.Postgres.Migrations b.Navigation("System"); }); - - modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b => - { - b.HasOne("DD.Persistence.Database.Entity.DataScheme", "DataScheme") - .WithMany() - .HasForeignKey("DiscriminatorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DataScheme"); - }); #pragma warning restore 612, 618 } } diff --git a/DD.Persistence.Database/DD.Persistence.Database.csproj b/DD.Persistence.Database/DD.Persistence.Database.csproj index 9303e6a..65f8d54 100644 --- a/DD.Persistence.Database/DD.Persistence.Database.csproj +++ b/DD.Persistence.Database/DD.Persistence.Database.csproj @@ -7,11 +7,13 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DD.Persistence.Repository/DependencyInjection.cs b/DD.Persistence.Database/DependencyInjection.cs similarity index 60% rename from DD.Persistence.Repository/DependencyInjection.cs rename to DD.Persistence.Database/DependencyInjection.cs index a6ae2af..a9291e2 100644 --- a/DD.Persistence.Repository/DependencyInjection.cs +++ b/DD.Persistence.Database/DependencyInjection.cs @@ -1,15 +1,19 @@ -using DD.Persistence.Database.Entity; +using DD.Persistence.Database.Entity; +using DD.Persistence.Database.Postgres.Repositories; +using DD.Persistence.Database.Postgres.RepositoriesCached; +using DD.Persistence.Database.Repositories; +using DD.Persistence.Database.RepositoriesCached; using DD.Persistence.Models; using DD.Persistence.Repositories; -using DD.Persistence.Repository.Repositories; -using DD.Persistence.Repository.RepositoriesCached; using Mapster; using Microsoft.Extensions.DependencyInjection; using System.Reflection; -namespace DD.Persistence.Repository; +namespace DD.Persistence.Database; + public static class DependencyInjection { + // ToDo: перенести в другой файл public static void MapsterSetup() { TypeAdapterConfig.GlobalSettings.Default.Config @@ -22,6 +26,12 @@ public static class DependencyInjection Value = src.Value, Id = src.Id }); + + TypeAdapterConfig, SchemeProperty>.NewConfig() + .Map(dest => dest.DiscriminatorId, src => src.Key) + .Map(dest => dest.Index, src => src.Value.Index) + .Map(dest => dest.PropertyKind, src => src.Value.PropertyKind) + .Map(dest => dest.PropertyName, src => src.Value.PropertyName); } public static IServiceCollection AddInfrastructure(this IServiceCollection services) @@ -37,8 +47,8 @@ public static class DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); return services; } diff --git a/DD.Persistence.Database/Entity/DataScheme.cs b/DD.Persistence.Database/Entity/DataScheme.cs deleted file mode 100644 index 75794c0..0000000 --- a/DD.Persistence.Database/Entity/DataScheme.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace DD.Persistence.Database.Entity; - -[Table("data_scheme")] -public class DataScheme -{ - [Key, Comment("Идентификатор схемы данных"),] - public Guid DiscriminatorId { get; set; } - - [Comment("Наименования полей в порядке индексации"), Column(TypeName = "jsonb")] - public string[] PropNames { get; set; } = []; -} diff --git a/DD.Persistence.Database/Entity/SchemeProperty.cs b/DD.Persistence.Database/Entity/SchemeProperty.cs new file mode 100644 index 0000000..6ef5bdb --- /dev/null +++ b/DD.Persistence.Database/Entity/SchemeProperty.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; + +namespace DD.Persistence.Database.Entity; + +[Table("scheme_property")] +[PrimaryKey(nameof(DiscriminatorId), nameof(Index))] +public class SchemeProperty +{ + [Comment("Идентификатор схемы данных")] + public Guid DiscriminatorId { get; set; } + + [Comment("Индекс поля")] + public int Index { get; set; } + + [Comment("Наименования индексируемого поля")] + public required string PropertyName { get; set; } + + [Comment("Тип индексируемого поля")] + public required JsonValueKind PropertyKind { get; set; } +} diff --git a/DD.Persistence.Database/Entity/TimestampedValues.cs b/DD.Persistence.Database/Entity/TimestampedValues.cs index a2835f7..d28e1cf 100644 --- a/DD.Persistence.Database/Entity/TimestampedValues.cs +++ b/DD.Persistence.Database/Entity/TimestampedValues.cs @@ -17,7 +17,4 @@ public class TimestampedValues : ITimestampedItem [Comment("Данные"), Column(TypeName = "jsonb")] public required object[] Values { get; set; } - - [Required, ForeignKey(nameof(DiscriminatorId)), Comment("Идентификаторы")] - public virtual DataScheme? DataScheme { get; set; } } diff --git a/DD.Persistence.Repository/Extensions/EFExtensionsSortBy.cs b/DD.Persistence.Database/Extensions/EFExtensionsSortBy.cs similarity index 99% rename from DD.Persistence.Repository/Extensions/EFExtensionsSortBy.cs rename to DD.Persistence.Database/Extensions/EFExtensionsSortBy.cs index bed529e..10818a4 100644 --- a/DD.Persistence.Repository/Extensions/EFExtensionsSortBy.cs +++ b/DD.Persistence.Database/Extensions/EFExtensionsSortBy.cs @@ -2,7 +2,7 @@ using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; -namespace DD.Persistence.Repository.Extensions; +namespace DD.Persistence.Database.Extensions; public static class EFExtensionsSortBy { diff --git a/DD.Persistence.Repository/CyclicArray.cs b/DD.Persistence.Database/Helpers/CyclicArray.cs similarity index 99% rename from DD.Persistence.Repository/CyclicArray.cs rename to DD.Persistence.Database/Helpers/CyclicArray.cs index 7a7ea1d..fcb5793 100644 --- a/DD.Persistence.Repository/CyclicArray.cs +++ b/DD.Persistence.Database/Helpers/CyclicArray.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace DD.Persistence.Repository; +namespace DD.Persistence.Database.Postgres.Helpers; /// /// Цикличный массив /// diff --git a/DD.Persistence.Repository/QueryBuilders.cs b/DD.Persistence.Database/Helpers/QueryBuilders.cs similarity index 90% rename from DD.Persistence.Repository/QueryBuilders.cs rename to DD.Persistence.Database/Helpers/QueryBuilders.cs index 52b6429..8529230 100644 --- a/DD.Persistence.Repository/QueryBuilders.cs +++ b/DD.Persistence.Database/Helpers/QueryBuilders.cs @@ -1,11 +1,10 @@ -using Microsoft.EntityFrameworkCore; -using DD.Persistence.Models.Requests; -using DD.Persistence.Models.Common; -using DD.Persistence.ModelsAbstractions; -using DD.Persistence.Database.EntityAbstractions; +using DD.Persistence.Database.EntityAbstractions; using DD.Persistence.Extensions; +using DD.Persistence.Models.Common; +using DD.Persistence.Models.Requests; +using Microsoft.EntityFrameworkCore; -namespace DD.Persistence.Repository; +namespace DD.Persistence.Database.Postgres.Helpers; /// /// класс с набором методов, необходимых для фильтрации записей @@ -55,4 +54,4 @@ public static class QueryBuilders return result; } -} +} \ No newline at end of file diff --git a/DD.Persistence.Database/PersistenceDbContext.cs b/DD.Persistence.Database/PersistenceDbContext.cs index fbbc057..4b0a874 100644 --- a/DD.Persistence.Database/PersistenceDbContext.cs +++ b/DD.Persistence.Database/PersistenceDbContext.cs @@ -10,7 +10,7 @@ public class PersistenceDbContext : DbContext { public DbSet Setpoint => Set(); - public DbSet DataSchemes => Set(); + public DbSet SchemeProperty => Set(); public DbSet TimestampedValues => Set(); @@ -30,10 +30,6 @@ public class PersistenceDbContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() - .Property(e => e.PropNames) - .HasJsonConversion(); - modelBuilder.Entity() .Property(e => e.Values) .HasJsonConversion(); diff --git a/DD.Persistence.Repository/Repositories/ChangeLogRepository.cs b/DD.Persistence.Database/Repositories/ChangeLogRepository.cs similarity index 96% rename from DD.Persistence.Repository/Repositories/ChangeLogRepository.cs rename to DD.Persistence.Database/Repositories/ChangeLogRepository.cs index 2af06ce..61e907d 100644 --- a/DD.Persistence.Repository/Repositories/ChangeLogRepository.cs +++ b/DD.Persistence.Database/Repositories/ChangeLogRepository.cs @@ -1,4 +1,5 @@ using DD.Persistence.Database.Entity; +using DD.Persistence.Database.Postgres.Helpers; using DD.Persistence.Models; using DD.Persistence.Models.Common; using DD.Persistence.Models.Requests; @@ -7,7 +8,7 @@ using Mapster; using Microsoft.EntityFrameworkCore; using UuidExtensions; -namespace DD.Persistence.Repository.Repositories; +namespace DD.Persistence.Database.Repositories; public class ChangeLogRepository : IChangeLogRepository { private readonly DbContext db; @@ -185,7 +186,7 @@ public class ChangeLogRepository : IChangeLogRepository var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); - var dates = Enumerable.Concat(datesCreate, datesUpdate); + var dates = datesCreate.Concat(datesUpdate); var datesOnly = dates .Select(d => new DateOnly(d.Year, d.Month, d.Day)) .Distinct() @@ -213,7 +214,7 @@ public class ChangeLogRepository : IChangeLogRepository public async Task> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token) { var date = dateBegin.ToUniversalTime(); - var query = this.db.Set() + var query = db.Set() .Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.Creation >= date || e.Obsolete >= date); @@ -232,7 +233,7 @@ public class ChangeLogRepository : IChangeLogRepository .Select(group => new { Min = group.Min(e => e.Creation), - Max = group.Max(e => (e.Obsolete.HasValue && e.Obsolete > e.Creation) + Max = group.Max(e => e.Obsolete.HasValue && e.Obsolete > e.Creation ? e.Obsolete.Value : e.Creation), }); diff --git a/DD.Persistence.Repository/Repositories/DataSourceSystemRepository.cs b/DD.Persistence.Database/Repositories/DataSourceSystemRepository.cs similarity index 94% rename from DD.Persistence.Repository/Repositories/DataSourceSystemRepository.cs rename to DD.Persistence.Database/Repositories/DataSourceSystemRepository.cs index d8b6c0a..d9e7fa7 100644 --- a/DD.Persistence.Repository/Repositories/DataSourceSystemRepository.cs +++ b/DD.Persistence.Database/Repositories/DataSourceSystemRepository.cs @@ -4,7 +4,7 @@ using DD.Persistence.Repositories; using Mapster; using Microsoft.EntityFrameworkCore; -namespace DD.Persistence.Repository.Repositories; +namespace DD.Persistence.Database.Postgres.Repositories; public class DataSourceSystemRepository : IDataSourceSystemRepository { protected DbContext db; diff --git a/DD.Persistence.Repository/Repositories/ParameterRepository.cs b/DD.Persistence.Database/Repositories/ParameterRepository.cs similarity index 98% rename from DD.Persistence.Repository/Repositories/ParameterRepository.cs rename to DD.Persistence.Database/Repositories/ParameterRepository.cs index d241de7..c489cd7 100644 --- a/DD.Persistence.Repository/Repositories/ParameterRepository.cs +++ b/DD.Persistence.Database/Repositories/ParameterRepository.cs @@ -5,7 +5,7 @@ using DD.Persistence.Models; using DD.Persistence.Repositories; using DD.Persistence.Models.Common; -namespace DD.Persistence.Repository.Repositories; +namespace DD.Persistence.Database.Postgres.Repositories; public class ParameterRepository : IParameterRepository { private DbContext db; diff --git a/DD.Persistence.Database/Repositories/SchemePropertyRepository.cs b/DD.Persistence.Database/Repositories/SchemePropertyRepository.cs new file mode 100644 index 0000000..26bb8aa --- /dev/null +++ b/DD.Persistence.Database/Repositories/SchemePropertyRepository.cs @@ -0,0 +1,43 @@ +using DD.Persistence.Database.Entity; +using DD.Persistence.Models; +using DD.Persistence.Repositories; +using Mapster; +using Microsoft.EntityFrameworkCore; + +namespace DD.Persistence.Database.Repositories; +public class SchemePropertyRepository : ISchemePropertyRepository +{ + protected DbContext db; + public SchemePropertyRepository(DbContext db) + { + this.db = db; + } + protected virtual IQueryable GetQueryReadOnly() => db.Set(); + + public virtual async Task AddRange(DataSchemeDto dataSchemeDto, CancellationToken token) + { + var entities = dataSchemeDto.Select(e => + KeyValuePair.Create(dataSchemeDto.DiscriminatorId, e) + .Adapt() + ); + + await db.Set().AddRangeAsync(entities, token); + await db.SaveChangesAsync(token); + } + + public virtual async Task Get(Guid dataSchemeId, CancellationToken token) + { + var query = GetQueryReadOnly() + .Where(e => e.DiscriminatorId == dataSchemeId); + var entities = await query.ToArrayAsync(token); + + DataSchemeDto? result = null; + if (entities.Length != 0) + { + var properties = entities.Select(e => e.Adapt()).ToArray(); + result = new DataSchemeDto(dataSchemeId, properties); + } + + return result; + } +} diff --git a/DD.Persistence.Repository/Repositories/SetpointRepository.cs b/DD.Persistence.Database/Repositories/SetpointRepository.cs similarity index 98% rename from DD.Persistence.Repository/Repositories/SetpointRepository.cs rename to DD.Persistence.Database/Repositories/SetpointRepository.cs index 97c6098..3cfd9b1 100644 --- a/DD.Persistence.Repository/Repositories/SetpointRepository.cs +++ b/DD.Persistence.Database/Repositories/SetpointRepository.cs @@ -6,7 +6,7 @@ using Mapster; using Microsoft.EntityFrameworkCore; using System.Text.Json; -namespace DD.Persistence.Repository.Repositories +namespace DD.Persistence.Database.Postgres.Repositories { public class SetpointRepository : ISetpointRepository { diff --git a/DD.Persistence.Repository/Repositories/TechMessagesRepository.cs b/DD.Persistence.Database/Repositories/TechMessagesRepository.cs similarity index 98% rename from DD.Persistence.Repository/Repositories/TechMessagesRepository.cs rename to DD.Persistence.Database/Repositories/TechMessagesRepository.cs index 1a84f25..15d8fe8 100644 --- a/DD.Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/DD.Persistence.Database/Repositories/TechMessagesRepository.cs @@ -7,7 +7,7 @@ using DD.Persistence.Repositories; using Mapster; using Microsoft.EntityFrameworkCore; -namespace DD.Persistence.Repository.Repositories +namespace DD.Persistence.Database.Postgres.Repositories { public class TechMessagesRepository : ITechMessagesRepository { diff --git a/DD.Persistence.Repository/Repositories/TimestampedValuesRepository.cs b/DD.Persistence.Database/Repositories/TimestampedValuesRepository.cs similarity index 99% rename from DD.Persistence.Repository/Repositories/TimestampedValuesRepository.cs rename to DD.Persistence.Database/Repositories/TimestampedValuesRepository.cs index 2005a5a..5f3f60e 100644 --- a/DD.Persistence.Repository/Repositories/TimestampedValuesRepository.cs +++ b/DD.Persistence.Database/Repositories/TimestampedValuesRepository.cs @@ -4,7 +4,7 @@ using DD.Persistence.Models.Common; using DD.Persistence.Repositories; using Microsoft.EntityFrameworkCore; -namespace DD.Persistence.Repository.Repositories; +namespace DD.Persistence.Database.Postgres.Repositories; public class TimestampedValuesRepository : ITimestampedValuesRepository { private readonly DbContext db; diff --git a/DD.Persistence.Repository/RepositoriesCached/DataSourceSystemCachedRepository.cs b/DD.Persistence.Database/RepositoriesCached/DataSourceSystemCachedRepository.cs similarity index 91% rename from DD.Persistence.Repository/RepositoriesCached/DataSourceSystemCachedRepository.cs rename to DD.Persistence.Database/RepositoriesCached/DataSourceSystemCachedRepository.cs index 87e487e..919c3ba 100644 --- a/DD.Persistence.Repository/RepositoriesCached/DataSourceSystemCachedRepository.cs +++ b/DD.Persistence.Database/RepositoriesCached/DataSourceSystemCachedRepository.cs @@ -1,10 +1,10 @@ using DD.Persistence.Database.Entity; using DD.Persistence.Models; -using DD.Persistence.Repository.Repositories; +using DD.Persistence.Database.Postgres.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; -namespace DD.Persistence.Repository.RepositoriesCached; +namespace DD.Persistence.Database.Postgres.RepositoriesCached; public class DataSourceSystemCachedRepository : DataSourceSystemRepository { private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey"; diff --git a/DD.Persistence.Repository/RepositoriesCached/DataSchemeCachedRepository.cs b/DD.Persistence.Database/RepositoriesCached/SchemePropertyCachedRepository.cs similarity index 57% rename from DD.Persistence.Repository/RepositoriesCached/DataSchemeCachedRepository.cs rename to DD.Persistence.Database/RepositoriesCached/SchemePropertyCachedRepository.cs index ea22196..64d9c01 100644 --- a/DD.Persistence.Repository/RepositoriesCached/DataSchemeCachedRepository.cs +++ b/DD.Persistence.Database/RepositoriesCached/SchemePropertyCachedRepository.cs @@ -1,21 +1,21 @@ using DD.Persistence.Models; -using DD.Persistence.Repository.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; +using DD.Persistence.Database.Repositories; -namespace DD.Persistence.Repository.RepositoriesCached; -public class DataSchemeCachedRepository : DataSchemeRepository +namespace DD.Persistence.Database.RepositoriesCached; +public class SchemePropertyCachedRepository : SchemePropertyRepository { private readonly IMemoryCache memoryCache; - public DataSchemeCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db) + public SchemePropertyCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db) { this.memoryCache = memoryCache; } - public override async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token) + public override async Task AddRange(DataSchemeDto dataSourceSystemDto, CancellationToken token) { - await base.Add(dataSourceSystemDto, token); + await base.AddRange(dataSourceSystemDto, token); memoryCache.Set(dataSourceSystemDto.DiscriminatorId, dataSourceSystemDto); } diff --git a/DD.Persistence.IntegrationTests/Controllers/TimestampedValuesControllerTest.cs b/DD.Persistence.IntegrationTests/Controllers/TimestampedValuesControllerTest.cs index c6642a5..9bb1d58 100644 --- a/DD.Persistence.IntegrationTests/Controllers/TimestampedValuesControllerTest.cs +++ b/DD.Persistence.IntegrationTests/Controllers/TimestampedValuesControllerTest.cs @@ -3,7 +3,6 @@ using DD.Persistence.Client.Clients; using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Database.Entity; -using DD.Persistence.Extensions; using DD.Persistence.Models; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -267,7 +266,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest var count = 2048; var timestampBegin = DateTimeOffset.UtcNow; var dtos = await AddRange(discriminatorId, count); - + //act var response = await timestampedValuesClient.GetResampledData(discriminatorId, timestampBegin, count); @@ -407,8 +406,12 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest private void Cleanup() { + foreach (var item in discriminatorIds) + { + memoryCache.Remove(item); + } discriminatorIds = []; dbContext.CleanupDbSet(); - dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); } } diff --git a/DD.Persistence.Models/DataSchemeDto.cs b/DD.Persistence.Models/DataSchemeDto.cs index d1efdd5..2b28176 100644 --- a/DD.Persistence.Models/DataSchemeDto.cs +++ b/DD.Persistence.Models/DataSchemeDto.cs @@ -1,9 +1,11 @@ -namespace DD.Persistence.Models; +using System.Collections; + +namespace DD.Persistence.Models; /// /// Схема для набора данных /// -public class DataSchemeDto +public class DataSchemeDto : IEnumerable, IEquatable> { /// /// Дискриминатор @@ -11,7 +13,30 @@ public class DataSchemeDto public Guid DiscriminatorId { get; set; } /// - /// Наименования полей + /// Поля /// - public string[] PropNames { get; set; } = []; + private IEnumerable Properties { get; } = []; + + /// + public DataSchemeDto(Guid discriminatorId, IEnumerable Properties) + { + DiscriminatorId = discriminatorId; + this.Properties = Properties; + } + + /// + public IEnumerator GetEnumerator() + => Properties.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + /// + public bool Equals(IEnumerable? otherProperties) + { + if (otherProperties is null) + return false; + + return Properties.SequenceEqual(otherProperties); + } } diff --git a/DD.Persistence.Models/SchemePropertyDto.cs b/DD.Persistence.Models/SchemePropertyDto.cs new file mode 100644 index 0000000..0c34ba7 --- /dev/null +++ b/DD.Persistence.Models/SchemePropertyDto.cs @@ -0,0 +1,30 @@ +using System.Text.Json; + +namespace DD.Persistence.Models; + +/// +/// Индексируемого поле из схемы для набора данных +/// +public class SchemePropertyDto : IEquatable +{ + /// + /// Индекс поля + /// + public required int Index { get; set; } + + /// + /// Наименование индексируемого поля + /// + public required string PropertyName { get; set; } + + /// + /// Тип индексируемого поля + /// + public required JsonValueKind PropertyKind { get; set; } + + /// + public bool Equals(SchemePropertyDto? other) + { + return Index == other?.Index && PropertyName == other?.PropertyName && PropertyKind == other?.PropertyKind; + } +} diff --git a/DD.Persistence.Repository.Test/DD.Persistence.Repository.Test.csproj b/DD.Persistence.Repository.Test/DD.Persistence.Repository.Test.csproj index 4f29d86..63b4134 100644 --- a/DD.Persistence.Repository.Test/DD.Persistence.Repository.Test.csproj +++ b/DD.Persistence.Repository.Test/DD.Persistence.Repository.Test.csproj @@ -20,7 +20,6 @@ - diff --git a/DD.Persistence.Repository.Test/SetpointRepositoryShould.cs b/DD.Persistence.Repository.Test/SetpointRepositoryShould.cs index 6b05ff3..d9fc3e4 100644 --- a/DD.Persistence.Repository.Test/SetpointRepositoryShould.cs +++ b/DD.Persistence.Repository.Test/SetpointRepositoryShould.cs @@ -1,12 +1,7 @@ using DD.Persistence.Database.Model; -using DD.Persistence.Repository.Repositories; +using DD.Persistence.Database.Postgres.Repositories; using Shouldly; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; namespace DD.Persistence.Repository.Test; public class SetpointRepositoryShould : IClassFixture @@ -29,7 +24,6 @@ public class SetpointRepositoryShould : IClassFixture var value = GetJsonFromObject(22); await sut.Add(id, value, Guid.NewGuid(), CancellationToken.None); - var t = fixture.dbContainer.GetConnectionString(); //act var result = await sut.GetCurrent([id], CancellationToken.None); diff --git a/DD.Persistence.Repository/DD.Persistence.Repository.csproj b/DD.Persistence.Repository/DD.Persistence.Repository.csproj deleted file mode 100644 index 75803db..0000000 --- a/DD.Persistence.Repository/DD.Persistence.Repository.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net9.0 - enable - enable - - - - - - - - - - - - - diff --git a/DD.Persistence.Repository/Repositories/DataSchemeRepository.cs b/DD.Persistence.Repository/Repositories/DataSchemeRepository.cs deleted file mode 100644 index c30c8da..0000000 --- a/DD.Persistence.Repository/Repositories/DataSchemeRepository.cs +++ /dev/null @@ -1,34 +0,0 @@ -using DD.Persistence.Database.Entity; -using DD.Persistence.Models; -using DD.Persistence.Repositories; -using Mapster; -using Microsoft.EntityFrameworkCore; - -namespace DD.Persistence.Repository.Repositories; -public class DataSchemeRepository : IDataSchemeRepository -{ - protected DbContext db; - public DataSchemeRepository(DbContext db) - { - this.db = db; - } - protected virtual IQueryable GetQueryReadOnly() => db.Set(); - - public virtual async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token) - { - var entity = dataSourceSystemDto.Adapt(); - - await db.Set().AddAsync(entity, token); - await db.SaveChangesAsync(token); - } - - public virtual async Task Get(Guid dataSchemeId, CancellationToken token) - { - var query = GetQueryReadOnly() - .Where(e => e.DiscriminatorId == dataSchemeId); - var entity = await query.ToArrayAsync(); - var dto = entity.Select(e => e.Adapt()).FirstOrDefault(); - - return dto; - } -} diff --git a/DD.Persistence.Repository/RepositoriesCached/TimestampedValuesCachedRepository.cs b/DD.Persistence.Repository/RepositoriesCached/TimestampedValuesCachedRepository.cs deleted file mode 100644 index f81f7a0..0000000 --- a/DD.Persistence.Repository/RepositoriesCached/TimestampedValuesCachedRepository.cs +++ /dev/null @@ -1,103 +0,0 @@ -//using DD.Persistence.Models; -//using DD.Persistence.Models.Common; -//using DD.Persistence.Repositories; -//using Microsoft.EntityFrameworkCore; - -//namespace DD.Persistence.Repository.Repositories; - -//public class TimestampedValuesCachedRepository : TimestampedValuesRepository -//{ -// public static TimestampedValuesDto? FirstByDate { get; private set; } -// public static CyclicArray LastData { get; } = new CyclicArray(CacheItemsCount); - -// private const int CacheItemsCount = 3600; - -// public TimestampedValuesCachedRepository(DbContext db, IDataSourceSystemRepository relatedDataRepository) : base(db, relatedDataRepository) -// { -// //Task.Run(async () => -// //{ -// // var firstDateItem = await base.GetFirst(CancellationToken.None); -// // if (firstDateItem == null) -// // { -// // return; -// // } - -// // FirstByDate = firstDateItem; - -// // var dtos = await base.GetLast(CacheItemsCount, CancellationToken.None); -// // dtos = dtos.OrderBy(d => d.Timestamp); -// // LastData.AddRange(dtos); -// //}).Wait(); -// } - -// public override async Task> GetGtDate(Guid discriminatorId, DateTimeOffset dateBegin, CancellationToken token) -// { - -// if (LastData.Count == 0 || LastData[0].Timestamp > dateBegin) -// { -// var dtos = await base.GetGtDate(discriminatorId, dateBegin, token); -// return dtos; -// } - -// var items = LastData -// .Where(i => i.Timestamp >= dateBegin); - -// return items; -// } - -// public override async Task AddRange(Guid discriminatorId, IEnumerable dtos, CancellationToken token) -// { -// var result = await base.AddRange(discriminatorId, dtos, token); -// if (result > 0) -// { - -// dtos = dtos.OrderBy(x => x.Timestamp); - -// FirstByDate = dtos.First(); -// LastData.AddRange(dtos); -// } - -// return result; -// } - -// public override async Task GetDatesRange(Guid discriminatorId, CancellationToken token) -// { -// if (FirstByDate == null) -// return null; - -// return await Task.Run(() => -// { -// return new DatesRangeDto -// { -// From = FirstByDate.Timestamp, -// To = LastData[^1].Timestamp -// }; -// }); -// } - -// public override async Task> GetResampledData( -// Guid discriminatorId, -// DateTimeOffset dateBegin, -// double intervalSec = 600d, -// int approxPointsCount = 1024, -// CancellationToken token = default) -// { -// var dtos = LastData.Where(i => i.Timestamp >= dateBegin); -// if (LastData.Count == 0 || LastData[0].Timestamp > dateBegin) -// { -// dtos = await base.GetGtDate(discriminatorId, dateBegin, token); -// } - -// var dateEnd = dateBegin.AddSeconds(intervalSec); -// dtos = dtos -// .Where(i => i.Timestamp <= dateEnd); - -// var ratio = dtos.Count() / approxPointsCount; -// if (ratio > 1) -// dtos = dtos -// .Where((_, index) => index % ratio == 0); - -// return dtos; -// } -//} - diff --git a/DD.Persistence.Test/TimestampedValuesServiceShould.cs b/DD.Persistence.Test/TimestampedValuesServiceShould.cs index 0acb6de..cb62521 100644 --- a/DD.Persistence.Test/TimestampedValuesServiceShould.cs +++ b/DD.Persistence.Test/TimestampedValuesServiceShould.cs @@ -3,12 +3,12 @@ using DD.Persistence.Repositories; using DD.Persistence.Services; using NSubstitute; -namespace DD.Persistence.Repository.Test; +namespace DD.Persistence.Test; public class TimestampedValuesServiceShould { private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For(); - private readonly IDataSchemeRepository dataSchemeRepository = Substitute.For(); - private TimestampedValuesService timestampedValuesService; + private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For(); + private readonly TimestampedValuesService timestampedValuesService; public TimestampedValuesServiceShould() { @@ -40,7 +40,6 @@ public class TimestampedValuesServiceShould private static IEnumerable Generate(int countToCreate, DateTimeOffset from) { - var result = new List(); for (int i = 0; i < countToCreate; i++) { var values = new Dictionary() diff --git a/DD.Persistence.Test/TreeBuilderTest.cs b/DD.Persistence.Test/TreeBuilderTest.cs new file mode 100644 index 0000000..1259ca7 --- /dev/null +++ b/DD.Persistence.Test/TreeBuilderTest.cs @@ -0,0 +1,107 @@ +using DD.Persistence.Filter.Models; +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder; +using Newtonsoft.Json; + +namespace DD.Persistence.Test; +public class TreeBuilderTest +{ + [Fact] + public void TreeBuildingShouldBuilt() + { + //arrange + var treeString = "(\"A\"==1)||(\"B\"==2)&&(\"C\"==3)||((\"D\"==4)||(\"E\"==5))&&(\"F\"==6)"; + + //act + var root = treeString.BuildTree(); + + //assert + Assert.NotNull(root); + + var expectedRoot = JsonConvert.SerializeObject(new TVertex( + OperationEnum.And, + new TVertex( + OperationEnum.And, + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Equal, "A", 1), + new TLeaf(OperationEnum.Equal, "B", 2) + ), + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Equal, "C", 3), + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Equal, "D", 4), + new TLeaf(OperationEnum.Equal, "E", 5) + ) + ) + ), + new TLeaf(OperationEnum.Equal, "F", 6) + )); + var actualRoot = JsonConvert.SerializeObject(root); + Assert.Equal(expectedRoot, actualRoot); + } + + [Fact] + public void TreeOperationsShouldBuilt() + { + //arrange + var treeString = "(\"A\"==1)||(\"B\"!=1)||(\"C\">1)||(\"D\">=1)||(\"E\"<1)||(\"F\"<=1)"; + + //act + var root = treeString.BuildTree(); + + //assert + Assert.NotNull(root); + + var expectedRoot = JsonConvert.SerializeObject(new TVertex( + OperationEnum.Or, + new TVertex( + OperationEnum.Or, + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Equal, "A", 1), + new TLeaf(OperationEnum.NotEqual, "B", 1) + ), + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Greate, "C", 1), + new TLeaf(OperationEnum.GreateOrEqual, "D", 1) + ) + ), + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Less, "E", 1), + new TLeaf(OperationEnum.LessOrEqual, "F", 1) + ) + )); + var actualRoot = JsonConvert.SerializeObject(root); + Assert.Equal(expectedRoot, actualRoot); + } + + [Fact] + public void LeafValuesShouldBuilt() + { + //arrange + var treeString = "(\"A\"==1.2345)||(\"B\"==12345)||(\"C\"==\"12345\")"; + + //act + var root = treeString.BuildTree(); + + //assert + Assert.NotNull(root); + + var expectedRoot = JsonConvert.SerializeObject(new TVertex( + OperationEnum.Or, + new TVertex( + OperationEnum.Or, + new TLeaf(OperationEnum.Equal, "A", 1.2345), + new TLeaf(OperationEnum.Equal, "B", 12345) + ), + new TLeaf(OperationEnum.Equal, "C", "12345") + )); + var actualRoot = JsonConvert.SerializeObject(root); + Assert.Equal(expectedRoot, actualRoot); + } +} diff --git a/DD.Persistence.sln b/DD.Persistence.sln index ca91e47..e42f411 100644 --- a/DD.Persistence.sln +++ b/DD.Persistence.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence", "DD.Persis EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.API", "DD.Persistence.API\DD.Persistence.API.csproj", "{8650A227-929E-45F0-AEF7-2C91F45FE884}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Repository", "DD.Persistence.Repository\DD.Persistence.Repository.csproj", "{493D6D92-231B-4CB6-831B-BE13884B0DE4}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Database", "DD.Persistence.Database\DD.Persistence.Database.csproj", "{F77475D1-D074-407A-9D69-2FADDDAE2056}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.IntegrationTests", "DD.Persistence.IntegrationTests\DD.Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}" @@ -51,10 +49,6 @@ Global {8650A227-929E-45F0-AEF7-2C91F45FE884}.Debug|Any CPU.Build.0 = Debug|Any CPU {8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.ActiveCfg = Release|Any CPU {8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.Build.0 = Release|Any CPU - {493D6D92-231B-4CB6-831B-BE13884B0DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {493D6D92-231B-4CB6-831B-BE13884B0DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {493D6D92-231B-4CB6-831B-BE13884B0DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {493D6D92-231B-4CB6-831B-BE13884B0DE4}.Release|Any CPU.Build.0 = Release|Any CPU {F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.Build.0 = Debug|Any CPU {F77475D1-D074-407A-9D69-2FADDDAE2056}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/DD.Persistence/Filter/Models/Abstractions/INodeVisitor.cs b/DD.Persistence/Filter/Models/Abstractions/INodeVisitor.cs new file mode 100644 index 0000000..f039ee1 --- /dev/null +++ b/DD.Persistence/Filter/Models/Abstractions/INodeVisitor.cs @@ -0,0 +1,22 @@ +namespace DD.Persistence.Filter.Models.Abstractions; + +/// +/// Посетитель бинарного дерева +/// +/// +public interface INodeVisitor +{ + /// + /// Посетить узел + /// + /// + /// + TVisitResult Visit(TVertex vertex); + + /// + /// Посетить лист + /// + /// + /// + TVisitResult Visit(TLeaf leaf); +} diff --git a/DD.Persistence/Filter/Models/Abstractions/TNode.cs b/DD.Persistence/Filter/Models/Abstractions/TNode.cs new file mode 100644 index 0000000..226a3ef --- /dev/null +++ b/DD.Persistence/Filter/Models/Abstractions/TNode.cs @@ -0,0 +1,28 @@ +using DD.Persistence.Filter.Models.Enumerations; + +namespace DD.Persistence.Filter.Models.Abstractions; + +/// +/// Абстрактная модель вершины +/// +public abstract class TNode +{ + /// + public TNode(OperationEnum operation) + { + Operation = operation; + } + + /// + /// Логическая операция + /// + public OperationEnum Operation { get; } + + /// + /// Принять посетителя + /// + /// + /// + /// + public abstract TVisitResult AcceptVisitor(INodeVisitor visitor); +} diff --git a/DD.Persistence/Filter/Models/Enumerations/OperationEnum.cs b/DD.Persistence/Filter/Models/Enumerations/OperationEnum.cs new file mode 100644 index 0000000..8f22b86 --- /dev/null +++ b/DD.Persistence/Filter/Models/Enumerations/OperationEnum.cs @@ -0,0 +1,47 @@ +namespace DD.Persistence.Filter.Models.Enumerations; + +/// +/// Логические операции +/// +public enum OperationEnum +{ + /// + /// И + /// + And = 1, + + /// + /// ИЛИ + /// + Or = 2, + + /// + /// РАВНО + /// + Equal = 3, + + /// + /// НЕ РАВНО + /// + NotEqual = 4, + + /// + /// БОЛЬШЕ + /// + Greate = 5, + + /// + /// БОЛЬШЕ ЛИБО РАВНО + /// + GreateOrEqual = 6, + + /// + /// МЕНЬШЕ + /// + Less = 7, + + /// + /// МЕНЬШЕ ЛИБО РАВНО + /// + LessOrEqual = 8 +} diff --git a/DD.Persistence/Filter/Models/TLeaf.cs b/DD.Persistence/Filter/Models/TLeaf.cs new file mode 100644 index 0000000..d974ced --- /dev/null +++ b/DD.Persistence/Filter/Models/TLeaf.cs @@ -0,0 +1,33 @@ +using DD.Persistence.Filter.Models.Abstractions; +using DD.Persistence.Filter.Models.Enumerations; + +namespace DD.Persistence.Filter.Models; + +/// +/// Модель листа +/// +public class TLeaf : TNode +{ + /// + /// Наименование поля + /// + public string PropName { get; } + + /// + /// Значение для фильтрации + /// + public object? Value { get; } + + /// + public TLeaf(OperationEnum operation, string fieldName, object? value) : base(operation) + { + PropName = fieldName; + Value = value; + } + + /// + public override TVisitResult AcceptVisitor(INodeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/DD.Persistence/Filter/Models/TVertex.cs b/DD.Persistence/Filter/Models/TVertex.cs new file mode 100644 index 0000000..3f6c200 --- /dev/null +++ b/DD.Persistence/Filter/Models/TVertex.cs @@ -0,0 +1,33 @@ +using DD.Persistence.Filter.Models.Abstractions; +using DD.Persistence.Filter.Models.Enumerations; + +namespace DD.Persistence.Filter.Models; + +/// +/// Модель узла +/// +public class TVertex : TNode +{ + /// + /// Левый потомок + /// + public TNode Left { get; } + + /// + /// Правый потомок + /// + public TNode Rigth { get; } + + /// + public TVertex(OperationEnum operation, TNode left, TNode rigth) : base(operation) + { + Left = left; + Rigth = rigth; + } + + /// + public override TVisitResult AcceptVisitor(INodeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Abstractions/IExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Abstractions/IExpression.cs new file mode 100644 index 0000000..8a5a526 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Abstractions/IExpression.cs @@ -0,0 +1,27 @@ +using DD.Persistence.Filter.Models.Enumerations; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Abstractions; + +/// +/// Интерфейс для выражений +/// +interface IExpression +{ + /// + /// Получить логическую операцию + /// + /// + OperationEnum GetOperation(); + + /// + /// Получить логическую операцию в виде строки (для регулярных выражений) + /// + /// + string GetOperationString(); + + /// + /// Реализация правила + /// + /// + void Interpret(InterpreterContext context); +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/Abstractions/NonTerminalExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/Abstractions/NonTerminalExpression.cs new file mode 100644 index 0000000..5ce524b --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/Abstractions/NonTerminalExpression.cs @@ -0,0 +1,83 @@ +using DD.Persistence.Extensions; +using DD.Persistence.Filter.Models; +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Abstractions; +using System.Text.RegularExpressions; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.NonTerminal.Abstractions; + +/// +/// Абстрактный класс для нетерминальных выражений +/// +abstract class NonTerminalExpression : IExpression +{ + /// + /// Реализация правила для нетерминальных выражений + /// + /// + public void Interpret(InterpreterContext context) + { + var operation = GetOperation(); + var operationString = GetOperationString(); + + var matches = GetMatches(context, operationString); + while (matches.Length != 0) + { + matches.ForEach(m => + { + var matchString = m.ToString(); + + var separator = operationString.Replace("\\", string.Empty); + var pair = matchString + .Trim(['(', ')']) + .Split(separator) + .Select(e => int.Parse(e)); + + var leftNode = context.TreeNodes + .FirstOrDefault(e => e.Key == pair.First()) + .Value; + var rigthNode = context.TreeNodes + .FirstOrDefault(e => e.Key == pair.Last()) + .Value; + var node = new TVertex(operation, leftNode, rigthNode); + + var key = context.TreeNodes.Count; + context.TreeNodes.Add(key, node); + + var keyString = key.ToString(); + context.TreeString = context.TreeString.Replace(matchString, keyString); + }); + + matches = GetMatches(context, operationString); + } + + var isRoot = int.TryParse(context.TreeString, out _); + if (isRoot) + { + context.TreeString = string.Empty; + context.Root = context.TreeNodes.Last().Value; + } + } + + /// + public abstract OperationEnum GetOperation(); + + /// + public abstract string GetOperationString(); + + + /// + /// Получить из акткуального состояния строки все совпадения для текущего выражения + /// + private static Match[] GetMatches(InterpreterContext context, string operationString) + { + string pattern = context.TreeString.Contains('(') && context.TreeString.Contains(')') + ? $@"\(\d+{operationString}\d+\)" : $@"\d+{operationString}\d+"; + Regex regex = new(pattern); + var matches = regex + .Matches(context.TreeString) + .ToArray(); + + return matches; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/AndExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/AndExpression.cs new file mode 100644 index 0000000..9af8dbe --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/AndExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.NonTerminal.Abstractions; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.NonTerminal; + +/// +/// Выражение для "И" +/// +class AndExpression : NonTerminalExpression +{ + private const string AndString = "&&"; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.And; + } + + /// + public override string GetOperationString() + { + return AndString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/OrExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/OrExpression.cs new file mode 100644 index 0000000..4be21bf --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/NonTerminal/OrExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.NonTerminal.Abstractions; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.NonTerminal; + +/// +/// Выражение для "ИЛИ" +/// +class OrExpression : NonTerminalExpression +{ + private const string OrString = @"\|\|"; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.Or; + } + + /// + public override string GetOperationString() + { + return OrString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/Abstract/TerminalExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/Abstract/TerminalExpression.cs new file mode 100644 index 0000000..1d13d6b --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/Abstract/TerminalExpression.cs @@ -0,0 +1,79 @@ +using DD.Persistence.Extensions; +using DD.Persistence.Filter.Models; +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Abstractions; +using System.Text.RegularExpressions; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +/// +/// Абстрактный класс для терминальных выражений +/// +abstract class TerminalExpression : IExpression +{ + /// + /// Реализация правила для терминальных выражений + /// + /// + public void Interpret(InterpreterContext context) + { + var operation = GetOperation(); + var operationString = GetOperationString(); + + var matches = GetMatches(context, operationString); + matches.ForEach(m => + { + var matchString = m.ToString(); + + var pair = matchString + .Trim(['(', ')']) + .Split(operationString); + var fieldName = pair + .First() + .Trim('\"'); + var value = ParseValue(pair.Last()); + var node = new TLeaf(operation, fieldName, value); + + var key = context.TreeNodes.Count; + context.TreeNodes.Add(key, node); + + var keyString = key.ToString(); + context.TreeString = context.TreeString.Replace(matchString, keyString); + }); + } + + /// + public abstract OperationEnum GetOperation(); + + /// + public abstract string GetOperationString(); + + /// + /// Получить из акткуального состояния строки все совпадения для текущего выражения + /// + private static Match[] GetMatches(InterpreterContext context, string operationString) + { + string pattern = $@"\([^()]*{operationString}.*?\)"; + Regex regex = new(pattern); + var matches = regex.Matches(context.TreeString).ToArray(); + + return matches; + } + + private static object? ParseValue(string value) + { + value = value.Replace('.', ','); + if (value.Contains(',') && double.TryParse(value, out _)) + { + return double.Parse(value); + } + + if (int.TryParse(value, out _)) + { + return int.Parse(value); + } + + value = value.Trim('\"'); + return value; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/EqualExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/EqualExpression.cs new file mode 100644 index 0000000..3c79045 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/EqualExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +/// +/// Выражение для "РАВНО" +/// +class EqualExpression : TerminalExpression +{ + private const string EqualString = "=="; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.Equal; + } + + /// + public override string GetOperationString() + { + return EqualString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/LessExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/LessExpression.cs new file mode 100644 index 0000000..dbbdf3c --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/LessExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +/// +/// Выражение для "МЕНЬШЕ" +/// +class LessExpression : TerminalExpression +{ + private const string EqualString = "<"; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.Less; + } + + /// + public override string GetOperationString() + { + return EqualString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/LessOrEqualExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/LessOrEqualExpression.cs new file mode 100644 index 0000000..c9514e5 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/LessOrEqualExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +/// +/// Выражение для "МЕНЬШЕ ЛИБО РАВНО" +/// +class LessOrEqualExpression : TerminalExpression +{ + private const string EqualString = "<="; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.LessOrEqual; + } + + /// + public override string GetOperationString() + { + return EqualString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/MoreExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/MoreExpression.cs new file mode 100644 index 0000000..6cdca36 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/MoreExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +/// +/// Выражение для "БОЛЬШЕ" +/// +class MoreExpression : TerminalExpression +{ + private const string EqualString = ">"; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.Greate; + } + + /// + public override string GetOperationString() + { + return EqualString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/MoreOrEqualExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/MoreOrEqualExpression.cs new file mode 100644 index 0000000..d0e13e3 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/MoreOrEqualExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +/// +/// Выражение для "БОЛЬШЕ ЛИБО РАВНО" +/// +class MoreOrEqualExpression : TerminalExpression +{ + private const string EqualString = ">="; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.GreateOrEqual; + } + + /// + public override string GetOperationString() + { + return EqualString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/NotEqualExpression.cs b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/NotEqualExpression.cs new file mode 100644 index 0000000..66662c6 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/Expressions/Terminal/NotEqualExpression.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models.Enumerations; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal.Abstract; + +namespace DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +/// +/// Выражение для "НЕРАВНО" +/// +class NotEqualExpression : TerminalExpression +{ + private const string NotEqulString = "!="; + + /// + public override OperationEnum GetOperation() + { + return OperationEnum.NotEqual; + } + + /// + public override string GetOperationString() + { + return NotEqulString; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/FilterTreeBuilder.cs b/DD.Persistence/Filter/TreeBuilder/FilterTreeBuilder.cs new file mode 100644 index 0000000..4112e21 --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/FilterTreeBuilder.cs @@ -0,0 +1,54 @@ +using DD.Persistence.Filter.Models.Abstractions; +using DD.Persistence.Filter.TreeBuilder.Expressions.Abstractions; +using DD.Persistence.Filter.TreeBuilder.Expressions.NonTerminal; +using DD.Persistence.Filter.TreeBuilder.Expressions.Terminal; + +namespace DD.Persistence.Filter.TreeBuilder; + +/// +/// Строитель бинарных деревьев +/// +public static class FilterTreeBuilder +{ + + /// + /// Построить бинарное дерево логических операций сравнения из строки + /// + /// + /// + public static TNode? BuildTree(this string treeString) + { + InterpreterContext context = new(treeString); + + // Порядок важен + List terminalExpressions = + [ + new EqualExpression(), + new NotEqualExpression(), + new MoreOrEqualExpression(), + new LessOrEqualExpression(), + new MoreExpression(), + new LessExpression() + ]; + terminalExpressions.ForEach(e => + { + e.Interpret(context); + }); + + // Порядок важен + List nonTerminalExpressions = + [ + new OrExpression(), + new AndExpression() + ]; + while (!string.IsNullOrEmpty(context.TreeString)) + { + nonTerminalExpressions.ForEach(e => + { + e.Interpret(context); + }); + } + + return context.Root; + } +} diff --git a/DD.Persistence/Filter/TreeBuilder/InterpreterContext.cs b/DD.Persistence/Filter/TreeBuilder/InterpreterContext.cs new file mode 100644 index 0000000..3471f8d --- /dev/null +++ b/DD.Persistence/Filter/TreeBuilder/InterpreterContext.cs @@ -0,0 +1,30 @@ +using DD.Persistence.Filter.Models.Abstractions; + +namespace DD.Persistence.Filter.TreeBuilder; + +/// +/// Контекст интерпретатора +/// +class InterpreterContext +{ + /// + /// Корень дерева (результат интерпретации) + /// + public TNode? Root { get; set; } + + /// + /// Дерево в виде строки (входной параметр) + /// + public string TreeString { get; set; } + + /// + /// Проиндексированные вершины дерева + /// + public Dictionary TreeNodes { get; set; } = []; + + /// + public InterpreterContext(string theeString) + { + TreeString = theeString; + } +} diff --git a/DD.Persistence/Filter/Visitors/NodeVisitor.cs b/DD.Persistence/Filter/Visitors/NodeVisitor.cs new file mode 100644 index 0000000..07c7792 --- /dev/null +++ b/DD.Persistence/Filter/Visitors/NodeVisitor.cs @@ -0,0 +1,24 @@ +using DD.Persistence.Filter.Models; +using DD.Persistence.Filter.Models.Abstractions; + +namespace DD.Persistence.Filter.Visitors; + +/// +public class NodeVisitor : INodeVisitor +{ + private readonly Func _ifVertex; + private readonly Func _ifLeaf; + + /// + public NodeVisitor(Func ifVertex, Func ifLeaf) + { + _ifVertex = ifVertex; + _ifLeaf = ifLeaf; + } + + /// + public TVisitResult Visit(TVertex vertex) => _ifVertex(vertex); + + /// + public TVisitResult Visit(TLeaf leaf) => _ifLeaf(leaf); +} diff --git a/DD.Persistence/Repositories/IDataSchemeRepository.cs b/DD.Persistence/Repositories/ISchemePropertyRepository.cs similarity index 84% rename from DD.Persistence/Repositories/IDataSchemeRepository.cs rename to DD.Persistence/Repositories/ISchemePropertyRepository.cs index c14a9cf..458f5cd 100644 --- a/DD.Persistence/Repositories/IDataSchemeRepository.cs +++ b/DD.Persistence/Repositories/ISchemePropertyRepository.cs @@ -5,7 +5,7 @@ namespace DD.Persistence.Repositories; /// /// Репозиторий для работы со схемами наборов данных /// -public interface IDataSchemeRepository +public interface ISchemePropertyRepository { /// /// Добавить схему @@ -13,7 +13,7 @@ public interface IDataSchemeRepository /// /// /// - Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token); + Task AddRange(DataSchemeDto dataSourceSystemDto, CancellationToken token); /// /// Вычитать схему diff --git a/DD.Persistence/Services/TimestampedValuesService.cs b/DD.Persistence/Services/TimestampedValuesService.cs index cbcb73c..cef700f 100644 --- a/DD.Persistence/Services/TimestampedValuesService.cs +++ b/DD.Persistence/Services/TimestampedValuesService.cs @@ -1,8 +1,8 @@ using DD.Persistence.Extensions; using DD.Persistence.Models; -using DD.Persistence.Models.Common; using DD.Persistence.Repositories; using DD.Persistence.Services.Interfaces; +using System.Text.Json; namespace DD.Persistence.Services; @@ -10,10 +10,10 @@ namespace DD.Persistence.Services; public class TimestampedValuesService : ITimestampedValuesService { private readonly ITimestampedValuesRepository timestampedValuesRepository; - private readonly IDataSchemeRepository dataSchemeRepository; + private readonly ISchemePropertyRepository dataSchemeRepository; /// - public TimestampedValuesService(ITimestampedValuesRepository timestampedValuesRepository, IDataSchemeRepository relatedDataRepository) + public TimestampedValuesService(ITimestampedValuesRepository timestampedValuesRepository, ISchemePropertyRepository relatedDataRepository) { this.timestampedValuesRepository = timestampedValuesRepository; this.dataSchemeRepository = relatedDataRepository; @@ -25,8 +25,7 @@ public class TimestampedValuesService : ITimestampedValuesService // ToDo: реализовать без foreach foreach (var dto in dtos) { - var keys = dto.Values.Keys.ToArray(); - await CreateSystemSpecificationIfNotExist(discriminatorId, keys, token); + await CreateDataSchemeIfNotExist(discriminatorId, dto, token); } var result = await timestampedValuesRepository.AddRange(discriminatorId, dtos, token); @@ -39,7 +38,7 @@ public class TimestampedValuesService : ITimestampedValuesService { var result = await timestampedValuesRepository.Get(discriminatorIds, geTimestamp, columnNames, skip, take, token); - var dtos = await Materialize(result, token); + var dtos = await BindingToDataScheme(result, token); if (!columnNames.IsNullOrEmpty()) { @@ -54,9 +53,9 @@ public class TimestampedValuesService : ITimestampedValuesService { var result = await timestampedValuesRepository.GetFirst(discriminatorId, takeCount, token); - var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } + var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) } .ToDictionary(); - var dtos = await Materialize(resultToMaterialize, token); + var dtos = await BindingToDataScheme(resultBeforeBinding, token); return dtos; } @@ -66,9 +65,9 @@ public class TimestampedValuesService : ITimestampedValuesService { var result = await timestampedValuesRepository.GetLast(discriminatorId, takeCount, token); - var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } + var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) } .ToDictionary(); - var dtos = await Materialize(resultToMaterialize, token); + var dtos = await BindingToDataScheme(resultBeforeBinding, token); return dtos; } @@ -83,9 +82,9 @@ public class TimestampedValuesService : ITimestampedValuesService { var result = await timestampedValuesRepository.GetResampledData(discriminatorId, beginTimestamp, intervalSec, approxPointsCount, token); - var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } + var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) } .ToDictionary(); - var dtos = await Materialize(resultToMaterialize, token); + var dtos = await BindingToDataScheme(resultBeforeBinding, token); return dtos; } @@ -94,42 +93,37 @@ public class TimestampedValuesService : ITimestampedValuesService public async Task> GetGtDate(Guid discriminatorId, DateTimeOffset beginTimestamp, CancellationToken token) { var result = await timestampedValuesRepository.GetGtDate(discriminatorId, beginTimestamp, token); - - var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } + + var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) } .ToDictionary(); - var dtos = await Materialize(resultToMaterialize, token); + var dtos = await BindingToDataScheme(resultBeforeBinding, token); return dtos; } + // ToDo: рефакторинг, переименовать (текущее название не отражает суть) /// /// Преобразовать результат запроса в набор dto /// /// /// /// - private async Task> Materialize(IDictionary> queryResult, CancellationToken token) + private async Task> BindingToDataScheme(IDictionary> queryResult, CancellationToken token) { IEnumerable result = []; foreach (var keyValuePair in queryResult) { var dataScheme = await dataSchemeRepository.Get(keyValuePair.Key, token); if (dataScheme is null) - { continue; - } - foreach (var tuple in keyValuePair.Value) + foreach (var (Timestamp, Values) in keyValuePair.Value) { - var identity = dataScheme!.PropNames; - var indexedIdentity = identity - .Select((value, index) => new { index, value }); - var dto = new TimestampedValuesDto() { - Timestamp = tuple.Timestamp.ToUniversalTime(), - Values = indexedIdentity - .ToDictionary(x => x.value, x => tuple.Values[x.index]) + Timestamp = Timestamp.ToUniversalTime(), + Values = dataScheme + .ToDictionary(k => k.PropertyName, v => Values[v.Index]) }; result = result.Append(dto); @@ -140,34 +134,36 @@ public class TimestampedValuesService : ITimestampedValuesService } /// - /// Создать спецификацию, при отсутствии таковой + /// Создать схему данных, при отсутствии таковой /// - /// Дискриминатор системы - /// Набор наименований полей + /// Дискриминатор схемы + /// Набор данных, по образу которого будет создана соответствующая схема /// /// /// Некорректный набор наименований полей - private async Task CreateSystemSpecificationIfNotExist(Guid discriminatorId, string[] fieldNames, CancellationToken token) + private async Task CreateDataSchemeIfNotExist(Guid discriminatorId, TimestampedValuesDto dto, CancellationToken token) { - var systemSpecification = await dataSchemeRepository.Get(discriminatorId, token); - if (systemSpecification is null) + var valuesList = dto.Values.ToList(); + var properties = valuesList.Select((e, index) => new SchemePropertyDto() { - systemSpecification = new DataSchemeDto() - { - DiscriminatorId = discriminatorId, - PropNames = fieldNames - }; - await dataSchemeRepository.Add(systemSpecification, token); + Index = index, + PropertyName = e.Key, + PropertyKind = ((JsonElement)e.Value).ValueKind + }); + + var dataScheme = await dataSchemeRepository.Get(discriminatorId, token); + if (dataScheme is null) + { + dataScheme = new DataSchemeDto(discriminatorId, properties); + await dataSchemeRepository.AddRange(dataScheme, token); return; } - if (!systemSpecification.PropNames.SequenceEqual(fieldNames)) + if (!dataScheme.Equals(properties)) { - var expectedFieldNames = string.Join(", ", systemSpecification.PropNames); - var actualFieldNames = string.Join(", ", fieldNames); throw new InvalidOperationException($"Для системы {discriminatorId.ToString()} " + - $"характерен набор данных: [{expectedFieldNames}], однако был передан набор: [{actualFieldNames}]"); + $"был передан нехарактерный набор данных"); } } @@ -177,7 +173,7 @@ public class TimestampedValuesService : ITimestampedValuesService /// /// Поля, которые необходимо оставить /// - private IEnumerable ReduceSetColumnsByNames(IEnumerable dtos, IEnumerable fieldNames) + private static IEnumerable ReduceSetColumnsByNames(IEnumerable dtos, IEnumerable fieldNames) { var result = dtos.Select(dto => {