From 2c66adfae790c701b4b1c74e39953dafe2ece5a0 Mon Sep 17 00:00:00 2001 From: Olga Nemt Date: Mon, 10 Feb 2025 16:41:31 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BF=D0=B5=D1=86?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ChangeLogController.cs | 2 +- .../Controllers/SetpointController.cs | 6 +- DD.Persistence.App/appsettings.Tests.json | 4 +- DD.Persistence.App/appsettings.json | 2 +- ...minatorUpdateToDiscriminatorId.Designer.cs | 230 ++++++++++++++++++ ..._IdDiscriminatorUpdateToDiscriminatorId.cs | 38 +++ ...PersistencePostgresContextModelSnapshot.cs | 12 +- DD.Persistence.Database/Entity/ChangeLog.cs | 4 +- DD.Persistence.Database/Entity/Setpoint.cs | 6 +- .../EntityAbstractions/IChangeLog.cs | 2 +- .../EntityAbstractions/IDiscriminatorItem.cs | 14 ++ .../Repositories/ChangeLogRepository.cs | 115 +++++---- .../Repositories/GroupByEvaluator.cs | 62 +++++ .../Repositories/SetpointRepository.cs | 66 +++-- .../ChangeLog/CreatedOrUpdatedGeDateSpec.cs | 17 ++ .../ChangeLog/FromCreationDateRangeSpec.cs | 25 ++ .../ChangeLog/FromObsoleteDateRangeSpece.cs | 18 ++ .../ChangeLog/IdContainsSpec.cs | 22 ++ .../ChangeLog/ObsoleteIsNullSpec.cs | 18 ++ .../ChangeLog/ObsoleteNotNullSpec.cs | 18 ++ .../Specifications/Common/DatesRangeSpec.cs | 19 ++ .../DiscriminatorContainsSpec.cs | 18 ++ .../DiscriminatorEqualSpec.cs | 18 ++ .../Controllers/ChangeLogControllerTest.cs | 4 +- 24 files changed, 650 insertions(+), 90 deletions(-) create mode 100644 DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.Designer.cs create mode 100644 DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.cs create mode 100644 DD.Persistence.Database/EntityAbstractions/IDiscriminatorItem.cs create mode 100644 DD.Persistence.Database/Repositories/GroupByEvaluator.cs create mode 100644 DD.Persistence.Database/Specifications/ChangeLog/CreatedOrUpdatedGeDateSpec.cs create mode 100644 DD.Persistence.Database/Specifications/ChangeLog/FromCreationDateRangeSpec.cs create mode 100644 DD.Persistence.Database/Specifications/ChangeLog/FromObsoleteDateRangeSpece.cs create mode 100644 DD.Persistence.Database/Specifications/ChangeLog/IdContainsSpec.cs create mode 100644 DD.Persistence.Database/Specifications/ChangeLog/ObsoleteIsNullSpec.cs create mode 100644 DD.Persistence.Database/Specifications/ChangeLog/ObsoleteNotNullSpec.cs create mode 100644 DD.Persistence.Database/Specifications/Common/DatesRangeSpec.cs create mode 100644 DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorContainsSpec.cs create mode 100644 DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorEqualSpec.cs diff --git a/DD.Persistence.API/Controllers/ChangeLogController.cs b/DD.Persistence.API/Controllers/ChangeLogController.cs index f10ae1a..13b2d17 100644 --- a/DD.Persistence.API/Controllers/ChangeLogController.cs +++ b/DD.Persistence.API/Controllers/ChangeLogController.cs @@ -9,7 +9,7 @@ using DD.Persistence.Models.Common; namespace DD.Persistence.API.Controllers; [ApiController] -[Authorize] +//[Authorize] [Route("api/[controller]")] public class ChangeLogController : ControllerBase, IChangeLogApi { diff --git a/DD.Persistence.API/Controllers/SetpointController.cs b/DD.Persistence.API/Controllers/SetpointController.cs index 2f1c6fc..6805cec 100644 --- a/DD.Persistence.API/Controllers/SetpointController.cs +++ b/DD.Persistence.API/Controllers/SetpointController.cs @@ -12,7 +12,7 @@ namespace DD.Persistence.API.Controllers; /// Работа с уставками /// [ApiController] -[Authorize] +//[Authorize] [Route("api/[controller]")] public class SetpointController : ControllerBase, ISetpointApi { @@ -105,8 +105,8 @@ public class SetpointController : ControllerBase, ISetpointApi [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] public async Task Add(Guid setpointKey, object newValue, CancellationToken token) { - var userId = User.GetUserId(); - await setpointRepository.Add(setpointKey, (JsonElement)newValue, userId, token); + //var userId = User.GetUserId(); + await setpointRepository.Add(setpointKey, (JsonElement)newValue, Guid.NewGuid(), token); return CreatedAtAction(nameof(Add), true); } diff --git a/DD.Persistence.App/appsettings.Tests.json b/DD.Persistence.App/appsettings.Tests.json index 72c43d3..e8d3cc4 100644 --- a/DD.Persistence.App/appsettings.Tests.json +++ b/DD.Persistence.App/appsettings.Tests.json @@ -1,10 +1,10 @@ { "DbConnection": { - "Host": "postgres", + "Host": "localhost", "Port": 5432, "Database": "persistence", "Username": "postgres", - "Password": "postgres" + "Password": "q" }, "NeedUseKeyCloak": false, "AuthUser": { diff --git a/DD.Persistence.App/appsettings.json b/DD.Persistence.App/appsettings.json index 7ad8c67..9b0d5be 100644 --- a/DD.Persistence.App/appsettings.json +++ b/DD.Persistence.App/appsettings.json @@ -6,7 +6,7 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Host=localhost;Database=persistence;Username=postgres;Password=postgres;Persist Security Info=True" + "DefaultConnection": "Host=localhost:5462;Database=persistence;Username=postgres;Password=postgres;Persist Security Info=True" }, "AllowedHosts": "*", "NeedUseKeyCloak": false, diff --git a/DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.Designer.cs b/DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.Designer.cs new file mode 100644 index 0000000..9b6bb31 --- /dev/null +++ b/DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.Designer.cs @@ -0,0 +1,230 @@ +// +using System; +using System.Text.Json; +using DD.Persistence.Database.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DD.Persistence.Database.Postgres.Migrations +{ + [DbContext(typeof(PersistencePostgresContext))] + [Migration("20250210104036_IdDiscriminatorUpdateToDiscriminatorId")] + partial class IdDiscriminatorUpdateToDiscriminatorId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Ключ записи"); + + b.Property("Creation") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания записи"); + + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Дискриминатор таблицы"); + + b.Property("IdAuthor") + .HasColumnType("uuid") + .HasComment("Автор изменения"); + + b.Property("IdEditor") + .HasColumnType("uuid") + .HasComment("Редактор"); + + b.Property("IdNext") + .HasColumnType("uuid") + .HasComment("Id заменяющей записи"); + + b.Property("Obsolete") + .HasColumnType("timestamp with time zone") + .HasComment("Дата устаревания (например при удалении)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Значение"); + + b.HasKey("Id"); + + b.ToTable("change_log"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => + { + b.Property("SystemId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Id системы - источника данных"); + + b.Property("Description") + .HasColumnType("text") + .HasComment("Описание системы - источника данных"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(256)") + .HasComment("Наименование системы - источника данных"); + + b.HasKey("SystemId"); + + b.ToTable("data_source_system"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.ParameterData", b => + { + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Дискриминатор системы"); + + b.Property("ParameterId") + .HasColumnType("integer") + .HasComment("Id параметра"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Временная отметка"); + + b.Property("Value") + .IsRequired() + .HasColumnType("varchar(256)") + .HasComment("Значение параметра в виде строки"); + + b.HasKey("DiscriminatorId", "ParameterId", "Timestamp"); + + b.ToTable("parameter_data"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.SchemeProperty", b => + { + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Идентификатор схемы данных"); + + b.Property("Index") + .HasColumnType("integer") + .HasComment("Индекс поля"); + + b.Property("PropertyKind") + .HasColumnType("smallint") + .HasComment("Тип индексируемого поля"); + + b.Property("PropertyName") + .IsRequired() + .HasColumnType("text") + .HasComment("Наименования индексируемого поля"); + + b.HasKey("DiscriminatorId", "Index"); + + b.ToTable("scheme_property"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => + { + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Ключ"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания уставки"); + + b.Property("IdUser") + .HasColumnType("uuid") + .HasComment("Id автора последнего изменения"); + + b.Property("Value") + .HasColumnType("jsonb") + .HasComment("Значение уставки"); + + b.HasKey("DiscriminatorId", "Timestamp"); + + b.ToTable("setpoint"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Id события"); + + b.Property("CategoryId") + .HasColumnType("integer") + .HasComment("Id Категории важности"); + + b.Property("EventState") + .HasColumnType("integer") + .HasComment("Статус события"); + + b.Property("SystemId") + .HasColumnType("uuid") + .HasComment("Id системы, к которой относится сообщение"); + + b.Property("Text") + .IsRequired() + .HasColumnType("varchar(512)") + .HasComment("Текст сообщения"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Дата возникновения"); + + b.HasKey("EventId"); + + b.HasIndex("SystemId"); + + b.ToTable("tech_message"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b => + { + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Дискриминатор системы"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Временная отметка"); + + b.Property("Values") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Данные"); + + b.HasKey("DiscriminatorId", "Timestamp"); + + b.ToTable("timestamped_values"); + }); + + modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => + { + b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System") + .WithMany() + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("System"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.cs b/DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.cs new file mode 100644 index 0000000..5089eea --- /dev/null +++ b/DD.Persistence.Database.Postgres/Migrations/20250210104036_IdDiscriminatorUpdateToDiscriminatorId.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DD.Persistence.Database.Postgres.Migrations +{ + /// + public partial class IdDiscriminatorUpdateToDiscriminatorId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Key", + table: "setpoint", + newName: "DiscriminatorId"); + + migrationBuilder.RenameColumn( + name: "IdDiscriminator", + table: "change_log", + newName: "DiscriminatorId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "DiscriminatorId", + table: "setpoint", + newName: "Key"); + + migrationBuilder.RenameColumn( + name: "DiscriminatorId", + table: "change_log", + newName: "IdDiscriminator"); + } + } +} diff --git a/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs b/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs index e2b0921..8a54dd6 100644 --- a/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs +++ b/DD.Persistence.Database.Postgres/Migrations/PersistencePostgresContextModelSnapshot.cs @@ -34,14 +34,14 @@ namespace DD.Persistence.Database.Postgres.Migrations .HasColumnType("timestamp with time zone") .HasComment("Дата создания записи"); + b.Property("DiscriminatorId") + .HasColumnType("uuid") + .HasComment("Дискриминатор таблицы"); + b.Property("IdAuthor") .HasColumnType("uuid") .HasComment("Автор изменения"); - b.Property("IdDiscriminator") - .HasColumnType("uuid") - .HasComment("Дискриминатор таблицы"); - b.Property("IdEditor") .HasColumnType("uuid") .HasComment("Редактор"); @@ -135,7 +135,7 @@ namespace DD.Persistence.Database.Postgres.Migrations modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => { - b.Property("Key") + b.Property("DiscriminatorId") .HasColumnType("uuid") .HasComment("Ключ"); @@ -151,7 +151,7 @@ namespace DD.Persistence.Database.Postgres.Migrations .HasColumnType("jsonb") .HasComment("Значение уставки"); - b.HasKey("Key", "Timestamp"); + b.HasKey("DiscriminatorId", "Timestamp"); b.ToTable("setpoint"); }); diff --git a/DD.Persistence.Database/Entity/ChangeLog.cs b/DD.Persistence.Database/Entity/ChangeLog.cs index db1537a..a2ade1c 100644 --- a/DD.Persistence.Database/Entity/ChangeLog.cs +++ b/DD.Persistence.Database/Entity/ChangeLog.cs @@ -11,13 +11,13 @@ namespace DD.Persistence.Database.Entity; /// Часть записи, описывающая изменение /// [Table("change_log")] -public class ChangeLog : IChangeLog +public class ChangeLog : IChangeLog, IDiscriminatorItem { [Key, Comment("Ключ записи")] public Guid Id { get; set; } [Comment("Дискриминатор таблицы")] - public Guid IdDiscriminator { get; set; } + public Guid DiscriminatorId { get; set; } [Comment("Автор изменения")] public Guid IdAuthor { get; set; } diff --git a/DD.Persistence.Database/Entity/Setpoint.cs b/DD.Persistence.Database/Entity/Setpoint.cs index 64c816c..ae94a0c 100644 --- a/DD.Persistence.Database/Entity/Setpoint.cs +++ b/DD.Persistence.Database/Entity/Setpoint.cs @@ -6,11 +6,11 @@ using System.Text.Json; namespace DD.Persistence.Database.Entity; [Table("setpoint")] -[PrimaryKey(nameof(Key), nameof(Timestamp))] -public class Setpoint : ITimestampedItem +[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))] +public class Setpoint : ITimestampedItem, IDiscriminatorItem { [Comment("Ключ")] - public Guid Key { get; set; } + public Guid DiscriminatorId { get; set; } [Column(TypeName = "jsonb"), Comment("Значение уставки")] public required JsonElement Value { get; set; } diff --git a/DD.Persistence.Database/EntityAbstractions/IChangeLog.cs b/DD.Persistence.Database/EntityAbstractions/IChangeLog.cs index 4d082a7..fb8219c 100644 --- a/DD.Persistence.Database/EntityAbstractions/IChangeLog.cs +++ b/DD.Persistence.Database/EntityAbstractions/IChangeLog.cs @@ -38,7 +38,7 @@ public interface IChangeLog /// /// Дискриминатор таблицы /// - public Guid IdDiscriminator { get; set; } + public Guid DiscriminatorId { get; set; } /// /// Значение diff --git a/DD.Persistence.Database/EntityAbstractions/IDiscriminatorItem.cs b/DD.Persistence.Database/EntityAbstractions/IDiscriminatorItem.cs new file mode 100644 index 0000000..b5d2b06 --- /dev/null +++ b/DD.Persistence.Database/EntityAbstractions/IDiscriminatorItem.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DD.Persistence.Database.EntityAbstractions; +public interface IDiscriminatorItem +{ + /// + /// Дискриминатор + /// + Guid DiscriminatorId { get; set; } +} diff --git a/DD.Persistence.Database/Repositories/ChangeLogRepository.cs b/DD.Persistence.Database/Repositories/ChangeLogRepository.cs index 61e907d..facb066 100644 --- a/DD.Persistence.Database/Repositories/ChangeLogRepository.cs +++ b/DD.Persistence.Database/Repositories/ChangeLogRepository.cs @@ -1,5 +1,9 @@ -using DD.Persistence.Database.Entity; +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using DD.Persistence.Database.Entity; using DD.Persistence.Database.Postgres.Helpers; +using DD.Persistence.Database.Specifications.ChangeLog; +using DD.Persistence.Database.Specifications.Common.DiscriminatorItem; using DD.Persistence.Models; using DD.Persistence.Models.Common; using DD.Persistence.Models.Requests; @@ -12,10 +16,13 @@ namespace DD.Persistence.Database.Repositories; public class ChangeLogRepository : IChangeLogRepository { private readonly DbContext db; + private readonly ObsoleteIsNullSpec obsoleteIsNullSpecification; public ChangeLogRepository(DbContext db) { this.db = db; + + this.obsoleteIsNullSpecification = new ObsoleteIsNullSpec(); } public async Task AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) @@ -35,9 +42,11 @@ public class ChangeLogRepository : IChangeLogRepository public async Task MarkAsDeleted(Guid idEditor, IEnumerable ids, CancellationToken token) { + var containsIdsSpecification = new IdContainsSpec(ids); + var query = db.Set() - .Where(s => ids.Contains(s.Id)) - .Where(s => s.Obsolete == null); + .WithSpecification(containsIdsSpecification) + .WithSpecification(obsoleteIsNullSpecification); if (query.Count() != ids.Count()) { @@ -53,9 +62,11 @@ public class ChangeLogRepository : IChangeLogRepository public async Task MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token) { + var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); + var query = db.Set() - .Where(s => s.IdDiscriminator == idDiscriminator) - .Where(e => e.Obsolete == null); + .WithSpecification(specDiscriminatorEqual) + .WithSpecification(obsoleteIsNullSpecification); var entities = await query.ToArrayAsync(token); @@ -97,8 +108,10 @@ public class ChangeLogRepository : IChangeLogRepository var dbSet = db.Set(); var updatedIds = dtos.Select(d => d.Id); + var containsIdsSpecification = new IdContainsSpec(updatedIds); + var updatedEntities = dbSet - .Where(s => updatedIds.Contains(s.Id)) + .WithSpecification(containsIdsSpecification) .ToDictionary(s => s.Id); var result = 0; @@ -112,7 +125,7 @@ public class ChangeLogRepository : IChangeLogRepository throw new ArgumentException($"Entity with id = {dto.Id} doesn't exist in Db", nameof(dto)); } - var newEntity = CreateEntityFromDto(idEditor, updatedEntity.IdDiscriminator, dto); + var newEntity = CreateEntityFromDto(idEditor, updatedEntity.DiscriminatorId, dto); dbSet.Add(newEntity); updatedEntity.IdNext = newEntity.Id; @@ -134,7 +147,11 @@ public class ChangeLogRepository : IChangeLogRepository PaginationRequest paginationRequest, CancellationToken token) { - var query = CreateQuery(idDiscriminator); + var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); + + var query = db.Set() + .WithSpecification(specDiscriminatorEqual); + query = query.Apply(momentUtc); var result = await query.ApplyPagination(paginationRequest, Convert, token); @@ -142,22 +159,21 @@ public class ChangeLogRepository : IChangeLogRepository return result; } - private IQueryable CreateQuery(Guid idDiscriminator) - { - var query = db.Set().Where(e => e.IdDiscriminator == idDiscriminator); - - return query; - } - public async Task> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) { - var query = db.Set().Where(s => s.IdDiscriminator == idDiscriminator); + var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); + + var query = db.Set() + .WithSpecification(specDiscriminatorEqual); var min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero); var max = new DateTimeOffset(dateEnd.ToUniversalTime().Date, TimeSpan.Zero); - var createdQuery = query.Where(e => e.Creation >= min && e.Creation <= max); - var editedQuery = query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); + var specCreatedByDateRange = new FromCreationDateRangeSpec(min, max); + var specHistoryByDateRange = new FromObsoleteDateRangeSpece(min, max); + + var createdQuery = query.WithSpecification(specCreatedByDateRange); + var editedQuery = query.WithSpecification(specHistoryByDateRange); query = createdQuery.Union(editedQuery); var entities = await query.ToArrayAsync(token); @@ -171,7 +187,11 @@ public class ChangeLogRepository : IChangeLogRepository public async Task> GetDatesChange(Guid idDiscriminator, CancellationToken token) { - var query = db.Set().Where(e => e.IdDiscriminator == idDiscriminator); + var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); + var specObsoleteNotNull = new ObsoleteNotNullSpec(); + + var query = db.Set() + .WithSpecification(specDiscriminatorEqual); var datesCreateQuery = query .Select(e => e.Creation) @@ -180,7 +200,7 @@ public class ChangeLogRepository : IChangeLogRepository var datesCreate = await datesCreateQuery.ToArrayAsync(token); var datesUpdateQuery = query - .Where(e => e.Obsolete != null) + .WithSpecification(specObsoleteNotNull) .Select(e => e.Obsolete!.Value) .Distinct(); @@ -202,7 +222,7 @@ public class ChangeLogRepository : IChangeLogRepository Id = Uuid7.Guid(), Creation = DateTimeOffset.UtcNow, IdAuthor = idAuthor, - IdDiscriminator = idDiscriminator, + DiscriminatorId = idDiscriminator, IdEditor = idAuthor, Value = dto.Value @@ -214,9 +234,13 @@ public class ChangeLogRepository : IChangeLogRepository public async Task> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token) { var date = dateBegin.ToUniversalTime(); + + var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); + var specCreatedOrUpdatedGeDate = new CreatedOrUpdatedGeDateSpec(date); + var query = db.Set() - .Where(e => e.IdDiscriminator == idDiscriminator) - .Where(e => e.Creation >= date || e.Obsolete >= date); + .WithSpecification(specDiscriminatorEqual) + .WithSpecification(specCreatedOrUpdatedGeDate); var entities = await query.ToArrayAsync(token); @@ -227,29 +251,34 @@ public class ChangeLogRepository : IChangeLogRepository public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) { - var query = db.Set() - .Where(e => e.IdDiscriminator == idDiscriminator) - .GroupBy(e => 1) - .Select(group => new - { - Min = group.Min(e => e.Creation), - Max = group.Max(e => e.Obsolete.HasValue && e.Obsolete > e.Creation - ? e.Obsolete.Value - : e.Creation), - }); + return null; + //var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); + //var evaluator = + // SpecificationEvaluator.Default + //var query = db.Set() + // //.Where(e => e.IdDiscriminator == idDiscriminator) + // .GroupBy(e => 1) + // .WithSpecification(specDiscriminatorEqual) + // .Select(group => new + // { + // Min = group.Min(e => e.Creation), + // Max = group.Max(e => e.Obsolete.HasValue && e.Obsolete > e.Creation + // ? e.Obsolete.Value + // : e.Creation), + // }); - var values = await query.FirstOrDefaultAsync(token); + //var values = await query.FirstOrDefaultAsync(token); - if (values is null) - { - return null; - } + //if (values is null) + //{ + // return null; + //} - return new DatesRangeDto - { - From = values.Min, - To = values.Max, - }; + //return new DatesRangeDto + //{ + // From = values.Min, + // To = values.Max, + //}; } private ChangeLogValuesDto Convert(ChangeLog entity) => entity.Adapt(); diff --git a/DD.Persistence.Database/Repositories/GroupByEvaluator.cs b/DD.Persistence.Database/Repositories/GroupByEvaluator.cs new file mode 100644 index 0000000..09fb608 --- /dev/null +++ b/DD.Persistence.Database/Repositories/GroupByEvaluator.cs @@ -0,0 +1,62 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; +using DD.Persistence.Database.Entity; +using DD.Persistence.Database.EntityAbstractions; +using DD.Persistence.Models.Common; + +namespace DD.Persistence.Database.Postgres.Repositories; +public class MyPartialEvaluator : IEvaluator + { + private MyPartialEvaluator() { } + public static MyPartialEvaluator Instance { get; } = new MyPartialEvaluator(); + + public bool IsCriteriaEvaluator { get; } = true; + + public IQueryable GetQuery(IQueryable query, ISpecification specification) where T : class + { + + // Проверяем, есть ли свойство Timestamp в типе T + var timestampProperty = typeof(T).GetProperty("Timestamp"); + + if (timestampProperty != null && timestampProperty.PropertyType == typeof(DateTimeOffset)) + { + // Если свойство существует и имеет тип DateTime, выполняем группировку и выборку + var t = query + .GroupBy(x => 1) + .Select(group => new + { + Min = group.Min(e => (DateTimeOffset)timestampProperty.GetValue(e)), + Max = group.Max(e => (DateTimeOffset)timestampProperty.GetValue(e)), + }) + .AsQueryable() as IQueryable; + return t; + } + + return query; + + + + + + } +} + +public class Test +{ + public Test() + { + } + + public DateTimeOffset Min { get; set; } + public DateTimeOffset Max { get; set; } +} + +public class MySpecificationEvaluator : SpecificationEvaluator +{ + public static MySpecificationEvaluator GroupBy { get; } = new MySpecificationEvaluator(); + + private MySpecificationEvaluator() : base() + { + Evaluators.Add(MyPartialEvaluator.Instance); + } +} \ No newline at end of file diff --git a/DD.Persistence.Database/Repositories/SetpointRepository.cs b/DD.Persistence.Database/Repositories/SetpointRepository.cs index 3cfd9b1..38505f3 100644 --- a/DD.Persistence.Database/Repositories/SetpointRepository.cs +++ b/DD.Persistence.Database/Repositories/SetpointRepository.cs @@ -1,4 +1,8 @@ +using Ardalis.Specification; +using Ardalis.Specification.EntityFrameworkCore; using DD.Persistence.Database.Entity; +using DD.Persistence.Database.Specifications.Common; +using DD.Persistence.Database.Specifications.Common.DiscriminatorItem; using DD.Persistence.Models; using DD.Persistence.Models.Common; using DD.Persistence.Repositories; @@ -19,14 +23,16 @@ namespace DD.Persistence.Database.Postgres.Repositories protected virtual IQueryable GetQueryReadOnly() => db.Set(); public async Task> GetCurrent( - IEnumerable setpointKeys, - CancellationToken token) + IEnumerable setpointKeys, + CancellationToken token) { var query = GetQueryReadOnly(); - var entities = await query - .Where(e => setpointKeys.Contains(e.Key)) - .GroupBy(e => e.Key) + var specDiscriminatorContains = new DiscriminatorContainsSpec(setpointKeys); + + var entities = await db.Set() + .WithSpecification(specDiscriminatorContains) + .GroupBy(e => e.DiscriminatorId) .Select(g => g.OrderByDescending(x => x.Timestamp).FirstOrDefault()) .ToArrayAsync(token); @@ -37,23 +43,27 @@ namespace DD.Persistence.Database.Postgres.Repositories { var query = GetQueryReadOnly(); - var entities = await query - .Where(e => setpointKeys.Contains(e.Key)) - .GroupBy(e => e.Key) + var specDiscriminatorContains = new DiscriminatorContainsSpec(setpointKeys); + + var entities = await db.Set() + .WithSpecification(specDiscriminatorContains) + .GroupBy(e => e.DiscriminatorId) .Select(g => g.OrderByDescending(x => x.Timestamp).FirstOrDefault()) - .ToDictionaryAsync(x=> x.Key, x => (object)x.Value, token); + .ToDictionaryAsync(x=> x.DiscriminatorId, x => (object)x.Value, token); return entities; } public async Task> GetHistory(IEnumerable setpointKeys, DateTimeOffset historyMoment, CancellationToken token) { - var query = GetQueryReadOnly(); - var entities = await query - .Where(e => setpointKeys.Contains(e.Key)) + var specDiscriminatorContains = new DiscriminatorContainsSpec(setpointKeys); + + var entities = await db.Set() + .WithSpecification(specDiscriminatorContains) .ToArrayAsync(token); + var filteredEntities = entities - .GroupBy(e => e.Key) + .GroupBy(e => e.DiscriminatorId) .Select(e => e.OrderBy(o => o.Timestamp)) .Select(e => e.Where(e => e.Timestamp <= historyMoment).Last()); var dtos = filteredEntities @@ -77,31 +87,35 @@ namespace DD.Persistence.Database.Postgres.Repositories public async Task GetDatesRangeAsync(CancellationToken token) { - var query = GetQueryReadOnly() + + var spec = new DatesRangeSpec(); + var query = db.Set() + .WithSpecification(spec, MySpecificationEvaluator.GroupBy); + + var query2 = GetQueryReadOnly() .GroupBy(e => 1) .Select(group => new { Min = group.Min(e => e.Timestamp), Max = group.Max(e => e.Timestamp), }); - var values = await query.FirstOrDefaultAsync(token); - var result = new DatesRangeDto() - { - From = values?.Min ?? DateTimeOffset.MinValue, - To = values?.Max ?? DateTimeOffset.MaxValue - }; - return result; + var values = await query.FirstOrDefaultAsync(token); + + + return null; } public async Task>> GetLog(IEnumerable setpointKeys, CancellationToken token) { - var query = GetQueryReadOnly(); - var entities = await query - .Where(e => setpointKeys.Contains(e.Key)) + var specDiscriminatorContains = new DiscriminatorContainsSpec(setpointKeys); + + var entities = await db.Set() + .WithSpecification(specDiscriminatorContains) .ToArrayAsync(token); + var dtos = entities - .GroupBy(e => e.Key) + .GroupBy(e => e.DiscriminatorId) .ToDictionary(e => e.Key, v => v.Select(z => z.Adapt())); return dtos; @@ -111,7 +125,7 @@ namespace DD.Persistence.Database.Postgres.Repositories { var entity = new Setpoint() { - Key = setpointKey, + DiscriminatorId = setpointKey, Value = newValue, IdUser = idUser, Timestamp = DateTimeOffset.UtcNow.ToUniversalTime() diff --git a/DD.Persistence.Database/Specifications/ChangeLog/CreatedOrUpdatedGeDateSpec.cs b/DD.Persistence.Database/Specifications/ChangeLog/CreatedOrUpdatedGeDateSpec.cs new file mode 100644 index 0000000..0d14981 --- /dev/null +++ b/DD.Persistence.Database/Specifications/ChangeLog/CreatedOrUpdatedGeDateSpec.cs @@ -0,0 +1,17 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DD.Persistence.Database.Specifications.ChangeLog; +public class CreatedOrUpdatedGeDateSpec : Specification + where TEntity : IChangeLog +{ + public CreatedOrUpdatedGeDateSpec(DateTimeOffset date) + { + Query.Where(e => e.Creation >= date || e.Obsolete >= date); + } +} diff --git a/DD.Persistence.Database/Specifications/ChangeLog/FromCreationDateRangeSpec.cs b/DD.Persistence.Database/Specifications/ChangeLog/FromCreationDateRangeSpec.cs new file mode 100644 index 0000000..614ebf6 --- /dev/null +++ b/DD.Persistence.Database/Specifications/ChangeLog/FromCreationDateRangeSpec.cs @@ -0,0 +1,25 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; + +namespace DD.Persistence.Database.Specifications.ChangeLog; + +/// +/// Спецификация для поиска созданных за определённый период записей IChangeLog +/// +/// +public class FromCreationDateRangeSpec : Specification + where TEntity : IChangeLog +{ + public FromCreationDateRangeSpec(DateTimeOffset? min, DateTimeOffset? max) + { + if (min.HasValue) + { + Query.Where(e => e.Creation >= min); + } + if (max.HasValue){ + Query.Where(e => e.Creation <= max); + } + //Query.Where(e => e.Creation >= min && e.Creation <= max); + } +} + diff --git a/DD.Persistence.Database/Specifications/ChangeLog/FromObsoleteDateRangeSpece.cs b/DD.Persistence.Database/Specifications/ChangeLog/FromObsoleteDateRangeSpece.cs new file mode 100644 index 0000000..3c79978 --- /dev/null +++ b/DD.Persistence.Database/Specifications/ChangeLog/FromObsoleteDateRangeSpece.cs @@ -0,0 +1,18 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; + +namespace DD.Persistence.Database.Specifications.ChangeLog; + +/// +/// Спецификация для поиска исторических записей IChangeLog за определённый период +/// +/// +public class FromObsoleteDateRangeSpece : Specification + where TEntity : IChangeLog +{ + public FromObsoleteDateRangeSpece(DateTimeOffset min, DateTimeOffset max) + { + Query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); + } +} + diff --git a/DD.Persistence.Database/Specifications/ChangeLog/IdContainsSpec.cs b/DD.Persistence.Database/Specifications/ChangeLog/IdContainsSpec.cs new file mode 100644 index 0000000..67c18c6 --- /dev/null +++ b/DD.Persistence.Database/Specifications/ChangeLog/IdContainsSpec.cs @@ -0,0 +1,22 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; + +namespace DD.Persistence.Database.Specifications.ChangeLog; + +/// +/// Спецификация для поиска записей IChangeLog по массиву ключей +/// +/// +public class IdContainsSpec : Specification + where TEntity : IChangeLog +{ + public IdContainsSpec() + { + + } + public IdContainsSpec(IEnumerable ids) + { + Query.Where(s => ids.Contains(s.Id)); + } +} + diff --git a/DD.Persistence.Database/Specifications/ChangeLog/ObsoleteIsNullSpec.cs b/DD.Persistence.Database/Specifications/ChangeLog/ObsoleteIsNullSpec.cs new file mode 100644 index 0000000..cf3487c --- /dev/null +++ b/DD.Persistence.Database/Specifications/ChangeLog/ObsoleteIsNullSpec.cs @@ -0,0 +1,18 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; + +namespace DD.Persistence.Database.Specifications.ChangeLog; + +/// +/// Спецификация для актуальных значений IChangeLog +/// +/// +public class ObsoleteIsNullSpec : Specification + where TEntity : IChangeLog +{ + public ObsoleteIsNullSpec() + { + Query.Where(e => e.Obsolete == null); + } +} + diff --git a/DD.Persistence.Database/Specifications/ChangeLog/ObsoleteNotNullSpec.cs b/DD.Persistence.Database/Specifications/ChangeLog/ObsoleteNotNullSpec.cs new file mode 100644 index 0000000..0ca30ce --- /dev/null +++ b/DD.Persistence.Database/Specifications/ChangeLog/ObsoleteNotNullSpec.cs @@ -0,0 +1,18 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; + +namespace DD.Persistence.Database.Specifications.ChangeLog; + +/// +/// Спецификация для поиска не актуальных значений IChangeLog +/// +/// +public class ObsoleteNotNullSpec : Specification + where TEntity : IChangeLog +{ + public ObsoleteNotNullSpec() + { + Query.Where(e => e.Obsolete != null); + } +} + diff --git a/DD.Persistence.Database/Specifications/Common/DatesRangeSpec.cs b/DD.Persistence.Database/Specifications/Common/DatesRangeSpec.cs new file mode 100644 index 0000000..5b7dfea --- /dev/null +++ b/DD.Persistence.Database/Specifications/Common/DatesRangeSpec.cs @@ -0,0 +1,19 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +namespace DD.Persistence.Database.Specifications.Common; + +public class DatesRangeSpec : Specification + where TEntity : IDiscriminatorItem +{ + public DatesRangeSpec() + { + //Query; + } +} \ No newline at end of file diff --git a/DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorContainsSpec.cs b/DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorContainsSpec.cs new file mode 100644 index 0000000..525deba --- /dev/null +++ b/DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorContainsSpec.cs @@ -0,0 +1,18 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +namespace DD.Persistence.Database.Specifications.Common.DiscriminatorItem; +public class DiscriminatorContainsSpec : Specification + where TEntity : IDiscriminatorItem +{ + public DiscriminatorContainsSpec(IEnumerable discriminatorIds) + { + Query.Where(e => discriminatorIds.Contains(e.DiscriminatorId)); + } +} \ No newline at end of file diff --git a/DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorEqualSpec.cs b/DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorEqualSpec.cs new file mode 100644 index 0000000..30a6bd9 --- /dev/null +++ b/DD.Persistence.Database/Specifications/Common/DiscriminatorItem/DiscriminatorEqualSpec.cs @@ -0,0 +1,18 @@ +using Ardalis.Specification; +using DD.Persistence.Database.EntityAbstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +namespace DD.Persistence.Database.Specifications.Common.DiscriminatorItem; +public class DiscriminatorEqualSpec : Specification + where TEntity : IDiscriminatorItem +{ + public DiscriminatorEqualSpec(Guid discriminatorId) + { + Query.Where(e => e.DiscriminatorId == discriminatorId); + } +} \ No newline at end of file diff --git a/DD.Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs b/DD.Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs index b4d90ab..7a56e5f 100644 --- a/DD.Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs +++ b/DD.Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs @@ -102,7 +102,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest var result = await client.Add(idDiscriminator, dto, new CancellationToken()); var entity = dbContext.ChangeLog - .Where(x => x.IdDiscriminator == idDiscriminator) + .Where(x => x.DiscriminatorId == idDiscriminator) .FirstOrDefault(); dto = entity.Adapt(); @@ -318,7 +318,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest var entities = dtos.Select(d => { var entity = d.Adapt(); - entity.IdDiscriminator = idDiscriminator; + entity.DiscriminatorId = idDiscriminator; entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount)); return entity;