Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
52f7bca9f1 | |||
2ef3efed33 | |||
4739a043f4 | |||
49cc9d6e39 | |||
c997bfe9a4 | |||
fca2ccb8aa | |||
bfb18cab8a | |||
c247489477 | |||
11eea8db67 | |||
43289d939b | |||
|
1f3df26e9a | ||
|
e0f0d9fdd0 | ||
|
ed6df68f7d | ||
|
2ab8101258 | ||
d6038ca579 | |||
|
86bf78f31f | ||
5ce5fa139b | |||
272e164482 | |||
aad8c57511 | |||
|
8fa661b605 | ||
|
979a651328 | ||
baf04ae3a6 | |||
598056c6d7 | |||
c5da82c210 | |||
bcb9749b1a | |||
44bc335151 | |||
7d973ba859 | |||
5abfcc0d50 | |||
0c27a0148d | |||
a340fbe23b | |||
9ca49cb1b5 | |||
431c7278cb | |||
4513de06fa | |||
63e6816e35 | |||
560073ed07 | |||
e3c1d02650 | |||
4b5477207d | |||
87264fd8db | |||
1d0921c3e8 | |||
f955aab218 | |||
cb0508afe9 | |||
287d6e7111 |
25
.docker/appsettings.json
Normal file
25
.docker/appsettings.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=db:5432;Database=persistence;Username=postgres;Password=postgres;Persist Security Info=True"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"NeedUseKeyCloak": false,
|
||||||
|
"KeyCloakAuthentication": {
|
||||||
|
"Audience": "account",
|
||||||
|
"Host": "http://192.168.0.10:8321/realms/Persistence"
|
||||||
|
},
|
||||||
|
"AuthUser": {
|
||||||
|
"username": "myuser",
|
||||||
|
"password": 12345,
|
||||||
|
"clientId": "webapi",
|
||||||
|
"grantType": "password",
|
||||||
|
"http://schemas.xmlsoap.org/ws/2005/05/identity /claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6"
|
||||||
|
},
|
||||||
|
"ClientUrl": "http://localhost:5000/"
|
||||||
|
}
|
31
.docker/compose.yaml
Normal file
31
.docker/compose.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
networks:
|
||||||
|
persistence:
|
||||||
|
external: false
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: timescale/timescaledb:latest-pg16
|
||||||
|
container_name: some-timescaledb-16
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- POSTGRES_PASSWORD=postgres
|
||||||
|
networks:
|
||||||
|
- persistence
|
||||||
|
ports:
|
||||||
|
- "5462:5432"
|
||||||
|
volumes:
|
||||||
|
- ./db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
image: git.ddrilling.ru/ddrilling/persistence:latest
|
||||||
|
container_name: persistence
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
networks:
|
||||||
|
- persistence
|
||||||
|
ports:
|
||||||
|
- "1111:8080"
|
||||||
|
volumes:
|
||||||
|
- ./appsettings.json:/app/appsettings.json
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using DD.Persistence.Models;
|
using DD.Persistence.Models;
|
||||||
using DD.Persistence.Models.Requests;
|
using DD.Persistence.Models.Requests;
|
||||||
@ -174,4 +174,87 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
|
|||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Метод, который возвращает статистику по количеству изменений в разрезе дней
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("statistics")]
|
||||||
|
public async Task<ActionResult<IEnumerable<StatisticsChangeLogDto>>> GetStatisticsCountAsync([FromQuery] ChangeLogRequest request, CancellationToken token)
|
||||||
|
{
|
||||||
|
var result = new List<StatisticsChangeLogDto>() {
|
||||||
|
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-60), ChangesCount = 10},
|
||||||
|
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-50), ChangesCount = 2},
|
||||||
|
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-25), ChangesCount = 560},
|
||||||
|
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-2), ChangesCount = 78},
|
||||||
|
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-1), ChangesCount = 39},
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Метод, который возвращает историю изменений в разрезе дней
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("history")]
|
||||||
|
public async Task<ActionResult<IEnumerable<HistoryChangeLogDto>>> HistoryChangeLogAsync([FromQuery] ChangeLogRequest request, CancellationToken token)
|
||||||
|
{
|
||||||
|
var userId = Guid.CreateVersion7();
|
||||||
|
var changeLogItemCurrentId = Guid.CreateVersion7();
|
||||||
|
var changeLogItemCreation = DateTimeOffset.UtcNow;
|
||||||
|
var changeLogItems = new List<ChangeLogDto>()
|
||||||
|
{
|
||||||
|
new ChangeLogDto()
|
||||||
|
{
|
||||||
|
Id = changeLogItemCurrentId,
|
||||||
|
Creation = changeLogItemCreation,
|
||||||
|
IdAuthor = userId,
|
||||||
|
IdEditor = userId,
|
||||||
|
Obsolete = null,
|
||||||
|
Value = new ChangeLogValuesDto(){
|
||||||
|
Id = Guid.CreateVersion7(),
|
||||||
|
Value = new Dictionary<string, object>() {
|
||||||
|
["1"] = new { id = 1, caption = "Изменение 1 (c правкой)" },
|
||||||
|
["2"] = new { id = 2, caption = "Изменение 2 (с правкой)" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ChangeLogDto()
|
||||||
|
{
|
||||||
|
Id = Guid.CreateVersion7(),
|
||||||
|
Creation = DateTimeOffset.UtcNow.AddDays(-10),
|
||||||
|
IdAuthor = userId,
|
||||||
|
IdEditor = userId,
|
||||||
|
IdNext = changeLogItemCurrentId,
|
||||||
|
Obsolete = DateTimeOffset.UtcNow.AddDays(-5),
|
||||||
|
Value = new ChangeLogValuesDto(){
|
||||||
|
Id = Guid.CreateVersion7(),
|
||||||
|
Value = new Dictionary<string, object>() {
|
||||||
|
["1"] = new { id = 1, caption = "Изменение 1" },
|
||||||
|
["2"] = new { id = 2, caption = "Изменение 2" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var result = new List<HistoryChangeLogDto>() {
|
||||||
|
new() {
|
||||||
|
Comment = "Петров И. Ю. попросил внести изменения",
|
||||||
|
DateTime = changeLogItemCreation,
|
||||||
|
DiscriminatorId = Guid.CreateVersion7(),
|
||||||
|
User = new UserDto()
|
||||||
|
{
|
||||||
|
Id = userId,
|
||||||
|
DisplayName = "Иванов И. И"
|
||||||
|
},
|
||||||
|
ChangeLogItems = changeLogItems
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -21,5 +21,5 @@
|
|||||||
"grantType": "password",
|
"grantType": "password",
|
||||||
"http://schemas.xmlsoap.org/ws/2005/05/identity /claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6"
|
"http://schemas.xmlsoap.org/ws/2005/05/identity /claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6"
|
||||||
},
|
},
|
||||||
"PeristenceClientUrl": "http://localhost:5000/"
|
"ClientUrl": "http://localhost:5000/"
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace DD.Persistence.Client.CustomExceptions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ошибка, которая выбрасывается в случае отсутствия необходимого свойства в appsettings.json
|
|
||||||
/// </summary>
|
|
||||||
public class AppSettingsPropertyNotFoundException : Exception
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message"></param>
|
|
||||||
public AppSettingsPropertyNotFoundException(string message)
|
|
||||||
: base(message) { }
|
|
||||||
}
|
|
@ -52,6 +52,7 @@
|
|||||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
|
||||||
<PackageReference Include="Refit" Version="8.0.0" />
|
<PackageReference Include="Refit" Version="8.0.0" />
|
||||||
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
|
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
|
||||||
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using DD.Persistence.Client.Clients.Interfaces.Refit;
|
using DD.Persistence.Client.Clients.Interfaces.Refit;
|
||||||
using DD.Persistence.Client.CustomExceptions;
|
using DD.Persistence.Client.Helpers;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Refit;
|
using Refit;
|
||||||
|
using System.Configuration;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace DD.Persistence.Client;
|
namespace DD.Persistence.Client;
|
||||||
@ -22,10 +23,10 @@ public class RefitClientFactory<T> : IRefitClientFactory<T> where T : IRefitClie
|
|||||||
//this.client = factory.CreateClient();
|
//this.client = factory.CreateClient();
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
var baseUrl = configuration.GetSection("PeristenceClientUrl").Get<string>();
|
var baseUrl = configuration.GetSection("ClientUrl").Get<string>();
|
||||||
if (String.IsNullOrEmpty(baseUrl))
|
if (String.IsNullOrEmpty(baseUrl))
|
||||||
{
|
{
|
||||||
var exception = new AppSettingsPropertyNotFoundException("В настройках конфигурации не указан адрес Persistence сервиса.");
|
var exception = new SettingsPropertyNotFoundException("В настройках конфигурации не указан адрес Persistence сервиса.");
|
||||||
|
|
||||||
logger.LogError(exception.Message);
|
logger.LogError(exception.Message);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
@ -7,11 +7,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
|
||||||
|
<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>
|
||||||
|
@ -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)
|
||||||
@ -37,8 +47,8 @@ public static class DependencyInjection
|
|||||||
services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>();
|
services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>();
|
||||||
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;
|
||||||
}
|
}
|
@ -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; } = [];
|
|
||||||
}
|
|
23
DD.Persistence.Database/Entity/SchemeProperty.cs
Normal file
23
DD.Persistence.Database/Entity/SchemeProperty.cs
Normal 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; }
|
||||||
|
}
|
@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
|
|||||||
|
|
||||||
[Table("timestamped_values")]
|
[Table("timestamped_values")]
|
||||||
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
|
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
|
||||||
public class TimestampedValues : ITimestampedItem
|
public class TimestampedValues : ITimestampedItem, IValuesItem
|
||||||
{
|
{
|
||||||
[Comment("Временная отметка"), Key]
|
[Comment("Временная отметка"), Key]
|
||||||
public DateTimeOffset Timestamp { get; set; }
|
public DateTimeOffset Timestamp { get; set; }
|
||||||
@ -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; }
|
|
||||||
}
|
}
|
||||||
|
14
DD.Persistence.Database/EntityAbstractions/IValuesItem.cs
Normal file
14
DD.Persistence.Database/EntityAbstractions/IValuesItem.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using DD.Persistence.Database.Entity;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сущность с данными, принадлежащими к определенной схеме
|
||||||
|
/// </summary>
|
||||||
|
public interface IValuesItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Значения
|
||||||
|
/// </summary>
|
||||||
|
object[] Values { get; set; }
|
||||||
|
}
|
@ -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
|
||||||
{
|
{
|
@ -0,0 +1,38 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.Helpers;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Postgres.Extensions;
|
||||||
|
|
||||||
|
public static class SpecificationExtensions
|
||||||
|
{
|
||||||
|
public static Expression<Func<T, bool>>? Or<T>(this ISpecification<T> spec, ISpecification<T> otherSpec)
|
||||||
|
{
|
||||||
|
var parameter = Expression.Parameter(typeof(T), "x");
|
||||||
|
|
||||||
|
var exprSpec1 = CombineWhereExpressions(spec.WhereExpressions, parameter);
|
||||||
|
var exprSpec2 = CombineWhereExpressions(otherSpec.WhereExpressions, parameter);
|
||||||
|
|
||||||
|
Expression? orExpression = exprSpec1 is not null && exprSpec2 is not null
|
||||||
|
? Expression.OrElse(exprSpec1, exprSpec2)
|
||||||
|
: exprSpec1 ?? exprSpec2;
|
||||||
|
|
||||||
|
if (orExpression is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var lambdaExpr = Expression.Lambda<Func<T, bool>>(orExpression, parameter);
|
||||||
|
|
||||||
|
return lambdaExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Expression? CombineWhereExpressions<T>(IEnumerable<WhereExpressionInfo<T>> whereExpressions, ParameterExpression parameter)
|
||||||
|
{
|
||||||
|
Expression? newExpr = null;
|
||||||
|
foreach (var where in whereExpressions)
|
||||||
|
{
|
||||||
|
var expr = ParameterReplacerVisitor.Replace(where.Filter.Body, where.Filter.Parameters[0], parameter);
|
||||||
|
newExpr = newExpr is null ? expr : Expression.AndAlso(newExpr, expr);
|
||||||
|
}
|
||||||
|
return newExpr;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
namespace DD.Persistence.Repository;
|
namespace DD.Persistence.Database.Postgres.Helpers;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Цикличный массив
|
/// Цикличный массив
|
||||||
/// </summary>
|
/// </summary>
|
106
DD.Persistence.Database/Helpers/FilterBuilder.cs
Normal file
106
DD.Persistence.Database/Helpers/FilterBuilder.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
using DD.Persistence.Database.Postgres.Extensions;
|
||||||
|
using DD.Persistence.Database.Specifications;
|
||||||
|
using DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
using DD.Persistence.Filter.Models;
|
||||||
|
using DD.Persistence.Filter.Models.Abstractions;
|
||||||
|
using DD.Persistence.Filter.Models.Enumerations;
|
||||||
|
using DD.Persistence.Filter.Visitors;
|
||||||
|
using DD.Persistence.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Postgres.Helpers;
|
||||||
|
public static class FilterBuilder
|
||||||
|
{
|
||||||
|
public static ISpecification<TEntity>? BuildFilter<TEntity>(this DataSchemeDto dataSchemeDto, TNode root)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var result = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(root);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpecification<TEntity>? BuildSpecificationByNextNode<TEntity>(this DataSchemeDto dataSchemeDto, TNode node)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var visitor = new NodeVisitor<ISpecification<TEntity>?>(
|
||||||
|
dataSchemeDto.VertexProcessing<TEntity>,
|
||||||
|
dataSchemeDto.LeafProcessing<TEntity>
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = node.AcceptVisitor(visitor);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpecification<TEntity>? VertexProcessing<TEntity>(this DataSchemeDto dataSchemeDto, TVertex vertex)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var leftSpecification = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(vertex.Left);
|
||||||
|
var rigthSpecification = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(vertex.Rigth);
|
||||||
|
if (leftSpecification is null)
|
||||||
|
return rigthSpecification;
|
||||||
|
if (rigthSpecification is null)
|
||||||
|
return leftSpecification;
|
||||||
|
|
||||||
|
ISpecification<TEntity>? result = null;
|
||||||
|
switch (vertex.Operation)
|
||||||
|
{
|
||||||
|
case OperationEnum.And:
|
||||||
|
result = new AndSpecification<TEntity>(leftSpecification, rigthSpecification);
|
||||||
|
break;
|
||||||
|
case OperationEnum.Or:
|
||||||
|
result = new OrSpecification<TEntity>(leftSpecification, rigthSpecification);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpecification<TEntity>? LeafProcessing<TEntity>(this DataSchemeDto dataSchemeDto, TLeaf leaf)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var schemeProperty = dataSchemeDto.FirstOrDefault(e => e.PropertyName.Equals(leaf.PropName));
|
||||||
|
if (schemeProperty is null)
|
||||||
|
throw new ArgumentException($"Свойство {leaf.PropName} не найдено в схеме данных");
|
||||||
|
|
||||||
|
ISpecification<TEntity>? result = null;
|
||||||
|
switch (schemeProperty.PropertyKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.String:
|
||||||
|
var stringValue = Convert.ToString(leaf.Value);
|
||||||
|
var stringSpecifications = StringSpecifications<TEntity>();
|
||||||
|
result = stringSpecifications[leaf.Operation](schemeProperty.Index, stringValue);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
var doubleValue = Convert.ToDouble(leaf.Value);
|
||||||
|
var doubleSpecifications = DoubleSpecifications<TEntity>();
|
||||||
|
result = doubleSpecifications[leaf.Operation](schemeProperty.Index, doubleValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<OperationEnum, Func<int, string?, ISpecification<TEntity>>> StringSpecifications<TEntity>()
|
||||||
|
where TEntity : IValuesItem => new()
|
||||||
|
{
|
||||||
|
{ OperationEnum.Equal, (int index, string? value) => new ValueEqaulSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.NotEqual, (int index, string? value) => new ValueNotEqualSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.Greate, (int index, string? value) => new ValueGreateSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.GreateOrEqual, (int index, string? value) => new ValueGreateOrEqualSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.Less, (int index, string? value) => new ValueLessSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.LessOrEqual, (int index, string? value) => new ValueLessOrEqualSpecification<TEntity>(index, value) }
|
||||||
|
};
|
||||||
|
private static Dictionary<OperationEnum, Func<int, double?, ISpecification<TEntity>>> DoubleSpecifications<TEntity>()
|
||||||
|
where TEntity : IValuesItem => new()
|
||||||
|
{
|
||||||
|
{ OperationEnum.Equal, (int index, double? value) => new ValueEqaulSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.NotEqual, (int index, double? value) => new ValueNotEqualSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.Greate, (int index, double? value) => new ValueGreateSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.GreateOrEqual, (int index, double? value) => new ValueGreateOrEqualSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.Less, (int index, double? value) => new ValueLessSpecification<TEntity>(index, value) },
|
||||||
|
{ OperationEnum.LessOrEqual, (int index, double? value) => new ValueLessOrEqualSpecification<TEntity>(index, value) }
|
||||||
|
};
|
||||||
|
}
|
20
DD.Persistence.Database/Helpers/ParameterReplacerVisitor.cs
Normal file
20
DD.Persistence.Database/Helpers/ParameterReplacerVisitor.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Helpers;
|
||||||
|
public class ParameterReplacerVisitor : ExpressionVisitor
|
||||||
|
{
|
||||||
|
private readonly Expression _newExpression;
|
||||||
|
private readonly ParameterExpression _oldParameter;
|
||||||
|
|
||||||
|
private ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression)
|
||||||
|
{
|
||||||
|
_oldParameter = oldParameter;
|
||||||
|
_newExpression = newExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Expression Replace(Expression expression, ParameterExpression oldParameter, Expression newExpression)
|
||||||
|
=> new ParameterReplacerVisitor(oldParameter, newExpression).Visit(expression);
|
||||||
|
|
||||||
|
protected override Expression VisitParameter(ParameterExpression p)
|
||||||
|
=> p == _oldParameter ? _newExpression : p;
|
||||||
|
}
|
@ -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>
|
||||||
/// класс с набором методов, необходимых для фильтрации записей
|
/// класс с набором методов, необходимых для фильтрации записей
|
||||||
@ -24,7 +23,6 @@ public static class QueryBuilders
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static async Task<PaginationContainer<TDto>> ApplyPagination<TEntity, TDto>(
|
public static async Task<PaginationContainer<TDto>> ApplyPagination<TEntity, TDto>(
|
||||||
this IQueryable<TEntity> query,
|
this IQueryable<TEntity> query,
|
||||||
PaginationRequest request,
|
PaginationRequest request,
|
@ -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();
|
||||||
|
@ -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),
|
||||||
});
|
});
|
@ -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;
|
@ -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;
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
{
|
{
|
@ -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
|
||||||
{
|
{
|
@ -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;
|
@ -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";
|
@ -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);
|
||||||
}
|
}
|
22
DD.Persistence.Database/Specifications/AndSpecification.cs
Normal file
22
DD.Persistence.Database/Specifications/AndSpecification.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications;
|
||||||
|
public class AndSpecification<TEntity> : Specification<TEntity>
|
||||||
|
{
|
||||||
|
public AndSpecification(ISpecification<TEntity> first, ISpecification<TEntity> second)
|
||||||
|
{
|
||||||
|
if (first is null || second is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplyCriteria(first);
|
||||||
|
ApplyCriteria(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyCriteria(ISpecification<TEntity> specification)
|
||||||
|
{
|
||||||
|
foreach (var criteria in specification.WhereExpressions)
|
||||||
|
{
|
||||||
|
Query.Where(criteria.Filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
DD.Persistence.Database/Specifications/OrSpecification.cs
Normal file
15
DD.Persistence.Database/Specifications/OrSpecification.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.Postgres.Extensions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications;
|
||||||
|
public class OrSpecification<TEntity> : Specification<TEntity>
|
||||||
|
{
|
||||||
|
public OrSpecification(ISpecification<TEntity> first, ISpecification<TEntity> second)
|
||||||
|
{
|
||||||
|
var orExpression = first.Or(second);
|
||||||
|
if (orExpression == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Query.Where(orExpression);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация эквивалентности значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueEqaulSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueEqaulSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToString(e.Values[index]) == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEqaulSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) == value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "больше либо равно" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueGreateOrEqualSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueGreateOrEqualSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueGreateOrEqualSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) >= value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "больше" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueGreateSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueGreateSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueGreateSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) > value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "меньше либо равно" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueLessOrEqualSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueLessOrEqualSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueLessOrEqualSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) <= value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "меньше" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueLessSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueLessSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueLessSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) < value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация неравенства значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueNotEqualSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueNotEqualSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToString(e.Values[index]) != value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueNotEqualSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) != value);
|
||||||
|
}
|
||||||
|
}
|
@ -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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
32
DD.Persistence.Models/HistoryChangeLogDto.cs
Normal file
32
DD.Persistence.Models/HistoryChangeLogDto.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
namespace DD.Persistence.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Модель, необходимая для отображения истории по журналу изменений
|
||||||
|
/// </summary>
|
||||||
|
public class HistoryChangeLogDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Дата и время изменений
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset DateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Пользователь, совершивший изменение данных
|
||||||
|
/// </summary>
|
||||||
|
public required UserDto User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проект, с которым связаны изменения
|
||||||
|
/// </summary>
|
||||||
|
public Guid DiscriminatorId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Список изменений
|
||||||
|
/// </summary>
|
||||||
|
public required IEnumerable<ChangeLogDto> ChangeLogItems { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Комментарий к изменению
|
||||||
|
/// </summary>
|
||||||
|
public required string Comment { get; set; }
|
||||||
|
}
|
17
DD.Persistence.Models/Requests/ChangeLogRequest.cs
Normal file
17
DD.Persistence.Models/Requests/ChangeLogRequest.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace DD.Persistence.Models.Requests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Запрос, используемый для получения данных по журналу операций
|
||||||
|
/// </summary>
|
||||||
|
public class ChangeLogRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Дискриминатор задачи
|
||||||
|
/// </summary>
|
||||||
|
public Guid DiscriminatorId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Пользователь
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
}
|
30
DD.Persistence.Models/SchemePropertyDto.cs
Normal file
30
DD.Persistence.Models/SchemePropertyDto.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
23
DD.Persistence.Models/StatisticsChangeLogDto.cs
Normal file
23
DD.Persistence.Models/StatisticsChangeLogDto.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Модель, необходимая для отображения статистики по журналу изменений
|
||||||
|
/// </summary>
|
||||||
|
public class StatisticsChangeLogDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Дата и время изменений
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset DateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Количество изменений
|
||||||
|
/// </summary>
|
||||||
|
public int ChangesCount { get; set; }
|
||||||
|
}
|
23
DD.Persistence.Models/UserDto.cs
Normal file
23
DD.Persistence.Models/UserDto.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Класс, описывающий пользователя
|
||||||
|
/// </summary>
|
||||||
|
public class UserDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор пользователя
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Имя пользователя для отображения
|
||||||
|
/// </summary>
|
||||||
|
public required string DisplayName { get; set; }
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
@ -16,8 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
|
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
|
||||||
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
281
DD.Persistence.Test/FilterBuilderShould.cs
Normal file
281
DD.Persistence.Test/FilterBuilderShould.cs
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
using Ardalis.Specification.EntityFrameworkCore;
|
||||||
|
using DD.Persistence.Database.Entity;
|
||||||
|
using DD.Persistence.Filter.Models;
|
||||||
|
using DD.Persistence.Filter.Models.Enumerations;
|
||||||
|
using DD.Persistence.Models;
|
||||||
|
using DD.Persistence.Database.Postgres.Helpers;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Test;
|
||||||
|
|
||||||
|
/// ToDo: переписать под Theory
|
||||||
|
public class FilterBuilderShould
|
||||||
|
{
|
||||||
|
private readonly SpecificationEvaluator SpecificationEvaluator;
|
||||||
|
public FilterBuilderShould()
|
||||||
|
{
|
||||||
|
this.SpecificationEvaluator = new SpecificationEvaluator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestFilterBuilding()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var discriminatorId = Guid.NewGuid();
|
||||||
|
var dataSchemeProperties = new SchemePropertyDto[]
|
||||||
|
{
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
PropertyName = "A",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 1,
|
||||||
|
PropertyName = "B",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 2,
|
||||||
|
PropertyName = "C",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
|
||||||
|
var filterDate = DateTime.Now.AddMinutes(-1);
|
||||||
|
var root = new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TLeaf(OperationEnum.Greate, "A", filterDate),
|
||||||
|
new TLeaf(OperationEnum.Less, "B", 2.22)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "C", "IsEqualText")
|
||||||
|
);
|
||||||
|
var queryableData = new[]
|
||||||
|
{
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(-1), 200, "IsEqualText" } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(1), 2.21, "IsNotEqualText" } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(-1), 2.22, "IsNotEqualText" } // false
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(-1), 2.21, "IsNotEqualText" } // false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(specification);
|
||||||
|
|
||||||
|
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
|
||||||
|
var result = query.ToList();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
|
||||||
|
var expectedCount = 2;
|
||||||
|
var actualCount = result.Count();
|
||||||
|
Assert.Equal(expectedCount, actualCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestFilterOperations()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var discriminatorId = Guid.NewGuid();
|
||||||
|
var dataSchemeProperties = new SchemePropertyDto[]
|
||||||
|
{
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
PropertyName = "A",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
|
||||||
|
var root = new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TLeaf(OperationEnum.Less, "A", 2),
|
||||||
|
new TLeaf(OperationEnum.LessOrEqual, "A", 1.99)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.GreateOrEqual, "A", 1.97)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Greate, "A", 1.96)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.NotEqual, "A", 1.98)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "A", 1)
|
||||||
|
);
|
||||||
|
var queryableData = new[]
|
||||||
|
{
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { 1 } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||||
|
Values = new object[] { 1.96 } // false
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||||
|
Values = new object[] { 1.97 } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||||
|
Values = new object[] { 1.98 } // false
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-5),
|
||||||
|
Values = new object[] { 1.99 } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-6),
|
||||||
|
Values = new object[] { 2 } // false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(specification);
|
||||||
|
|
||||||
|
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
|
||||||
|
var result = query.ToList();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
|
||||||
|
var expectedCount = 3;
|
||||||
|
var actualCount = result.Count();
|
||||||
|
Assert.Equal(expectedCount, actualCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestFilterValues()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var discriminatorId = Guid.NewGuid();
|
||||||
|
var filterDate = DateTimeOffset.Now;
|
||||||
|
var dataSchemeProperties = new SchemePropertyDto[]
|
||||||
|
{
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
PropertyName = "A",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 1,
|
||||||
|
PropertyName = "B",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 2,
|
||||||
|
PropertyName = "C",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 3,
|
||||||
|
PropertyName = "D",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
|
||||||
|
|
||||||
|
var root = new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TLeaf(OperationEnum.Equal, "A", 1),
|
||||||
|
new TLeaf(OperationEnum.Equal, "B", 1.11)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "C", "IsEqualText")
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "D", filterDate)
|
||||||
|
);
|
||||||
|
var queryableData = new[]
|
||||||
|
{
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { 1, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||||
|
Values = new object[] { 2, 1.11, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||||
|
Values = new object[] { 2, 2.22, "IsEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||||
|
Values = new object[] { 2, 2.22, "IsNotEqualText", filterDate } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { 2, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(specification);
|
||||||
|
|
||||||
|
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
|
||||||
|
var result = query.ToList();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
|
||||||
|
var expectedCount = 4;
|
||||||
|
var actualCount = result.Count();
|
||||||
|
Assert.Equal(expectedCount, actualCount);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
using DD.Persistence.Models;
|
using DD.Persistence.Models;
|
||||||
using DD.Persistence.Repositories;
|
using DD.Persistence.Repositories;
|
||||||
using DD.Persistence.Services;
|
using DD.Persistence.Services;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
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 TimestampedValuesService timestampedValuesService;
|
||||||
|
|
||||||
public TimestampedValuesServiceShould()
|
public TimestampedValuesServiceShould()
|
||||||
@ -40,15 +41,14 @@ 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>()
|
||||||
{
|
{
|
||||||
{ "A", i },
|
{ "A", GetJsonFromObject(i) },
|
||||||
{ "B", i * 1.1 },
|
{ "B", GetJsonFromObject(i * 1.1) },
|
||||||
{ "C", $"Any{i}" },
|
{ "C", GetJsonFromObject($"Any{i}") },
|
||||||
{ "D", DateTimeOffset.Now },
|
{ "D", GetJsonFromObject(DateTimeOffset.Now) }
|
||||||
};
|
};
|
||||||
|
|
||||||
yield return new TimestampedValuesDto()
|
yield return new TimestampedValuesDto()
|
||||||
@ -58,4 +58,11 @@ public class TimestampedValuesServiceShould
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JsonElement GetJsonFromObject(object value)
|
||||||
|
{
|
||||||
|
var jsonString = JsonSerializer.Serialize(value);
|
||||||
|
var doc = JsonDocument.Parse(jsonString);
|
||||||
|
return doc.RootElement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
107
DD.Persistence.Test/TreeBuilderTest.cs
Normal file
107
DD.Persistence.Test/TreeBuilderTest.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
22
DD.Persistence/Filter/Models/Abstractions/INodeVisitor.cs
Normal file
22
DD.Persistence/Filter/Models/Abstractions/INodeVisitor.cs
Normal 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);
|
||||||
|
}
|
28
DD.Persistence/Filter/Models/Abstractions/TNode.cs
Normal file
28
DD.Persistence/Filter/Models/Abstractions/TNode.cs
Normal 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);
|
||||||
|
}
|
47
DD.Persistence/Filter/Models/Enumerations/OperationEnum.cs
Normal file
47
DD.Persistence/Filter/Models/Enumerations/OperationEnum.cs
Normal 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
|
||||||
|
}
|
33
DD.Persistence/Filter/Models/TLeaf.cs
Normal file
33
DD.Persistence/Filter/Models/TLeaf.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
33
DD.Persistence/Filter/Models/TVertex.cs
Normal file
33
DD.Persistence/Filter/Models/TVertex.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
54
DD.Persistence/Filter/TreeBuilder/FilterTreeBuilder.cs
Normal file
54
DD.Persistence/Filter/TreeBuilder/FilterTreeBuilder.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
30
DD.Persistence/Filter/TreeBuilder/InterpreterContext.cs
Normal file
30
DD.Persistence/Filter/TreeBuilder/InterpreterContext.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
24
DD.Persistence/Filter/Visitors/NodeVisitor.cs
Normal file
24
DD.Persistence/Filter/Visitors/NodeVisitor.cs
Normal 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);
|
||||||
|
}
|
@ -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>
|
||||||
/// Вычитать схему
|
/// Вычитать схему
|
@ -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,
|
||||||
DiscriminatorId = discriminatorId,
|
PropertyKind = ((JsonElement)e.Value).ValueKind
|
||||||
PropNames = fieldNames
|
});
|
||||||
};
|
|
||||||
await dataSchemeRepository.Add(systemSpecification, token);
|
var dataScheme = await dataSchemeRepository.Get(discriminatorId, token);
|
||||||
|
if (dataScheme is null)
|
||||||
|
{
|
||||||
|
dataScheme = new DataSchemeDto(discriminatorId, properties);
|
||||||
|
await dataSchemeRepository.AddRange(dataScheme, 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 =>
|
||||||
{
|
{
|
||||||
|
@ -1 +1,10 @@
|
|||||||
# Persistence
|
# Persistence
|
||||||
|
## Инструкция по развертыванию persistence в docker
|
||||||
|
1. Необходимо скопировать себе локально папку **.docker**, которая находится внутри проекта **persistence**
|
||||||
|
|
||||||
|
2. Авторизоваться в gitea-registry при помощи командры: `docker login -u пользователь -p пароль https://git.ddrilling.ru`
|
||||||
|
|
||||||
|
3. Из папки **.docker** запустить команду:
|
||||||
|
`docker-compose up`
|
||||||
|
|
||||||
|
4. При успешном старте persistence необходимо откорректировать ссылку в браузере: `[host]:[port]/swagger/index.html`
|
Loading…
Reference in New Issue
Block a user