Merge from dev

This commit is contained in:
Оля Бизюкова 2025-02-06 15:37:39 +05:00
commit 979a651328
36 changed files with 308 additions and 364 deletions

View File

@ -25,7 +25,6 @@
<ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.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" />
</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.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

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

View File

@ -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
{
/// <inheritdoc />
@ -67,23 +67,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
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 =>
{
b.Property<Guid>("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<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 =>
{
b.Property<Guid>("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
}
}

View File

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

View File

@ -64,23 +64,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
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 =>
{
b.Property<Guid>("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<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 =>
{
b.Property<Guid>("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
}
}

View File

@ -7,11 +7,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="UuidExtensions" Version="1.2.0" />
</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.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<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)
@ -37,8 +47,8 @@ public static class DependencyInjection
services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>();
services.AddTransient<ITechMessagesRepository, TechMessagesRepository>();
services.AddTransient<IParameterRepository, ParameterRepository>();
services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>();
services.AddTransient<IDataSchemeRepository, DataSchemeCachedRepository>();
services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>();
services.AddTransient<ISchemePropertyRepository, SchemePropertyCachedRepository>();
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")]
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.Reflection;
namespace DD.Persistence.Repository.Extensions;
namespace DD.Persistence.Database.Extensions;
public static class EFExtensionsSortBy
{

View File

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

View File

@ -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;
/// <summary>
/// класс с набором методов, необходимых для фильтрации записей
@ -55,4 +54,4 @@ public static class QueryBuilders
return result;
}
}
}

View File

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

View File

@ -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<IEnumerable<ChangeLogValuesDto>> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token)
{
var date = dateBegin.ToUniversalTime();
var query = this.db.Set<ChangeLog>()
var query = db.Set<ChangeLog>()
.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),
});

View File

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

View File

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

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 System.Text.Json;
namespace DD.Persistence.Repository.Repositories
namespace DD.Persistence.Database.Postgres.Repositories
{
public class SetpointRepository : ISetpointRepository
{

View File

@ -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
{

View File

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

View File

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

View File

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

View File

@ -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<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>
public class DataSchemeDto
public class DataSchemeDto : IEnumerable<SchemePropertyDto>, IEquatable<IEnumerable<SchemePropertyDto>>
{
/// <summary>
/// Дискриминатор
@ -11,7 +13,30 @@ public class DataSchemeDto
public Guid DiscriminatorId { get; set; }
/// <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>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
<ProjectReference Include="..\DD.Persistence.Repository\DD.Persistence.Repository.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -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<RepositoryTestFixture>

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

@ -2,12 +2,13 @@
using DD.Persistence.Repositories;
using DD.Persistence.Services;
using NSubstitute;
using System.Text.Json;
namespace DD.Persistence.Repository.Test;
public class TimestampedValuesServiceShould
{
private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>();
private readonly IDataSchemeRepository dataSchemeRepository = Substitute.For<IDataSchemeRepository>();
private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For<ISchemePropertyRepository>();
private TimestampedValuesService timestampedValuesService;
public TimestampedValuesServiceShould()
@ -45,10 +46,10 @@ public class TimestampedValuesServiceShould
{
var values = new Dictionary<string, object>()
{
{ "A", i },
{ "B", i * 1.1 },
{ "C", $"Any{i}" },
{ "D", DateTimeOffset.Now },
{ "A", GetJsonFromObject(i) },
{ "B", GetJsonFromObject(i * 1.1) },
{ "C", GetJsonFromObject($"Any{i}") },
{ "D", GetJsonFromObject(DateTimeOffset.Now) }
};
yield return new TimestampedValuesDto()
@ -58,4 +59,11 @@ public class TimestampedValuesServiceShould
};
}
}
private static JsonElement GetJsonFromObject(object value)
{
var jsonString = JsonSerializer.Serialize(value);
var doc = JsonDocument.Parse(jsonString);
return doc.RootElement;
}
}

View File

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

View File

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

View File

@ -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;
/// <inheritdoc/>
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<IEnumerable<TimestampedValuesDto>> 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: рефакторинг, переименовать (текущее название не отражает суть)
/// <summary>
/// Преобразовать результат запроса в набор dto
/// </summary>
/// <param name="queryResult"></param>
/// <param name="token"></param>
/// <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 = [];
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
}
/// <summary>
/// Создать спецификацию, при отсутствии таковой
/// Создать схему данных, при отсутствии таковой
/// </summary>
/// <param name="discriminatorId">Дискриминатор системы</param>
/// <param name="fieldNames">Набор наименований полей</param>
/// <param name="discriminatorId">Дискриминатор схемы</param>
/// <param name="dto">Набор данных, по образу которого будет создана соответствующая схема</param>
/// <param name="token"></param>
/// <returns></returns>
/// <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);
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
/// <param name="dtos"></param>
/// <param name="fieldNames">Поля, которые необходимо оставить</param>
/// <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 =>
{