Compare commits

...

1 Commits

Author SHA1 Message Date
2c66adfae7 Наработки для спецификаций 2025-02-10 16:41:31 +05:00
24 changed files with 650 additions and 90 deletions

View File

@ -9,7 +9,7 @@ using DD.Persistence.Models.Common;
namespace DD.Persistence.API.Controllers; namespace DD.Persistence.API.Controllers;
[ApiController] [ApiController]
[Authorize] //[Authorize]
[Route("api/[controller]")] [Route("api/[controller]")]
public class ChangeLogController : ControllerBase, IChangeLogApi public class ChangeLogController : ControllerBase, IChangeLogApi
{ {

View File

@ -12,7 +12,7 @@ namespace DD.Persistence.API.Controllers;
/// Работа с уставками /// Работа с уставками
/// </summary> /// </summary>
[ApiController] [ApiController]
[Authorize] //[Authorize]
[Route("api/[controller]")] [Route("api/[controller]")]
public class SetpointController : ControllerBase, ISetpointApi public class SetpointController : ControllerBase, ISetpointApi
{ {
@ -105,8 +105,8 @@ public class SetpointController : ControllerBase, ISetpointApi
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
public async Task<IActionResult> Add(Guid setpointKey, object newValue, CancellationToken token) public async Task<IActionResult> Add(Guid setpointKey, object newValue, CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); //var userId = User.GetUserId<Guid>();
await setpointRepository.Add(setpointKey, (JsonElement)newValue, userId, token); await setpointRepository.Add(setpointKey, (JsonElement)newValue, Guid.NewGuid(), token);
return CreatedAtAction(nameof(Add), true); return CreatedAtAction(nameof(Add), true);
} }

View File

@ -1,10 +1,10 @@
{ {
"DbConnection": { "DbConnection": {
"Host": "postgres", "Host": "localhost",
"Port": 5432, "Port": 5432,
"Database": "persistence", "Database": "persistence",
"Username": "postgres", "Username": "postgres",
"Password": "postgres" "Password": "q"
}, },
"NeedUseKeyCloak": false, "NeedUseKeyCloak": false,
"AuthUser": { "AuthUser": {

View File

@ -6,7 +6,7 @@
} }
}, },
"ConnectionStrings": { "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": "*", "AllowedHosts": "*",
"NeedUseKeyCloak": false, "NeedUseKeyCloak": false,

View File

@ -0,0 +1,230 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Ключ записи");
b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи");
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid>("IdAuthor")
.HasColumnType("uuid")
.HasComment("Автор изменения");
b.Property<Guid?>("IdEditor")
.HasColumnType("uuid")
.HasComment("Редактор");
b.Property<Guid?>("IdNext")
.HasColumnType("uuid")
.HasComment("Id заменяющей записи");
b.Property<DateTimeOffset?>("Obsolete")
.HasColumnType("timestamp with time zone")
.HasComment("Дата устаревания (например при удалении)");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Значение");
b.HasKey("Id");
b.ToTable("change_log");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{
b.Property<Guid>("SystemId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Id системы - источника данных");
b.Property<string>("Description")
.HasColumnType("text")
.HasComment("Описание системы - источника данных");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(256)")
.HasComment("Наименование системы - источника данных");
b.HasKey("SystemId");
b.ToTable("data_source_system");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.ParameterData", b =>
{
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор системы");
b.Property<int>("ParameterId")
.HasColumnType("integer")
.HasComment("Id параметра");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone")
.HasComment("Временная отметка");
b.Property<string>("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<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<int>("Index")
.HasColumnType("integer")
.HasComment("Индекс поля");
b.Property<byte>("PropertyKind")
.HasColumnType("smallint")
.HasComment("Тип индексируемого поля");
b.Property<string>("PropertyName")
.IsRequired()
.HasColumnType("text")
.HasComment("Наименования индексируемого поля");
b.HasKey("DiscriminatorId", "Index");
b.ToTable("scheme_property");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b =>
{
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Ключ");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания уставки");
b.Property<Guid>("IdUser")
.HasColumnType("uuid")
.HasComment("Id автора последнего изменения");
b.Property<JsonElement>("Value")
.HasColumnType("jsonb")
.HasComment("Значение уставки");
b.HasKey("DiscriminatorId", "Timestamp");
b.ToTable("setpoint");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>
{
b.Property<Guid>("EventId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Id события");
b.Property<int>("CategoryId")
.HasColumnType("integer")
.HasComment("Id Категории важности");
b.Property<int>("EventState")
.HasColumnType("integer")
.HasComment("Статус события");
b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы, к которой относится сообщение");
b.Property<string>("Text")
.IsRequired()
.HasColumnType("varchar(512)")
.HasComment("Текст сообщения");
b.Property<DateTimeOffset>("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<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор системы");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone")
.HasComment("Временная отметка");
b.Property<string>("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
}
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DD.Persistence.Database.Postgres.Migrations
{
/// <inheritdoc />
public partial class IdDiscriminatorUpdateToDiscriminatorId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Key",
table: "setpoint",
newName: "DiscriminatorId");
migrationBuilder.RenameColumn(
name: "IdDiscriminator",
table: "change_log",
newName: "DiscriminatorId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "DiscriminatorId",
table: "setpoint",
newName: "Key");
migrationBuilder.RenameColumn(
name: "DiscriminatorId",
table: "change_log",
newName: "IdDiscriminator");
}
}
}

View File

@ -34,14 +34,14 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи"); .HasComment("Дата создания записи");
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid>("IdAuthor") b.Property<Guid>("IdAuthor")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Автор изменения"); .HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor") b.Property<Guid?>("IdEditor")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Редактор"); .HasComment("Редактор");
@ -135,7 +135,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b => modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Ключ"); .HasComment("Ключ");
@ -151,7 +151,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasComment("Значение уставки"); .HasComment("Значение уставки");
b.HasKey("Key", "Timestamp"); b.HasKey("DiscriminatorId", "Timestamp");
b.ToTable("setpoint"); b.ToTable("setpoint");
}); });

View File

@ -11,13 +11,13 @@ namespace DD.Persistence.Database.Entity;
/// Часть записи, описывающая изменение /// Часть записи, описывающая изменение
/// </summary> /// </summary>
[Table("change_log")] [Table("change_log")]
public class ChangeLog : IChangeLog public class ChangeLog : IChangeLog, IDiscriminatorItem
{ {
[Key, Comment("Ключ записи")] [Key, Comment("Ключ записи")]
public Guid Id { get; set; } public Guid Id { get; set; }
[Comment("Дискриминатор таблицы")] [Comment("Дискриминатор таблицы")]
public Guid IdDiscriminator { get; set; } public Guid DiscriminatorId { get; set; }
[Comment("Автор изменения")] [Comment("Автор изменения")]
public Guid IdAuthor { get; set; } public Guid IdAuthor { get; set; }

View File

@ -6,11 +6,11 @@ using System.Text.Json;
namespace DD.Persistence.Database.Entity; namespace DD.Persistence.Database.Entity;
[Table("setpoint")] [Table("setpoint")]
[PrimaryKey(nameof(Key), nameof(Timestamp))] [PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
public class Setpoint : ITimestampedItem public class Setpoint : ITimestampedItem, IDiscriminatorItem
{ {
[Comment("Ключ")] [Comment("Ключ")]
public Guid Key { get; set; } public Guid DiscriminatorId { get; set; }
[Column(TypeName = "jsonb"), Comment("Значение уставки")] [Column(TypeName = "jsonb"), Comment("Значение уставки")]
public required JsonElement Value { get; set; } public required JsonElement Value { get; set; }

View File

@ -38,7 +38,7 @@ public interface IChangeLog
/// <summary> /// <summary>
/// Дискриминатор таблицы /// Дискриминатор таблицы
/// </summary> /// </summary>
public Guid IdDiscriminator { get; set; } public Guid DiscriminatorId { get; set; }
/// <summary> /// <summary>
/// Значение /// Значение

View File

@ -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
{
/// <summary>
/// Дискриминатор
/// </summary>
Guid DiscriminatorId { get; set; }
}

View File

@ -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.Postgres.Helpers;
using DD.Persistence.Database.Specifications.ChangeLog;
using DD.Persistence.Database.Specifications.Common.DiscriminatorItem;
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;
@ -12,10 +16,13 @@ namespace DD.Persistence.Database.Repositories;
public class ChangeLogRepository : IChangeLogRepository public class ChangeLogRepository : IChangeLogRepository
{ {
private readonly DbContext db; private readonly DbContext db;
private readonly ObsoleteIsNullSpec<ChangeLog> obsoleteIsNullSpecification;
public ChangeLogRepository(DbContext db) public ChangeLogRepository(DbContext db)
{ {
this.db = db; this.db = db;
this.obsoleteIsNullSpecification = new ObsoleteIsNullSpec<ChangeLog>();
} }
public async Task<int> AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
@ -35,9 +42,11 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<int> MarkAsDeleted(Guid idEditor, IEnumerable<Guid> ids, CancellationToken token) public async Task<int> MarkAsDeleted(Guid idEditor, IEnumerable<Guid> ids, CancellationToken token)
{ {
var containsIdsSpecification = new IdContainsSpec<ChangeLog>(ids);
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(s => ids.Contains(s.Id)) .WithSpecification(containsIdsSpecification)
.Where(s => s.Obsolete == null); .WithSpecification(obsoleteIsNullSpecification);
if (query.Count() != ids.Count()) if (query.Count() != ids.Count())
{ {
@ -53,9 +62,11 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<int> MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token) public async Task<int> MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token)
{ {
var specDiscriminatorEqual = new DiscriminatorEqualSpec<ChangeLog>(idDiscriminator);
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(s => s.IdDiscriminator == idDiscriminator) .WithSpecification(specDiscriminatorEqual)
.Where(e => e.Obsolete == null); .WithSpecification(obsoleteIsNullSpecification);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
@ -97,8 +108,10 @@ public class ChangeLogRepository : IChangeLogRepository
var dbSet = db.Set<ChangeLog>(); var dbSet = db.Set<ChangeLog>();
var updatedIds = dtos.Select(d => d.Id); var updatedIds = dtos.Select(d => d.Id);
var containsIdsSpecification = new IdContainsSpec<ChangeLog>(updatedIds);
var updatedEntities = dbSet var updatedEntities = dbSet
.Where(s => updatedIds.Contains(s.Id)) .WithSpecification(containsIdsSpecification)
.ToDictionary(s => s.Id); .ToDictionary(s => s.Id);
var result = 0; 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)); 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); dbSet.Add(newEntity);
updatedEntity.IdNext = newEntity.Id; updatedEntity.IdNext = newEntity.Id;
@ -134,7 +147,11 @@ public class ChangeLogRepository : IChangeLogRepository
PaginationRequest paginationRequest, PaginationRequest paginationRequest,
CancellationToken token) CancellationToken token)
{ {
var query = CreateQuery(idDiscriminator); var specDiscriminatorEqual = new DiscriminatorEqualSpec<ChangeLog>(idDiscriminator);
var query = db.Set<ChangeLog>()
.WithSpecification(specDiscriminatorEqual);
query = query.Apply(momentUtc); query = query.Apply(momentUtc);
var result = await query.ApplyPagination(paginationRequest, Convert, token); var result = await query.ApplyPagination(paginationRequest, Convert, token);
@ -142,22 +159,21 @@ public class ChangeLogRepository : IChangeLogRepository
return result; return result;
} }
private IQueryable<ChangeLog> CreateQuery(Guid idDiscriminator)
{
var query = db.Set<ChangeLog>().Where(e => e.IdDiscriminator == idDiscriminator);
return query;
}
public async Task<IEnumerable<ChangeLogDto>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) public async Task<IEnumerable<ChangeLogDto>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
{ {
var query = db.Set<ChangeLog>().Where(s => s.IdDiscriminator == idDiscriminator); var specDiscriminatorEqual = new DiscriminatorEqualSpec<ChangeLog>(idDiscriminator);
var query = db.Set<ChangeLog>()
.WithSpecification(specDiscriminatorEqual);
var min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero); var min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero);
var max = new DateTimeOffset(dateEnd.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 specCreatedByDateRange = new FromCreationDateRangeSpec<ChangeLog>(min, max);
var editedQuery = query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max); var specHistoryByDateRange = new FromObsoleteDateRangeSpece<ChangeLog>(min, max);
var createdQuery = query.WithSpecification(specCreatedByDateRange);
var editedQuery = query.WithSpecification(specHistoryByDateRange);
query = createdQuery.Union(editedQuery); query = createdQuery.Union(editedQuery);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
@ -171,7 +187,11 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<IEnumerable<DateOnly>> GetDatesChange(Guid idDiscriminator, CancellationToken token) public async Task<IEnumerable<DateOnly>> GetDatesChange(Guid idDiscriminator, CancellationToken token)
{ {
var query = db.Set<ChangeLog>().Where(e => e.IdDiscriminator == idDiscriminator); var specDiscriminatorEqual = new DiscriminatorEqualSpec<ChangeLog>(idDiscriminator);
var specObsoleteNotNull = new ObsoleteNotNullSpec<ChangeLog>();
var query = db.Set<ChangeLog>()
.WithSpecification(specDiscriminatorEqual);
var datesCreateQuery = query var datesCreateQuery = query
.Select(e => e.Creation) .Select(e => e.Creation)
@ -180,7 +200,7 @@ public class ChangeLogRepository : IChangeLogRepository
var datesCreate = await datesCreateQuery.ToArrayAsync(token); var datesCreate = await datesCreateQuery.ToArrayAsync(token);
var datesUpdateQuery = query var datesUpdateQuery = query
.Where(e => e.Obsolete != null) .WithSpecification(specObsoleteNotNull)
.Select(e => e.Obsolete!.Value) .Select(e => e.Obsolete!.Value)
.Distinct(); .Distinct();
@ -202,7 +222,7 @@ public class ChangeLogRepository : IChangeLogRepository
Id = Uuid7.Guid(), Id = Uuid7.Guid(),
Creation = DateTimeOffset.UtcNow, Creation = DateTimeOffset.UtcNow,
IdAuthor = idAuthor, IdAuthor = idAuthor,
IdDiscriminator = idDiscriminator, DiscriminatorId = idDiscriminator,
IdEditor = idAuthor, IdEditor = idAuthor,
Value = dto.Value Value = dto.Value
@ -214,9 +234,13 @@ 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 specDiscriminatorEqual = new DiscriminatorEqualSpec<ChangeLog>(idDiscriminator);
var specCreatedOrUpdatedGeDate = new CreatedOrUpdatedGeDateSpec<ChangeLog>(date);
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator) .WithSpecification(specDiscriminatorEqual)
.Where(e => e.Creation >= date || e.Obsolete >= date); .WithSpecification(specCreatedOrUpdatedGeDate);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
@ -226,30 +250,35 @@ public class ChangeLogRepository : IChangeLogRepository
} }
public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token) public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token)
{
var query = db.Set<ChangeLog>()
.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),
});
var values = await query.FirstOrDefaultAsync(token);
if (values is null)
{ {
return null; return null;
} //var specDiscriminatorEqual = new DiscriminatorEqualSpec<ChangeLog>(idDiscriminator);
//var evaluator =
// SpecificationEvaluator.Default
//var query = db.Set<ChangeLog>()
// //.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),
// });
return new DatesRangeDto //var values = await query.FirstOrDefaultAsync(token);
{
From = values.Min, //if (values is null)
To = values.Max, //{
}; // return null;
//}
//return new DatesRangeDto
//{
// From = values.Min,
// To = values.Max,
//};
} }
private ChangeLogValuesDto Convert(ChangeLog entity) => entity.Adapt<ChangeLogValuesDto>(); private ChangeLogValuesDto Convert(ChangeLog entity) => entity.Adapt<ChangeLogValuesDto>();

View File

@ -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<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> 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<T>;
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);
}
}

View File

@ -1,4 +1,8 @@
using Ardalis.Specification;
using Ardalis.Specification.EntityFrameworkCore;
using DD.Persistence.Database.Entity; 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;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
@ -24,9 +28,11 @@ namespace DD.Persistence.Database.Postgres.Repositories
{ {
var query = GetQueryReadOnly(); var query = GetQueryReadOnly();
var entities = await query var specDiscriminatorContains = new DiscriminatorContainsSpec<Setpoint>(setpointKeys);
.Where(e => setpointKeys.Contains(e.Key))
.GroupBy(e => e.Key) var entities = await db.Set<Setpoint>()
.WithSpecification(specDiscriminatorContains)
.GroupBy(e => e.DiscriminatorId)
.Select(g => g.OrderByDescending(x => x.Timestamp).FirstOrDefault()) .Select(g => g.OrderByDescending(x => x.Timestamp).FirstOrDefault())
.ToArrayAsync(token); .ToArrayAsync(token);
@ -37,23 +43,27 @@ namespace DD.Persistence.Database.Postgres.Repositories
{ {
var query = GetQueryReadOnly(); var query = GetQueryReadOnly();
var entities = await query var specDiscriminatorContains = new DiscriminatorContainsSpec<Setpoint>(setpointKeys);
.Where(e => setpointKeys.Contains(e.Key))
.GroupBy(e => e.Key) var entities = await db.Set<Setpoint>()
.WithSpecification(specDiscriminatorContains)
.GroupBy(e => e.DiscriminatorId)
.Select(g => g.OrderByDescending(x => x.Timestamp).FirstOrDefault()) .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; return entities;
} }
public async Task<IEnumerable<SetpointValueDto>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment, CancellationToken token) public async Task<IEnumerable<SetpointValueDto>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment, CancellationToken token)
{ {
var query = GetQueryReadOnly(); var specDiscriminatorContains = new DiscriminatorContainsSpec<Setpoint>(setpointKeys);
var entities = await query
.Where(e => setpointKeys.Contains(e.Key)) var entities = await db.Set<Setpoint>()
.WithSpecification(specDiscriminatorContains)
.ToArrayAsync(token); .ToArrayAsync(token);
var filteredEntities = entities var filteredEntities = entities
.GroupBy(e => e.Key) .GroupBy(e => e.DiscriminatorId)
.Select(e => e.OrderBy(o => o.Timestamp)) .Select(e => e.OrderBy(o => o.Timestamp))
.Select(e => e.Where(e => e.Timestamp <= historyMoment).Last()); .Select(e => e.Where(e => e.Timestamp <= historyMoment).Last());
var dtos = filteredEntities var dtos = filteredEntities
@ -77,31 +87,35 @@ namespace DD.Persistence.Database.Postgres.Repositories
public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token) public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token)
{ {
var query = GetQueryReadOnly()
var spec = new DatesRangeSpec<Setpoint>();
var query = db.Set<Setpoint>()
.WithSpecification(spec, MySpecificationEvaluator.GroupBy);
var query2 = GetQueryReadOnly()
.GroupBy(e => 1) .GroupBy(e => 1)
.Select(group => new .Select(group => new
{ {
Min = group.Min(e => e.Timestamp), Min = group.Min(e => e.Timestamp),
Max = group.Max(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<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token) public async Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token)
{ {
var query = GetQueryReadOnly(); var specDiscriminatorContains = new DiscriminatorContainsSpec<Setpoint>(setpointKeys);
var entities = await query
.Where(e => setpointKeys.Contains(e.Key)) var entities = await db.Set<Setpoint>()
.WithSpecification(specDiscriminatorContains)
.ToArrayAsync(token); .ToArrayAsync(token);
var dtos = entities var dtos = entities
.GroupBy(e => e.Key) .GroupBy(e => e.DiscriminatorId)
.ToDictionary(e => e.Key, v => v.Select(z => z.Adapt<SetpointLogDto>())); .ToDictionary(e => e.Key, v => v.Select(z => z.Adapt<SetpointLogDto>()));
return dtos; return dtos;
@ -111,7 +125,7 @@ namespace DD.Persistence.Database.Postgres.Repositories
{ {
var entity = new Setpoint() var entity = new Setpoint()
{ {
Key = setpointKey, DiscriminatorId = setpointKey,
Value = newValue, Value = newValue,
IdUser = idUser, IdUser = idUser,
Timestamp = DateTimeOffset.UtcNow.ToUniversalTime() Timestamp = DateTimeOffset.UtcNow.ToUniversalTime()

View File

@ -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<TEntity> : Specification<TEntity>
where TEntity : IChangeLog
{
public CreatedOrUpdatedGeDateSpec(DateTimeOffset date)
{
Query.Where(e => e.Creation >= date || e.Obsolete >= date);
}
}

View File

@ -0,0 +1,25 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ChangeLog;
/// <summary>
/// Спецификация для поиска созданных за определённый период записей IChangeLog
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class FromCreationDateRangeSpec<TEntity> : Specification<TEntity>
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);
}
}

View File

@ -0,0 +1,18 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ChangeLog;
/// <summary>
/// Спецификация для поиска исторических записей IChangeLog за определённый период
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class FromObsoleteDateRangeSpece<TEntity> : Specification<TEntity>
where TEntity : IChangeLog
{
public FromObsoleteDateRangeSpece(DateTimeOffset min, DateTimeOffset max)
{
Query.Where(e => e.Obsolete != null && e.Obsolete >= min && e.Obsolete <= max);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ChangeLog;
/// <summary>
/// Спецификация для поиска записей IChangeLog по массиву ключей
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class IdContainsSpec<TEntity> : Specification<TEntity>
where TEntity : IChangeLog
{
public IdContainsSpec()
{
}
public IdContainsSpec(IEnumerable<Guid> ids)
{
Query.Where(s => ids.Contains(s.Id));
}
}

View File

@ -0,0 +1,18 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ChangeLog;
/// <summary>
/// Спецификация для актуальных значений IChangeLog
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ObsoleteIsNullSpec<TEntity> : Specification<TEntity>
where TEntity : IChangeLog
{
public ObsoleteIsNullSpec()
{
Query.Where(e => e.Obsolete == null);
}
}

View File

@ -0,0 +1,18 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ChangeLog;
/// <summary>
/// Спецификация для поиска не актуальных значений IChangeLog
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ObsoleteNotNullSpec<TEntity> : Specification<TEntity>
where TEntity : IChangeLog
{
public ObsoleteNotNullSpec()
{
Query.Where(e => e.Obsolete != null);
}
}

View File

@ -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<TEntity> : Specification<TEntity>
where TEntity : IDiscriminatorItem
{
public DatesRangeSpec()
{
//Query;
}
}

View File

@ -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<TEntity> : Specification<TEntity>
where TEntity : IDiscriminatorItem
{
public DiscriminatorContainsSpec(IEnumerable<Guid> discriminatorIds)
{
Query.Where(e => discriminatorIds.Contains(e.DiscriminatorId));
}
}

View File

@ -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<TEntity> : Specification<TEntity>
where TEntity : IDiscriminatorItem
{
public DiscriminatorEqualSpec(Guid discriminatorId)
{
Query.Where(e => e.DiscriminatorId == discriminatorId);
}
}

View File

@ -102,7 +102,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var result = await client.Add(idDiscriminator, dto, new CancellationToken()); var result = await client.Add(idDiscriminator, dto, new CancellationToken());
var entity = dbContext.ChangeLog var entity = dbContext.ChangeLog
.Where(x => x.IdDiscriminator == idDiscriminator) .Where(x => x.DiscriminatorId == idDiscriminator)
.FirstOrDefault(); .FirstOrDefault();
dto = entity.Adapt<ChangeLogValuesDto>(); dto = entity.Adapt<ChangeLogValuesDto>();
@ -318,7 +318,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var entities = dtos.Select(d => var entities = dtos.Select(d =>
{ {
var entity = d.Adapt<ChangeLog>(); var entity = d.Adapt<ChangeLog>();
entity.IdDiscriminator = idDiscriminator; entity.DiscriminatorId = idDiscriminator;
entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount)); entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount));
return entity; return entity;