Merge branch 'master' into TimestampedValuesGet

This commit is contained in:
Roman Efremov 2025-01-27 09:17:17 +05:00
commit 0dcaca3226
23 changed files with 209 additions and 103 deletions

View File

@ -1,5 +1,5 @@
using DD.Persistence.Database.Model;
using DD.Persistence.Database.Postgres;
using DD.Persistence.Database.Postgres.Extensions;
using DD.Persistence.Repository;
namespace DD.Persistence.API;
@ -59,7 +59,6 @@ public class Startup
var context = provider.GetRequiredService<PersistencePostgresContext>();
context.Database.EnsureCreatedAndMigrated();
context.Database.AddPartitioning();
}
}

View File

@ -1,24 +1,50 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
ARG PUBLISHERPASSWORD
# 1. Настраиваем рабочую директорию
WORKDIR /src
# 2. Добавляем приватный NuGet-репозиторий
#RUN dotnet nuget add source \
#--name gitea \
#--username publisher \
#--password $PUBLISHERPASSWORD \
#--store-password-in-clear-text \
#https://git.ddrilling.ru/api/packages/DDrilling/nuget/index.json
# 3. Копируем csproj-файлы и восстанавливаем пакеты
COPY ["DD.Persistence.App/DD.Persistence.App.csproj", "DD.Persistence.App/"]
COPY ["DD.Persistence.API/DD.Persistence.API.csproj", "DD.Persistence.API/"]
COPY ["DD.Persistence/DD.Persistence.csproj", "DD.Persistence/"]
COPY ["DD.Persistence.Database/DD.Persistence.Database.csproj", "DD.Persistence.Database/"]
COPY ["DD.Persistence.Database.Postgres/DD.Persistence.Database.Postgres.csproj", "DD.Persistence.Database.Postgres/"]
COPY ["DD.Persistence.Models/DD.Persistence.Models.csproj", "DD.Persistence.Models/"]
COPY ["DD.Persistence.Repository/DD.Persistence.Repository.csproj", "DD.Persistence.Repository/"]
RUN dotnet restore "./DD.Persistence.App/DD.Persistence.App.csproj"
# 4. Копируем *всё* оставшееся (код и т. п.)
COPY . .
# 5. Собираем
WORKDIR "/src/DD.Persistence.App"
RUN dotnet build "./DD.Persistence.App.csproj" -c $BUILD_CONFIGURATION -o /app/build
# ---------- Публикация приложения ----------
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./DD.Persistence.App.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# ---------- Финальный образ с рантаймом ----------
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

View File

@ -2,7 +2,7 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using System.Diagnostics;
namespace DD.Persistence.Database.Postgres;
namespace DD.Persistence.Database.Postgres.Extensions;
public static class EFExtensionsInitialization
{
public static void EnsureCreatedAndMigrated(this DatabaseFacade db)

View File

@ -0,0 +1,44 @@
using DD.Persistence.Database.Entity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
namespace DD.Persistence.Database.Postgres.Extensions;
public static class EFExtensionsPartitioning
{
public static void AddPartitioning(this DatabaseFacade db)
{
db.CreateTimescaledbExtension();
db.AddParameterDataPartitioning();
}
private static void CreateTimescaledbExtension(this DatabaseFacade db)
{
var sqlString = $"CREATE EXTENSION IF NOT EXISTS timescaledb;";
db.ExecuteSqlRaw(sqlString);
}
/// <summary>
/// Добавить партиционирование таблицы ParameterData (Wits - данные)
/// </summary>
/// <param name="db"></param>
private static void AddParameterDataPartitioning(this DatabaseFacade db)
{
var type = typeof(ParameterData);
var tableAttribute = type.GetCustomAttribute<TableAttribute>();
if (tableAttribute is null)
{
return;
}
const int sectionsNumber = 2;
const int chunkTimeInterval = 5;
var sqlString = $"SELECT create_hypertable({tableAttribute.Name}," +
$"'{nameof(ParameterData.Timestamp)}'," +
$"'{nameof(ParameterData.ParameterId)}'," +
$"{sectionsNumber}," +
$"chunk_time_interval => INTERVAL '{chunkTimeInterval} day');";
db.ExecuteSqlRaw(sqlString);
}
}

View File

@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DD.Persistence.Database.Postgres.Migrations
{
[DbContext(typeof(PersistencePostgresContext))]
[Migration("20250122111321_Init")]
[Migration("20250122120353_Init")]
partial class Init
{
/// <inheritdoc />
@ -40,7 +40,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("DiscriminatorId");
b.ToTable("DataSchemes");
b.ToTable("data_scheme");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
@ -61,7 +61,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("SystemId");
b.ToTable("DataSourceSystem");
b.ToTable("data_source_system");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.ParameterData", b =>
@ -85,7 +85,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("DiscriminatorId", "ParameterId", "Timestamp");
b.ToTable("ParameterData");
b.ToTable("parameter_data");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>
@ -120,7 +120,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasIndex("SystemId");
b.ToTable("TechMessage");
b.ToTable("tech_message");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b =>
@ -140,7 +140,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("DiscriminatorId", "Timestamp");
b.ToTable("TimestampedValues");
b.ToTable("timestamped_values");
});
modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b =>
@ -193,7 +193,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Id");
b.ToTable("ChangeLog");
b.ToTable("change_log");
});
modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b =>
@ -216,7 +216,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Key", "Timestamp");
b.ToTable("Setpoint");
b.ToTable("setpoint");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>

View File

@ -13,7 +13,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ChangeLog",
name: "change_log",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ записи"),
@ -30,11 +30,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_ChangeLog", x => x.Id);
table.PrimaryKey("PK_change_log", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DataSchemes",
name: "data_scheme",
columns: table => new
{
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"),
@ -42,11 +42,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_DataSchemes", x => x.DiscriminatorId);
table.PrimaryKey("PK_data_scheme", x => x.DiscriminatorId);
});
migrationBuilder.CreateTable(
name: "DataSourceSystem",
name: "data_source_system",
columns: table => new
{
SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы - источника данных"),
@ -55,11 +55,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_DataSourceSystem", x => x.SystemId);
table.PrimaryKey("PK_data_source_system", x => x.SystemId);
});
migrationBuilder.CreateTable(
name: "ParameterData",
name: "parameter_data",
columns: table => new
{
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор системы"),
@ -69,11 +69,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_ParameterData", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp });
table.PrimaryKey("PK_parameter_data", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp });
});
migrationBuilder.CreateTable(
name: "Setpoint",
name: "setpoint",
columns: table => new
{
Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"),
@ -83,11 +83,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_Setpoint", x => new { x.Key, x.Timestamp });
table.PrimaryKey("PK_setpoint", x => new { x.Key, x.Timestamp });
});
migrationBuilder.CreateTable(
name: "TimestampedValues",
name: "timestamped_values",
columns: table => new
{
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Временная отметка"),
@ -96,17 +96,17 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_TimestampedValues", x => new { x.DiscriminatorId, x.Timestamp });
table.PrimaryKey("PK_timestamped_values", x => new { x.DiscriminatorId, x.Timestamp });
table.ForeignKey(
name: "FK_TimestampedValues_DataSchemes_DiscriminatorId",
name: "FK_timestamped_values_data_scheme_DiscriminatorId",
column: x => x.DiscriminatorId,
principalTable: "DataSchemes",
principalTable: "data_scheme",
principalColumn: "DiscriminatorId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TechMessage",
name: "tech_message",
columns: table => new
{
EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
@ -118,18 +118,18 @@ namespace DD.Persistence.Database.Postgres.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_TechMessage", x => x.EventId);
table.PrimaryKey("PK_tech_message", x => x.EventId);
table.ForeignKey(
name: "FK_TechMessage_DataSourceSystem_SystemId",
name: "FK_tech_message_data_source_system_SystemId",
column: x => x.SystemId,
principalTable: "DataSourceSystem",
principalTable: "data_source_system",
principalColumn: "SystemId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_TechMessage_SystemId",
table: "TechMessage",
name: "IX_tech_message_SystemId",
table: "tech_message",
column: "SystemId");
}
@ -137,25 +137,25 @@ namespace DD.Persistence.Database.Postgres.Migrations
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ChangeLog");
name: "change_log");
migrationBuilder.DropTable(
name: "ParameterData");
name: "parameter_data");
migrationBuilder.DropTable(
name: "Setpoint");
name: "setpoint");
migrationBuilder.DropTable(
name: "TechMessage");
name: "tech_message");
migrationBuilder.DropTable(
name: "TimestampedValues");
name: "timestamped_values");
migrationBuilder.DropTable(
name: "DataSourceSystem");
name: "data_source_system");
migrationBuilder.DropTable(
name: "DataSchemes");
name: "data_scheme");
}
}
}

View File

@ -37,7 +37,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("DiscriminatorId");
b.ToTable("DataSchemes");
b.ToTable("data_scheme");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
@ -58,7 +58,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("SystemId");
b.ToTable("DataSourceSystem");
b.ToTable("data_source_system");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.ParameterData", b =>
@ -82,7 +82,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("DiscriminatorId", "ParameterId", "Timestamp");
b.ToTable("ParameterData");
b.ToTable("parameter_data");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>
@ -117,7 +117,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasIndex("SystemId");
b.ToTable("TechMessage");
b.ToTable("tech_message");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b =>
@ -137,7 +137,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("DiscriminatorId", "Timestamp");
b.ToTable("TimestampedValues");
b.ToTable("timestamped_values");
});
modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b =>
@ -190,7 +190,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Id");
b.ToTable("ChangeLog");
b.ToTable("change_log");
});
modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b =>
@ -213,7 +213,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Key", "Timestamp");
b.ToTable("Setpoint");
b.ToTable("setpoint");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>

View File

@ -1,15 +1,16 @@

using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.ModelsAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using DD.Persistence.ModelsAbstractions;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Model;
namespace DD.Persistence.Database.Entity;
/// <summary>
/// Часть записи, описывающая изменение
/// </summary>
[Table("change_log")]
public class ChangeLog : IChangeLog
{
[Key, Comment("Ключ записи")]

View File

@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("data_scheme")]
public class DataScheme
{
[Key, Comment("Идентификатор схемы данных"),]

View File

@ -3,6 +3,8 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("data_source_system")]
public class DataSourceSystem
{
[Key, Comment("Id системы - источника данных")]

View File

@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("parameter_data")]
[PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))]
public class ParameterData : ITimestampedItem
{

View File

@ -1,23 +1,23 @@
using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace DD.Persistence.Database.Model
namespace DD.Persistence.Database.Entity;
[Table("setpoint")]
[PrimaryKey(nameof(Key), nameof(Timestamp))]
public class Setpoint : ITimestampedItem
{
[PrimaryKey(nameof(Key), nameof(Timestamp))]
public class Setpoint : ITimestampedItem
{
[Comment("Ключ")]
public Guid Key { get; set; }
[Comment("Ключ")]
public Guid Key { get; set; }
[Column(TypeName = "jsonb"), Comment("Значение уставки")]
public required JsonElement Value { get; set; }
[Column(TypeName = "jsonb"), Comment("Значение уставки")]
public required JsonElement Value { get; set; }
[Comment("Дата создания уставки")]
public DateTimeOffset Timestamp { get; set; }
[Comment("Дата создания уставки")]
public DateTimeOffset Timestamp { get; set; }
[Comment("Id автора последнего изменения")]
public Guid IdUser { get; set; }
}
[Comment("Id автора последнего изменения")]
public Guid IdUser { get; set; }
}

View File

@ -3,18 +3,19 @@ using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity
namespace DD.Persistence.Database.Entity;
[Table("tech_message")]
public class TechMessage : ITimestampedItem
{
public class TechMessage : ITimestampedItem
{
[Key, Comment("Id события")]
public Guid EventId { get; set; }
[Key, Comment("Id события")]
public Guid EventId { get; set; }
[Comment("Id Категории важности")]
public int CategoryId { get; set; }
[Comment("Id Категории важности")]
public int CategoryId { get; set; }
[Comment("Дата возникновения")]
public DateTimeOffset Timestamp { get; set; }
[Comment("Дата возникновения")]
public DateTimeOffset Timestamp { get; set; }
[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
public required string Text { get; set; }
@ -28,4 +29,3 @@ namespace DD.Persistence.Database.Entity
[Comment("Статус события")]
public int EventState { get; set; }
}
}

View File

@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("timestamped_values")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
public class TimestampedValues : ITimestampedItem
{

View File

@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Model;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Database;

View File

@ -1,17 +1,15 @@
using DD.Persistence.Client;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Models.Requests;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using DD.Persistence.Database.Model;
using DD.Persistence.Models;
using DD.Persistence.Models.Requests;
using Xunit;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Clients;
using Microsoft.Extensions.Logging;
using Refit;
using System.Net.Http;
using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers;
public class ChangeLogControllerTest : BaseIntegrationTest

View File

@ -2,7 +2,7 @@ using DD.Persistence.Client;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Model;
using DD.Persistence.Database.Entity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Json;

View File

@ -8,12 +8,12 @@ using Microsoft.Extensions.Logging;
using DD.Persistence.API;
using DD.Persistence.Client;
using DD.Persistence.Database.Model;
using DD.Persistence.Database.Postgres;
using RestSharp;
using DD.Persistence.App;
using DD.Persistence.Client.Helpers;
using DD.Persistence.Factories;
using System.Net;
using DD.Persistence.Database.Postgres.Extensions;
namespace DD.Persistence.IntegrationTests;
public class WebAppFactoryFixture : WebApplicationFactory<Program>

View File

@ -1,12 +1,11 @@
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using DD.Persistence.Database.Model;
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using DD.Persistence.Repository.Repositories;
using DD.Persistence.Database.Entity;
using System.Reflection;
using DD.Persistence.Repository.RepositoriesCached;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace DD.Persistence.Repository;
public static class DependencyInjection

View File

@ -1,11 +1,11 @@
using Mapster;
using Microsoft.EntityFrameworkCore;
using DD.Persistence.Database.Model;
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
using UuidExtensions;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Repository.Repositories;
public class ChangeLogRepository : IChangeLogRepository

View File

@ -1,10 +1,10 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
using DD.Persistence.Database.Model;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using System.Text.Json;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Repository.Repositories
{

View File

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

View File

@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
namespace DD.Persistence.Test;
public class TableAttributeShould
{
private const string Separator = "_";
private const string TargetAssembly = "DD.Persistence.Database";
private const string TargetNamespace = "DD.Persistence.Database.Entity";
[Fact]
public void Test()
{
Assembly assembly = Assembly.Load(TargetAssembly);
var typesInNamespace = assembly.GetTypes()
.Where(t => t.IsClass && t.Namespace == TargetNamespace)
.ToList();
foreach (var type in typesInNamespace)
{
var tableAttribute = type.GetCustomAttribute<TableAttribute>();
Assert.NotNull(tableAttribute);
var partsOfClassName = Regex
.Split(type.Name, @"(?=[A-Z])")
.Where(s => s != string.Empty)
.Select(s => s.ToLower(CultureInfo.InvariantCulture));
var expectedClassName = string.Join(Separator, partsOfClassName);
Assert.Equal(expectedClassName, tableAttribute.Name);
}
}
}