Merge branch 'fix/mock-methods-for-change-log' of ssh://git.ddrilling.ru:2221/on.nemtina/persistence into fix/mock-methods-for-change-log

This commit is contained in:
Оля Бизюкова 2025-02-06 16:57:31 +05:00
commit ed6df68f7d
56 changed files with 1057 additions and 364 deletions

View File

@ -25,7 +25,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" /> <ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" /> <ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
<ProjectReference Include="..\DD.Persistence.Repository\DD.Persistence.Repository.csproj" />
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" /> <ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -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.Database.Postgres.Extensions;
using DD.Persistence.Repository;
namespace DD.Persistence.API; namespace DD.Persistence.API;
@ -14,7 +14,7 @@ public class Startup
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
// Add services to the container. // AddRange services to the container.
services.AddControllers(); services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

View File

@ -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.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace DD.Persistence.Database.Model; namespace DD.Persistence.Database.Model;

View File

@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DD.Persistence.Database.Postgres.Migrations namespace DD.Persistence.Database.Postgres.Migrations
{ {
[DbContext(typeof(PersistencePostgresContext))] [DbContext(typeof(PersistencePostgresContext))]
[Migration("20250203061429_Init")] [Migration("20250205114037_Init")]
partial class Init partial class Init
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -67,23 +67,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b =>
{
b.Property<Guid>("DiscriminatorId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<string>("PropNames")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Наименования полей в порядке индексации");
b.HasKey("DiscriminatorId");
b.ToTable("data_scheme");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
@ -129,6 +112,30 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("parameter_data"); b.ToTable("parameter_data");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.SchemeProperty", b =>
{
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<int>("Index")
.HasColumnType("integer")
.HasComment("Индекс поля");
b.Property<byte>("PropertyKind")
.HasColumnType("smallint")
.HasComment("Тип индексируемого поля");
b.Property<string>("PropertyName")
.IsRequired()
.HasColumnType("text")
.HasComment("Наименования индексируемого поля");
b.HasKey("DiscriminatorId", "Index");
b.ToTable("scheme_property");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("Key")
@ -217,17 +224,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); 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 #pragma warning restore 612, 618
} }
} }

View File

@ -30,18 +30,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
table.PrimaryKey("PK_change_log", x => x.Id); table.PrimaryKey("PK_change_log", x => x.Id);
}); });
migrationBuilder.CreateTable(
name: "data_scheme",
columns: table => new
{
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"),
PropNames = table.Column<string>(type: "jsonb", nullable: false, comment: "Наименования полей в порядке индексации")
},
constraints: table =>
{
table.PrimaryKey("PK_data_scheme", x => x.DiscriminatorId);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "data_source_system", name: "data_source_system",
columns: table => new 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 }); table.PrimaryKey("PK_parameter_data", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp });
}); });
migrationBuilder.CreateTable(
name: "scheme_property",
columns: table => new
{
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"),
Index = table.Column<int>(type: "integer", nullable: false, comment: "Индекс поля"),
PropertyName = table.Column<string>(type: "text", nullable: false, comment: "Наименования индексируемого поля"),
PropertyKind = table.Column<byte>(type: "smallint", nullable: false, comment: "Тип индексируемого поля")
},
constraints: table =>
{
table.PrimaryKey("PK_scheme_property", x => new { x.DiscriminatorId, x.Index });
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "setpoint", name: "setpoint",
columns: table => new columns: table => new
@ -94,12 +96,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_timestamped_values", x => new { x.DiscriminatorId, x.Timestamp }); 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( migrationBuilder.CreateTable(
@ -139,6 +135,9 @@ namespace DD.Persistence.Database.Postgres.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "parameter_data"); name: "parameter_data");
migrationBuilder.DropTable(
name: "scheme_property");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "setpoint"); name: "setpoint");
@ -150,9 +149,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "data_source_system"); name: "data_source_system");
migrationBuilder.DropTable(
name: "data_scheme");
} }
} }
} }

View File

@ -64,23 +64,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b =>
{
b.Property<Guid>("DiscriminatorId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<string>("PropNames")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Наименования полей в порядке индексации");
b.HasKey("DiscriminatorId");
b.ToTable("data_scheme");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
@ -126,6 +109,30 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("parameter_data"); b.ToTable("parameter_data");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.SchemeProperty", b =>
{
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<int>("Index")
.HasColumnType("integer")
.HasComment("Индекс поля");
b.Property<byte>("PropertyKind")
.HasColumnType("smallint")
.HasComment("Тип индексируемого поля");
b.Property<string>("PropertyName")
.IsRequired()
.HasColumnType("text")
.HasComment("Наименования индексируемого поля");
b.HasKey("DiscriminatorId", "Index");
b.ToTable("scheme_property");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("Key")
@ -214,17 +221,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); 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 #pragma warning restore 612, 618
} }
} }

View File

@ -7,11 +7,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="UuidExtensions" Version="1.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -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.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Repository.Repositories;
using DD.Persistence.Repository.RepositoriesCached;
using Mapster; using Mapster;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Reflection; using System.Reflection;
namespace DD.Persistence.Repository; namespace DD.Persistence.Database;
public static class DependencyInjection public static class DependencyInjection
{ {
// ToDo: перенести в другой файл
public static void MapsterSetup() public static void MapsterSetup()
{ {
TypeAdapterConfig.GlobalSettings.Default.Config TypeAdapterConfig.GlobalSettings.Default.Config
@ -22,6 +26,12 @@ public static class DependencyInjection
Value = src.Value, Value = src.Value,
Id = src.Id Id = src.Id
}); });
TypeAdapterConfig<KeyValuePair<Guid, SchemePropertyDto>, 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) public static IServiceCollection AddInfrastructure(this IServiceCollection services)
@ -38,7 +48,7 @@ public static class DependencyInjection
services.AddTransient<ITechMessagesRepository, TechMessagesRepository>(); services.AddTransient<ITechMessagesRepository, TechMessagesRepository>();
services.AddTransient<IParameterRepository, ParameterRepository>(); services.AddTransient<IParameterRepository, ParameterRepository>();
services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>(); services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>();
services.AddTransient<IDataSchemeRepository, DataSchemeCachedRepository>(); services.AddTransient<ISchemePropertyRepository, SchemePropertyCachedRepository>();
return services; return services;
} }

View File

@ -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; } = [];
}

View File

@ -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; }
}

View File

@ -17,7 +17,4 @@ public class TimestampedValues : ITimestampedItem
[Comment("Данные"), Column(TypeName = "jsonb")] [Comment("Данные"), Column(TypeName = "jsonb")]
public required object[] Values { get; set; } public required object[] Values { get; set; }
[Required, ForeignKey(nameof(DiscriminatorId)), Comment("Идентификаторы")]
public virtual DataScheme? DataScheme { get; set; }
} }

View File

@ -2,7 +2,7 @@ using System.Collections.Concurrent;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace DD.Persistence.Repository.Extensions; namespace DD.Persistence.Database.Extensions;
public static class EFExtensionsSortBy public static class EFExtensionsSortBy
{ {

View File

@ -1,6 +1,6 @@
using System.Collections; using System.Collections;
namespace DD.Persistence.Repository; namespace DD.Persistence.Database.Postgres.Helpers;
/// <summary> /// <summary>
/// Цикличный массив /// Цикличный массив
/// </summary> /// </summary>

View File

@ -1,11 +1,10 @@
using Microsoft.EntityFrameworkCore; using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Models.Requests;
using DD.Persistence.Models.Common;
using DD.Persistence.ModelsAbstractions;
using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Extensions; 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;
/// <summary> /// <summary>
/// класс с набором методов, необходимых для фильтрации записей /// класс с набором методов, необходимых для фильтрации записей

View File

@ -10,7 +10,7 @@ public class PersistenceDbContext : DbContext
{ {
public DbSet<Setpoint> Setpoint => Set<Setpoint>(); public DbSet<Setpoint> Setpoint => Set<Setpoint>();
public DbSet<DataScheme> DataSchemes => Set<DataScheme>(); public DbSet<SchemeProperty> SchemeProperty => Set<SchemeProperty>();
public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>(); public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>();
@ -30,10 +30,6 @@ public class PersistenceDbContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<DataScheme>()
.Property(e => e.PropNames)
.HasJsonConversion();
modelBuilder.Entity<TimestampedValues>() modelBuilder.Entity<TimestampedValues>()
.Property(e => e.Values) .Property(e => e.Values)
.HasJsonConversion(); .HasJsonConversion();

View File

@ -1,4 +1,5 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Postgres.Helpers;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
@ -7,7 +8,7 @@ using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using UuidExtensions; using UuidExtensions;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Repositories;
public class ChangeLogRepository : IChangeLogRepository public class ChangeLogRepository : IChangeLogRepository
{ {
private readonly DbContext db; private readonly DbContext db;
@ -185,7 +186,7 @@ public class ChangeLogRepository : IChangeLogRepository
var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); var datesUpdate = await datesUpdateQuery.ToArrayAsync(token);
var dates = Enumerable.Concat(datesCreate, datesUpdate); var dates = datesCreate.Concat(datesUpdate);
var datesOnly = dates var datesOnly = dates
.Select(d => new DateOnly(d.Year, d.Month, d.Day)) .Select(d => new DateOnly(d.Year, d.Month, d.Day))
.Distinct() .Distinct()
@ -213,7 +214,7 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<IEnumerable<ChangeLogValuesDto>> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token) public async Task<IEnumerable<ChangeLogValuesDto>> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token)
{ {
var date = dateBegin.ToUniversalTime(); var date = dateBegin.ToUniversalTime();
var query = this.db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.IdDiscriminator == idDiscriminator)
.Where(e => e.Creation >= date || e.Obsolete >= date); .Where(e => e.Creation >= date || e.Obsolete >= date);
@ -232,7 +233,7 @@ public class ChangeLogRepository : IChangeLogRepository
.Select(group => new .Select(group => new
{ {
Min = group.Min(e => e.Creation), 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.Obsolete.Value
: e.Creation), : e.Creation),
}); });

View File

@ -4,7 +4,7 @@ using DD.Persistence.Repositories;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Postgres.Repositories;
public class DataSourceSystemRepository : IDataSourceSystemRepository public class DataSourceSystemRepository : IDataSourceSystemRepository
{ {
protected DbContext db; protected DbContext db;

View File

@ -5,7 +5,7 @@ using DD.Persistence.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Postgres.Repositories;
public class ParameterRepository : IParameterRepository public class ParameterRepository : IParameterRepository
{ {
private DbContext db; private DbContext db;

View File

@ -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<SchemeProperty> GetQueryReadOnly() => db.Set<SchemeProperty>();
public virtual async Task AddRange(DataSchemeDto dataSchemeDto, CancellationToken token)
{
var entities = dataSchemeDto.Select(e =>
KeyValuePair.Create(dataSchemeDto.DiscriminatorId, e)
.Adapt<SchemeProperty>()
);
await db.Set<SchemeProperty>().AddRangeAsync(entities, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<DataSchemeDto?> 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<SchemePropertyDto>()).ToArray();
result = new DataSchemeDto(dataSchemeId, properties);
}
return result;
}
}

View File

@ -6,7 +6,7 @@ using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text.Json; using System.Text.Json;
namespace DD.Persistence.Repository.Repositories namespace DD.Persistence.Database.Postgres.Repositories
{ {
public class SetpointRepository : ISetpointRepository public class SetpointRepository : ISetpointRepository
{ {

View File

@ -7,7 +7,7 @@ using DD.Persistence.Repositories;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories namespace DD.Persistence.Database.Postgres.Repositories
{ {
public class TechMessagesRepository : ITechMessagesRepository public class TechMessagesRepository : ITechMessagesRepository
{ {

View File

@ -4,7 +4,7 @@ using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Postgres.Repositories;
public class TimestampedValuesRepository : ITimestampedValuesRepository public class TimestampedValuesRepository : ITimestampedValuesRepository
{ {
private readonly DbContext db; private readonly DbContext db;

View File

@ -1,10 +1,10 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repository.Repositories; using DD.Persistence.Database.Postgres.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
namespace DD.Persistence.Repository.RepositoriesCached; namespace DD.Persistence.Database.Postgres.RepositoriesCached;
public class DataSourceSystemCachedRepository : DataSourceSystemRepository public class DataSourceSystemCachedRepository : DataSourceSystemRepository
{ {
private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey"; private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey";

View File

@ -1,21 +1,21 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repository.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using DD.Persistence.Database.Repositories;
namespace DD.Persistence.Repository.RepositoriesCached; namespace DD.Persistence.Database.RepositoriesCached;
public class DataSchemeCachedRepository : DataSchemeRepository public class SchemePropertyCachedRepository : SchemePropertyRepository
{ {
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
public DataSchemeCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db) public SchemePropertyCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db)
{ {
this.memoryCache = memoryCache; 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); memoryCache.Set(dataSourceSystemDto.DiscriminatorId, dataSourceSystemDto);
} }

View File

@ -3,7 +3,6 @@ using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Extensions;
using DD.Persistence.Models; using DD.Persistence.Models;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -407,8 +406,12 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
private void Cleanup() private void Cleanup()
{ {
foreach (var item in discriminatorIds)
{
memoryCache.Remove(item);
}
discriminatorIds = []; discriminatorIds = [];
dbContext.CleanupDbSet<TimestampedValues>(); dbContext.CleanupDbSet<TimestampedValues>();
dbContext.CleanupDbSet<DataScheme>(); dbContext.CleanupDbSet<SchemeProperty>();
} }
} }

View File

@ -1,9 +1,11 @@
namespace DD.Persistence.Models; using System.Collections;
namespace DD.Persistence.Models;
/// <summary> /// <summary>
/// Схема для набора данных /// Схема для набора данных
/// </summary> /// </summary>
public class DataSchemeDto public class DataSchemeDto : IEnumerable<SchemePropertyDto>, IEquatable<IEnumerable<SchemePropertyDto>>
{ {
/// <summary> /// <summary>
/// Дискриминатор /// Дискриминатор
@ -11,7 +13,30 @@ public class DataSchemeDto
public Guid DiscriminatorId { get; set; } public Guid DiscriminatorId { get; set; }
/// <summary> /// <summary>
/// Наименования полей /// Поля
/// </summary> /// </summary>
public string[] PropNames { get; set; } = []; private IEnumerable<SchemePropertyDto> Properties { get; } = [];
/// <inheritdoc/>
public DataSchemeDto(Guid discriminatorId, IEnumerable<SchemePropertyDto> Properties)
{
DiscriminatorId = discriminatorId;
this.Properties = Properties;
}
/// <inheritdoc/>
public IEnumerator<SchemePropertyDto> GetEnumerator()
=> Properties.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <inheritdoc/>
public bool Equals(IEnumerable<SchemePropertyDto>? otherProperties)
{
if (otherProperties is null)
return false;
return Properties.SequenceEqual(otherProperties);
}
} }

View File

@ -0,0 +1,30 @@
using System.Text.Json;
namespace DD.Persistence.Models;
/// <summary>
/// Индексируемого поле из схемы для набора данных
/// </summary>
public class SchemePropertyDto : IEquatable<SchemePropertyDto>
{
/// <summary>
/// Индекс поля
/// </summary>
public required int Index { get; set; }
/// <summary>
/// Наименование индексируемого поля
/// </summary>
public required string PropertyName { get; set; }
/// <summary>
/// Тип индексируемого поля
/// </summary>
public required JsonValueKind PropertyKind { get; set; }
/// <inheritdoc/>
public bool Equals(SchemePropertyDto? other)
{
return Index == other?.Index && PropertyName == other?.PropertyName && PropertyKind == other?.PropertyKind;
}
}

View File

@ -20,7 +20,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" /> <ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
<ProjectReference Include="..\DD.Persistence.Repository\DD.Persistence.Repository.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,12 +1,7 @@
using DD.Persistence.Database.Model; using DD.Persistence.Database.Model;
using DD.Persistence.Repository.Repositories; using DD.Persistence.Database.Postgres.Repositories;
using Shouldly; using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace DD.Persistence.Repository.Test; namespace DD.Persistence.Repository.Test;
public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture> public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture>
@ -29,7 +24,6 @@ public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture>
var value = GetJsonFromObject(22); var value = GetJsonFromObject(22);
await sut.Add(id, value, Guid.NewGuid(), CancellationToken.None); await sut.Add(id, value, Guid.NewGuid(), CancellationToken.None);
var t = fixture.dbContainer.GetConnectionString();
//act //act
var result = await sut.GetCurrent([id], CancellationToken.None); var result = await sut.GetCurrent([id], CancellationToken.None);

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="UuidExtensions" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
</ItemGroup>
</Project>

View File

@ -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<DataScheme> GetQueryReadOnly() => db.Set<DataScheme>();
public virtual async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token)
{
var entity = dataSourceSystemDto.Adapt<DataScheme>();
await db.Set<DataScheme>().AddAsync(entity, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<DataSchemeDto?> 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<DataSchemeDto>()).FirstOrDefault();
return dto;
}
}

View File

@ -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<TimestampedValuesDto> LastData { get; } = new CyclicArray<TimestampedValuesDto>(CacheItemsCount);
// private const int CacheItemsCount = 3600;
// public TimestampedValuesCachedRepository(DbContext db, IDataSourceSystemRepository<ValuesIdentityDto> 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<IEnumerable<TimestampedValuesDto>> 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<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> 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<DatesRangeDto?> 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<IEnumerable<TimestampedValuesDto>> 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;
// }
//}

View File

@ -3,12 +3,12 @@ using DD.Persistence.Repositories;
using DD.Persistence.Services; using DD.Persistence.Services;
using NSubstitute; using NSubstitute;
namespace DD.Persistence.Repository.Test; namespace DD.Persistence.Test;
public class TimestampedValuesServiceShould public class TimestampedValuesServiceShould
{ {
private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>(); private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>();
private readonly IDataSchemeRepository dataSchemeRepository = Substitute.For<IDataSchemeRepository>(); private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For<ISchemePropertyRepository>();
private TimestampedValuesService timestampedValuesService; private readonly TimestampedValuesService timestampedValuesService;
public TimestampedValuesServiceShould() public TimestampedValuesServiceShould()
{ {
@ -40,7 +40,6 @@ public class TimestampedValuesServiceShould
private static IEnumerable<TimestampedValuesDto> Generate(int countToCreate, DateTimeOffset from) private static IEnumerable<TimestampedValuesDto> Generate(int countToCreate, DateTimeOffset from)
{ {
var result = new List<TimestampedValuesDto>();
for (int i = 0; i < countToCreate; i++) for (int i = 0; i < countToCreate; i++)
{ {
var values = new Dictionary<string, object>() var values = new Dictionary<string, object>()

View File

@ -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);
}
}

View File

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence", "DD.Persis
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.API", "DD.Persistence.API\DD.Persistence.API.csproj", "{8650A227-929E-45F0-AEF7-2C91F45FE884}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.API", "DD.Persistence.API\DD.Persistence.API.csproj", "{8650A227-929E-45F0-AEF7-2C91F45FE884}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Database", "DD.Persistence.Database\DD.Persistence.Database.csproj", "{F77475D1-D074-407A-9D69-2FADDDAE2056}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.IntegrationTests", "DD.Persistence.IntegrationTests\DD.Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}" 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}.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.ActiveCfg = Release|Any CPU
{8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.Build.0 = 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 {F77475D1-D074-407A-9D69-2FADDDAE2056}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@ -0,0 +1,22 @@
namespace DD.Persistence.Filter.Models.Abstractions;
/// <summary>
/// Посетитель бинарного дерева
/// </summary>
/// <typeparam name="TVisitResult"></typeparam>
public interface INodeVisitor<TVisitResult>
{
/// <summary>
/// Посетить узел
/// </summary>
/// <param name="vertex"></param>
/// <returns></returns>
TVisitResult Visit(TVertex vertex);
/// <summary>
/// Посетить лист
/// </summary>
/// <param name="leaf"></param>
/// <returns></returns>
TVisitResult Visit(TLeaf leaf);
}

View File

@ -0,0 +1,28 @@
using DD.Persistence.Filter.Models.Enumerations;
namespace DD.Persistence.Filter.Models.Abstractions;
/// <summary>
/// Абстрактная модель вершины
/// </summary>
public abstract class TNode
{
/// <inheritdoc/>
public TNode(OperationEnum operation)
{
Operation = operation;
}
/// <summary>
/// Логическая операция
/// </summary>
public OperationEnum Operation { get; }
/// <summary>
/// Принять посетителя
/// </summary>
/// <typeparam name="TVisitResult"></typeparam>
/// <param name="visitor"></param>
/// <returns></returns>
public abstract TVisitResult AcceptVisitor<TVisitResult>(INodeVisitor<TVisitResult> visitor);
}

View File

@ -0,0 +1,47 @@
namespace DD.Persistence.Filter.Models.Enumerations;
/// <summary>
/// Логические операции
/// </summary>
public enum OperationEnum
{
/// <summary>
/// И
/// </summary>
And = 1,
/// <summary>
/// ИЛИ
/// </summary>
Or = 2,
/// <summary>
/// РАВНО
/// </summary>
Equal = 3,
/// <summary>
/// НЕ РАВНО
/// </summary>
NotEqual = 4,
/// <summary>
/// БОЛЬШЕ
/// </summary>
Greate = 5,
/// <summary>
/// БОЛЬШЕ ЛИБО РАВНО
/// </summary>
GreateOrEqual = 6,
/// <summary>
/// МЕНЬШЕ
/// </summary>
Less = 7,
/// <summary>
/// МЕНЬШЕ ЛИБО РАВНО
/// </summary>
LessOrEqual = 8
}

View File

@ -0,0 +1,33 @@
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Filter.Models.Enumerations;
namespace DD.Persistence.Filter.Models;
/// <summary>
/// Модель листа
/// </summary>
public class TLeaf : TNode
{
/// <summary>
/// Наименование поля
/// </summary>
public string PropName { get; }
/// <summary>
/// Значение для фильтрации
/// </summary>
public object? Value { get; }
/// <inheritdoc/>
public TLeaf(OperationEnum operation, string fieldName, object? value) : base(operation)
{
PropName = fieldName;
Value = value;
}
/// <inheritdoc/>
public override TVisitResult AcceptVisitor<TVisitResult>(INodeVisitor<TVisitResult> visitor)
{
return visitor.Visit(this);
}
}

View File

@ -0,0 +1,33 @@
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Filter.Models.Enumerations;
namespace DD.Persistence.Filter.Models;
/// <summary>
/// Модель узла
/// </summary>
public class TVertex : TNode
{
/// <summary>
/// Левый потомок
/// </summary>
public TNode Left { get; }
/// <summary>
/// Правый потомок
/// </summary>
public TNode Rigth { get; }
/// <inheritdoc/>
public TVertex(OperationEnum operation, TNode left, TNode rigth) : base(operation)
{
Left = left;
Rigth = rigth;
}
/// <inheritdoc/>
public override TVisitResult AcceptVisitor<TVisitResult>(INodeVisitor<TVisitResult> visitor)
{
return visitor.Visit(this);
}
}

View File

@ -0,0 +1,27 @@
using DD.Persistence.Filter.Models.Enumerations;
namespace DD.Persistence.Filter.TreeBuilder.Expressions.Abstractions;
/// <summary>
/// Интерфейс для выражений
/// </summary>
interface IExpression
{
/// <summary>
/// Получить логическую операцию
/// </summary>
/// <returns></returns>
OperationEnum GetOperation();
/// <summary>
/// Получить логическую операцию в виде строки (для регулярных выражений)
/// </summary>
/// <returns></returns>
string GetOperationString();
/// <summary>
/// Реализация правила
/// </summary>
/// <param name="context"></param>
void Interpret(InterpreterContext context);
}

View File

@ -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;
/// <summary>
/// Абстрактный класс для нетерминальных выражений
/// </summary>
abstract class NonTerminalExpression : IExpression
{
/// <summary>
/// Реализация правила для нетерминальных выражений
/// </summary>
/// <param name="context"></param>
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;
}
}
/// <inheritdoc/>
public abstract OperationEnum GetOperation();
/// <inheritdoc/>
public abstract string GetOperationString();
/// <summary>
/// Получить из акткуального состояния строки все совпадения для текущего выражения
/// </summary>
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;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "И"
/// </summary>
class AndExpression : NonTerminalExpression
{
private const string AndString = "&&";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.And;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return AndString;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "ИЛИ"
/// </summary>
class OrExpression : NonTerminalExpression
{
private const string OrString = @"\|\|";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.Or;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return OrString;
}
}

View File

@ -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;
/// <summary>
/// Абстрактный класс для терминальных выражений
/// </summary>
abstract class TerminalExpression : IExpression
{
/// <summary>
/// Реализация правила для терминальных выражений
/// </summary>
/// <param name="context"></param>
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);
});
}
/// <inheritdoc/>
public abstract OperationEnum GetOperation();
/// <inheritdoc/>
public abstract string GetOperationString();
/// <summary>
/// Получить из акткуального состояния строки все совпадения для текущего выражения
/// </summary>
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;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "РАВНО"
/// </summary>
class EqualExpression : TerminalExpression
{
private const string EqualString = "==";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.Equal;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return EqualString;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "МЕНЬШЕ"
/// </summary>
class LessExpression : TerminalExpression
{
private const string EqualString = "<";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.Less;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return EqualString;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "МЕНЬШЕ ЛИБО РАВНО"
/// </summary>
class LessOrEqualExpression : TerminalExpression
{
private const string EqualString = "<=";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.LessOrEqual;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return EqualString;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "БОЛЬШЕ"
/// </summary>
class MoreExpression : TerminalExpression
{
private const string EqualString = ">";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.Greate;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return EqualString;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "БОЛЬШЕ ЛИБО РАВНО"
/// </summary>
class MoreOrEqualExpression : TerminalExpression
{
private const string EqualString = ">=";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.GreateOrEqual;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return EqualString;
}
}

View File

@ -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;
/// <summary>
/// Выражение для "НЕРАВНО"
/// </summary>
class NotEqualExpression : TerminalExpression
{
private const string NotEqulString = "!=";
/// <inheritdoc/>
public override OperationEnum GetOperation()
{
return OperationEnum.NotEqual;
}
/// <inheritdoc/>
public override string GetOperationString()
{
return NotEqulString;
}
}

View File

@ -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;
/// <summary>
/// Строитель бинарных деревьев
/// </summary>
public static class FilterTreeBuilder
{
/// <summary>
/// Построить бинарное дерево логических операций сравнения из строки
/// </summary>
/// <param name="treeString"></param>
/// <returns></returns>
public static TNode? BuildTree(this string treeString)
{
InterpreterContext context = new(treeString);
// Порядок важен
List<IExpression> terminalExpressions =
[
new EqualExpression(),
new NotEqualExpression(),
new MoreOrEqualExpression(),
new LessOrEqualExpression(),
new MoreExpression(),
new LessExpression()
];
terminalExpressions.ForEach(e =>
{
e.Interpret(context);
});
// Порядок важен
List<IExpression> nonTerminalExpressions =
[
new OrExpression(),
new AndExpression()
];
while (!string.IsNullOrEmpty(context.TreeString))
{
nonTerminalExpressions.ForEach(e =>
{
e.Interpret(context);
});
}
return context.Root;
}
}

View File

@ -0,0 +1,30 @@
using DD.Persistence.Filter.Models.Abstractions;
namespace DD.Persistence.Filter.TreeBuilder;
/// <summary>
/// Контекст интерпретатора
/// </summary>
class InterpreterContext
{
/// <summary>
/// Корень дерева (результат интерпретации)
/// </summary>
public TNode? Root { get; set; }
/// <summary>
/// Дерево в виде строки (входной параметр)
/// </summary>
public string TreeString { get; set; }
/// <summary>
/// Проиндексированные вершины дерева
/// </summary>
public Dictionary<int, TNode> TreeNodes { get; set; } = [];
/// <inheritdoc/>
public InterpreterContext(string theeString)
{
TreeString = theeString;
}
}

View File

@ -0,0 +1,24 @@
using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Abstractions;
namespace DD.Persistence.Filter.Visitors;
/// <inheritdoc/>
public class NodeVisitor<TVisitResult> : INodeVisitor<TVisitResult>
{
private readonly Func<TVertex, TVisitResult> _ifVertex;
private readonly Func<TLeaf, TVisitResult> _ifLeaf;
/// <inheritdoc/>
public NodeVisitor(Func<TVertex, TVisitResult> ifVertex, Func<TLeaf, TVisitResult> ifLeaf)
{
_ifVertex = ifVertex;
_ifLeaf = ifLeaf;
}
/// <inheritdoc/>
public TVisitResult Visit(TVertex vertex) => _ifVertex(vertex);
/// <inheritdoc/>
public TVisitResult Visit(TLeaf leaf) => _ifLeaf(leaf);
}

View File

@ -5,7 +5,7 @@ namespace DD.Persistence.Repositories;
/// <summary> /// <summary>
/// Репозиторий для работы со схемами наборов данных /// Репозиторий для работы со схемами наборов данных
/// </summary> /// </summary>
public interface IDataSchemeRepository public interface ISchemePropertyRepository
{ {
/// <summary> /// <summary>
/// Добавить схему /// Добавить схему
@ -13,7 +13,7 @@ public interface IDataSchemeRepository
/// <param name="dataSourceSystemDto"></param> /// <param name="dataSourceSystemDto"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token); Task AddRange(DataSchemeDto dataSourceSystemDto, CancellationToken token);
/// <summary> /// <summary>
/// Вычитать схему /// Вычитать схему

View File

@ -1,8 +1,8 @@
using DD.Persistence.Extensions; using DD.Persistence.Extensions;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces; using DD.Persistence.Services.Interfaces;
using System.Text.Json;
namespace DD.Persistence.Services; namespace DD.Persistence.Services;
@ -10,10 +10,10 @@ namespace DD.Persistence.Services;
public class TimestampedValuesService : ITimestampedValuesService public class TimestampedValuesService : ITimestampedValuesService
{ {
private readonly ITimestampedValuesRepository timestampedValuesRepository; private readonly ITimestampedValuesRepository timestampedValuesRepository;
private readonly IDataSchemeRepository dataSchemeRepository; private readonly ISchemePropertyRepository dataSchemeRepository;
/// <inheritdoc/> /// <inheritdoc/>
public TimestampedValuesService(ITimestampedValuesRepository timestampedValuesRepository, IDataSchemeRepository relatedDataRepository) public TimestampedValuesService(ITimestampedValuesRepository timestampedValuesRepository, ISchemePropertyRepository relatedDataRepository)
{ {
this.timestampedValuesRepository = timestampedValuesRepository; this.timestampedValuesRepository = timestampedValuesRepository;
this.dataSchemeRepository = relatedDataRepository; this.dataSchemeRepository = relatedDataRepository;
@ -25,8 +25,7 @@ public class TimestampedValuesService : ITimestampedValuesService
// ToDo: реализовать без foreach // ToDo: реализовать без foreach
foreach (var dto in dtos) foreach (var dto in dtos)
{ {
var keys = dto.Values.Keys.ToArray(); await CreateDataSchemeIfNotExist(discriminatorId, dto, token);
await CreateSystemSpecificationIfNotExist(discriminatorId, keys, token);
} }
var result = await timestampedValuesRepository.AddRange(discriminatorId, dtos, 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 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()) if (!columnNames.IsNullOrEmpty())
{ {
@ -54,9 +53,9 @@ public class TimestampedValuesService : ITimestampedValuesService
{ {
var result = await timestampedValuesRepository.GetFirst(discriminatorId, takeCount, token); var result = await timestampedValuesRepository.GetFirst(discriminatorId, takeCount, token);
var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
.ToDictionary(); .ToDictionary();
var dtos = await Materialize(resultToMaterialize, token); var dtos = await BindingToDataScheme(resultBeforeBinding, token);
return dtos; return dtos;
} }
@ -66,9 +65,9 @@ public class TimestampedValuesService : ITimestampedValuesService
{ {
var result = await timestampedValuesRepository.GetLast(discriminatorId, takeCount, token); var result = await timestampedValuesRepository.GetLast(discriminatorId, takeCount, token);
var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
.ToDictionary(); .ToDictionary();
var dtos = await Materialize(resultToMaterialize, token); var dtos = await BindingToDataScheme(resultBeforeBinding, token);
return dtos; return dtos;
} }
@ -83,9 +82,9 @@ public class TimestampedValuesService : ITimestampedValuesService
{ {
var result = await timestampedValuesRepository.GetResampledData(discriminatorId, beginTimestamp, intervalSec, approxPointsCount, token); 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(); .ToDictionary();
var dtos = await Materialize(resultToMaterialize, token); var dtos = await BindingToDataScheme(resultBeforeBinding, token);
return dtos; return dtos;
} }
@ -95,41 +94,36 @@ public class TimestampedValuesService : ITimestampedValuesService
{ {
var result = await timestampedValuesRepository.GetGtDate(discriminatorId, beginTimestamp, token); var result = await timestampedValuesRepository.GetGtDate(discriminatorId, beginTimestamp, token);
var resultToMaterialize = new[] { KeyValuePair.Create(discriminatorId, result) } var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
.ToDictionary(); .ToDictionary();
var dtos = await Materialize(resultToMaterialize, token); var dtos = await BindingToDataScheme(resultBeforeBinding, token);
return dtos; return dtos;
} }
// ToDo: рефакторинг, переименовать (текущее название не отражает суть)
/// <summary> /// <summary>
/// Преобразовать результат запроса в набор dto /// Преобразовать результат запроса в набор dto
/// </summary> /// </summary>
/// <param name="queryResult"></param> /// <param name="queryResult"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
private async Task<IEnumerable<TimestampedValuesDto>> Materialize(IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> queryResult, CancellationToken token) private async Task<IEnumerable<TimestampedValuesDto>> BindingToDataScheme(IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> queryResult, CancellationToken token)
{ {
IEnumerable<TimestampedValuesDto> result = []; IEnumerable<TimestampedValuesDto> result = [];
foreach (var keyValuePair in queryResult) foreach (var keyValuePair in queryResult)
{ {
var dataScheme = await dataSchemeRepository.Get(keyValuePair.Key, token); var dataScheme = await dataSchemeRepository.Get(keyValuePair.Key, token);
if (dataScheme is null) if (dataScheme is null)
{
continue; 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() var dto = new TimestampedValuesDto()
{ {
Timestamp = tuple.Timestamp.ToUniversalTime(), Timestamp = Timestamp.ToUniversalTime(),
Values = indexedIdentity Values = dataScheme
.ToDictionary(x => x.value, x => tuple.Values[x.index]) .ToDictionary(k => k.PropertyName, v => Values[v.Index])
}; };
result = result.Append(dto); result = result.Append(dto);
@ -140,34 +134,36 @@ public class TimestampedValuesService : ITimestampedValuesService
} }
/// <summary> /// <summary>
/// Создать спецификацию, при отсутствии таковой /// Создать схему данных, при отсутствии таковой
/// </summary> /// </summary>
/// <param name="discriminatorId">Дискриминатор системы</param> /// <param name="discriminatorId">Дискриминатор схемы</param>
/// <param name="fieldNames">Набор наименований полей</param> /// <param name="dto">Набор данных, по образу которого будет создана соответствующая схема</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="InvalidOperationException">Некорректный набор наименований полей</exception> /// <exception cref="InvalidOperationException">Некорректный набор наименований полей</exception>
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); var valuesList = dto.Values.ToList();
if (systemSpecification is null) var properties = valuesList.Select((e, index) => new SchemePropertyDto()
{ {
systemSpecification = new DataSchemeDto() Index = index,
PropertyName = e.Key,
PropertyKind = ((JsonElement)e.Value).ValueKind
});
var dataScheme = await dataSchemeRepository.Get(discriminatorId, token);
if (dataScheme is null)
{ {
DiscriminatorId = discriminatorId, dataScheme = new DataSchemeDto(discriminatorId, properties);
PropNames = fieldNames await dataSchemeRepository.AddRange(dataScheme, token);
};
await dataSchemeRepository.Add(systemSpecification, token);
return; 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()} " + throw new InvalidOperationException($"Для системы {discriminatorId.ToString()} " +
$"характерен набор данных: [{expectedFieldNames}], однако был передан набор: [{actualFieldNames}]"); $"был передан нехарактерный набор данных");
} }
} }
@ -177,7 +173,7 @@ public class TimestampedValuesService : ITimestampedValuesService
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="fieldNames">Поля, которые необходимо оставить</param> /// <param name="fieldNames">Поля, которые необходимо оставить</param>
/// <returns></returns> /// <returns></returns>
private IEnumerable<TimestampedValuesDto> ReduceSetColumnsByNames(IEnumerable<TimestampedValuesDto> dtos, IEnumerable<string> fieldNames) private static IEnumerable<TimestampedValuesDto> ReduceSetColumnsByNames(IEnumerable<TimestampedValuesDto> dtos, IEnumerable<string> fieldNames)
{ {
var result = dtos.Select(dto => var result = dtos.Select(dto =>
{ {