Добавить таблицу для учета комментариев и действий пользователя для вывода статистики по ChangeLog #30

Open
on.nemtina wants to merge 24 commits from feature/#956-change-log-table-comment into master
13 changed files with 294 additions and 80 deletions
Showing only changes of commit b3c6acbd18 - Show all commits

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
{ {
@ -25,10 +25,13 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
public async Task<IActionResult> Add( public async Task<IActionResult> Add(

Должен остаться только один

Должен остаться только один
[FromRoute] Guid idDiscriminator, [FromRoute] Guid idDiscriminator,
[FromBody] ChangeLogValuesDto dto, [FromBody] ChangeLogValuesDto dto,

Было бы здорова дать методам контроллеров текстовое описание для сваггера.

Было бы здорова дать методам контроллеров текстовое описание для сваггера.
string comment,
CancellationToken token) CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); //var userId = User.GetUserId<Guid>();
var result = await repository.AddRange(userId, idDiscriminator, [dto], token); var userId = Guid.NewGuid();
var changeLogCommit = new ChangeLogCommitDto(userId, comment, [dto]);

Скорее всего этот метод лишний.

Скорее всего этот метод лишний.
var result = await repository.AddRange(idDiscriminator, changeLogCommit, token);
return CreatedAtAction(nameof(Add), result); return CreatedAtAction(nameof(Add), result);
} }
@ -38,10 +41,13 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
public async Task<IActionResult> AddRange( public async Task<IActionResult> AddRange(
[FromRoute] Guid idDiscriminator, [FromRoute] Guid idDiscriminator,
[FromBody] IEnumerable<ChangeLogValuesDto> dtos, [FromBody] IEnumerable<ChangeLogValuesDto> dtos,
string comment,
CancellationToken token) CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); //var userId = User.GetUserId<Guid>();
var result = await repository.AddRange(userId, idDiscriminator, dtos, token); var userId = Guid.NewGuid();
var changeLogCommit = new ChangeLogCommitDto(userId, comment, dtos);
var result = await repository.AddRange(idDiscriminator, changeLogCommit, token);
return CreatedAtAction(nameof(AddRange), result); return CreatedAtAction(nameof(AddRange), result);
} }
@ -71,10 +77,13 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
public async Task<IActionResult> ClearAndAddRange( public async Task<IActionResult> ClearAndAddRange(
[FromRoute] Guid idDiscriminator, [FromRoute] Guid idDiscriminator,
[FromBody] IEnumerable<ChangeLogValuesDto> dtos, [FromBody] IEnumerable<ChangeLogValuesDto> dtos,
string comment,
CancellationToken token) CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); //var userId = User.GetUserId<Guid>();
var result = await repository.ClearAndAddRange(userId, idDiscriminator, dtos, token); var userId = Guid.NewGuid();
var changeLogCommit = new ChangeLogCommitDto(userId, comment, dtos);
var result = await repository.ClearAndAddRange(idDiscriminator, changeLogCommit, token);
return Ok(result); return Ok(result);
} }
@ -82,10 +91,13 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Update( public async Task<IActionResult> Update(
ChangeLogValuesDto dto, ChangeLogValuesDto dto,
string comment,
CancellationToken token) CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); //var userId = User.GetUserId<Guid>();
var result = await repository.UpdateRange(userId, [dto], token); var userId = Guid.NewGuid();
var changeLogCommit = new ChangeLogCommitDto(userId, comment, [dto]);
var result = await repository.UpdateRange(changeLogCommit, token);
return Ok(result); return Ok(result);
} }
@ -94,10 +106,13 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> UpdateRange( public async Task<IActionResult> UpdateRange(
IEnumerable<ChangeLogValuesDto> dtos, IEnumerable<ChangeLogValuesDto> dtos,
string comment,
CancellationToken token) CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); //var userId = User.GetUserId<Guid>();
var result = await repository.UpdateRange(userId, dtos, token); var userId = Guid.NewGuid();
var changeLogCommit = new ChangeLogCommitDto(userId, comment, dtos);
var result = await repository.UpdateRange(changeLogCommit, token);
return Ok(result); return Ok(result);
} }

View File

@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DD.Persistence.Database.Postgres.Migrations namespace DD.Persistence.Database.Postgres.Migrations
{ {
[DbContext(typeof(PersistencePostgresContext))] [DbContext(typeof(PersistencePostgresContext))]
[Migration("20250205114037_Init")] [Migration("20250211124554_Init")]
partial class Init partial class Init
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -41,14 +41,14 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Автор изменения"); .HasComment("Автор изменения");
b.Property<Guid>("IdCommit")
.HasColumnType("uuid")
.HasComment("Id коммита");
b.Property<Guid>("IdDiscriminator") b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Дискриминатор таблицы"); .HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor")
.HasColumnType("uuid")
.HasComment("Редактор");
b.Property<Guid?>("IdNext") b.Property<Guid?>("IdNext")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Id заменяющей записи"); .HasComment("Id заменяющей записи");
@ -64,9 +64,36 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("IdCommit");
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Id коммита");
b.Property<string>("Comment")
.IsRequired()
.HasColumnType("text")
.HasComment("Комментарий к коммиту");
b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания коммита");
b.Property<Guid>("IdCommitAuthor")
.HasColumnType("uuid")
.HasComment("Пользователь, создавший коммит");
b.HasKey("Id");
b.ToTable("change_log_commit");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
@ -214,6 +241,17 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("timestamped_values"); b.ToTable("timestamped_values");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLog", b =>
{
b.HasOne("DD.Persistence.Database.Entity.ChangeLogCommit", "Commit")
.WithMany("ChangeLogItems")
.HasForeignKey("IdCommit")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Commit");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>
{ {
b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System") b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System")
@ -224,6 +262,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); b.Navigation("System");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{
b.Navigation("ChangeLogItems");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -13,21 +13,17 @@ namespace DD.Persistence.Database.Postgres.Migrations
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "change_log", name: "change_log_commit",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ записи"), Id = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id коммита"),
IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор таблицы"), IdCommitAuthor = table.Column<Guid>(type: "uuid", nullable: false, comment: "Пользователь, создавший коммит"),
IdAuthor = table.Column<Guid>(type: "uuid", nullable: false, comment: "Автор изменения"), Creation = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата создания коммита"),
IdEditor = table.Column<Guid>(type: "uuid", nullable: true, comment: "Редактор"), Comment = table.Column<string>(type: "text", nullable: false, comment: "Комментарий к коммиту")
Creation = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата создания записи"),
Obsolete = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true, comment: "Дата устаревания (например при удалении)"),
IdNext = table.Column<Guid>(type: "uuid", nullable: true, comment: "Id заменяющей записи"),
Value = table.Column<string>(type: "jsonb", nullable: false, comment: "Значение")
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_change_log", x => x.Id); table.PrimaryKey("PK_change_log_commit", x => x.Id);
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
@ -98,6 +94,30 @@ namespace DD.Persistence.Database.Postgres.Migrations
table.PrimaryKey("PK_timestamped_values", x => new { x.DiscriminatorId, x.Timestamp }); table.PrimaryKey("PK_timestamped_values", x => new { x.DiscriminatorId, x.Timestamp });
}); });
migrationBuilder.CreateTable(
name: "change_log",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ записи"),
IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор таблицы"),
IdAuthor = table.Column<Guid>(type: "uuid", nullable: false, comment: "Автор изменения"),
Creation = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата создания записи"),
Obsolete = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true, comment: "Дата устаревания (например при удалении)"),
IdNext = table.Column<Guid>(type: "uuid", nullable: true, comment: "Id заменяющей записи"),
Value = table.Column<string>(type: "jsonb", nullable: false, comment: "Значение"),
IdCommit = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id коммита")
},
constraints: table =>
{
table.PrimaryKey("PK_change_log", x => x.Id);
table.ForeignKey(
name: "FK_change_log_change_log_commit_IdCommit",
column: x => x.IdCommit,
principalTable: "change_log_commit",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "tech_message", name: "tech_message",
columns: table => new columns: table => new
@ -120,6 +140,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); });
migrationBuilder.CreateIndex(
name: "IX_change_log_IdCommit",
table: "change_log",
column: "IdCommit");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_tech_message_SystemId", name: "IX_tech_message_SystemId",
table: "tech_message", table: "tech_message",
@ -147,6 +172,9 @@ namespace DD.Persistence.Database.Postgres.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "timestamped_values"); name: "timestamped_values");
migrationBuilder.DropTable(
name: "change_log_commit");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "data_source_system"); name: "data_source_system");
} }

View File

@ -38,14 +38,14 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Автор изменения"); .HasComment("Автор изменения");
b.Property<Guid>("IdCommit")
.HasColumnType("uuid")
.HasComment("Id коммита");
b.Property<Guid>("IdDiscriminator") b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Дискриминатор таблицы"); .HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor")
.HasColumnType("uuid")
.HasComment("Редактор");
b.Property<Guid?>("IdNext") b.Property<Guid?>("IdNext")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Id заменяющей записи"); .HasComment("Id заменяющей записи");
@ -61,9 +61,36 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("IdCommit");
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Id коммита");
b.Property<string>("Comment")
.IsRequired()
.HasColumnType("text")
.HasComment("Комментарий к коммиту");
b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания коммита");
b.Property<Guid>("IdCommitAuthor")
.HasColumnType("uuid")
.HasComment("Пользователь, создавший коммит");
b.HasKey("Id");
b.ToTable("change_log_commit");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
@ -211,6 +238,17 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("timestamped_values"); b.ToTable("timestamped_values");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLog", b =>
{
b.HasOne("DD.Persistence.Database.Entity.ChangeLogCommit", "Commit")
.WithMany("ChangeLogItems")
.HasForeignKey("IdCommit")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Commit");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("DD.Persistence.Database.Entity.TechMessage", b =>
{ {
b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System") b.HasOne("DD.Persistence.Database.Entity.DataSourceSystem", "System")
@ -221,6 +259,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); b.Navigation("System");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{
b.Navigation("ChangeLogItems");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -22,9 +22,6 @@ public class ChangeLog : IChangeLog
[Comment("Автор изменения")] [Comment("Автор изменения")]
public Guid IdAuthor { get; set; } public Guid IdAuthor { get; set; }
Review

Комментарий про денормализацию БД

Комментарий про денормализацию БД
[Comment("Редактор")]
public Guid? IdEditor { get; set; }
[Comment("Дата создания записи")] [Comment("Дата создания записи")]
public DateTimeOffset Creation { get; set; } public DateTimeOffset Creation { get; set; }
@ -36,4 +33,10 @@ public class ChangeLog : IChangeLog
[Column(TypeName = "jsonb"), Comment("Значение")] [Column(TypeName = "jsonb"), Comment("Значение")]
public required IDictionary<string, object> Value { get; set; } public required IDictionary<string, object> Value { get; set; }
[Required, Comment("Id коммита")]
public Guid IdCommit { get; set; }

Для устаревших записей у нас есть 2 коммита: один при создании записи и еще один при устаревании.
Кстати туда же можно унести инфо о пользователях и датах.

Для устаревших записей у нас есть 2 коммита: один при создании записи и еще один при устаревании. Кстати туда же можно унести инфо о пользователях и датах.
[Required, ForeignKey(nameof(IdCommit)), Comment("Коммит пользователя")]
public virtual ChangeLogCommit Commit { get; set; } = null!;
} }

View File

@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DD.Persistence.Database.Entity;
/// <summary>
/// Таблица c коммитами пользователей
/// </summary>
[Table("change_log_commit")]
public class ChangeLogCommit
{
[Key, Comment("Id коммита")]
public Guid Id { get; set; }
[Comment("Пользователь, создавший коммит")]
public Guid IdCommitAuthor { get; set; }
[Comment("Дата создания коммита")]
public DateTimeOffset Creation { get; set; }
[Comment("Комментарий к коммиту")]
public required string Comment { get; set; }
[Required, InverseProperty(nameof(ChangeLog.Commit)), Comment("Журнал изменений")]
public virtual ICollection<ChangeLog> ChangeLogItems { get; set; } = null!;
}

View File

@ -17,15 +17,15 @@ public class TechMessage : ITimestampedItem
[Comment("Дата возникновения")] [Comment("Дата возникновения")]
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")] [Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
public required string Text { get; set; } public required string Text { get; set; }
[Required, Comment("Id системы, к которой относится сообщение")] [Required, Comment("Id системы, к которой относится сообщение")]
public required Guid SystemId { get; set; } public required Guid SystemId { get; set; }
[Required, ForeignKey(nameof(SystemId)), Comment("Система, к которой относится сообщение")] [Required, ForeignKey(nameof(SystemId)), Comment("Система, к которой относится сообщение")]
public virtual required DataSourceSystem System { get; set; } public virtual required DataSourceSystem System { get; set; }
[Comment("Статус события")] [Comment("Статус события")]
public int EventState { get; set; } public int EventState { get; set; }
} }

View File

@ -15,11 +15,6 @@ public interface IChangeLog
/// </summary> /// </summary>
public Guid IdAuthor { get; set; } public Guid IdAuthor { get; set; }
/// <summary>
/// Редактор
/// </summary>
public Guid? IdEditor { get; set; }

Свойства не должны быть реализованы в интерфейсе.
А почему автор коммита устаревания удален, а автор коммита создания нет?

Свойства не должны быть реализованы в интерфейсе. А почему автор коммита устаревания удален, а автор коммита создания нет?
/// <summary> /// <summary>
/// Дата создания записи /// Дата создания записи
/// </summary> /// </summary>

View File

@ -16,6 +16,8 @@ public class PersistenceDbContext : DbContext
public DbSet<ChangeLog> ChangeLog => Set<ChangeLog>(); public DbSet<ChangeLog> ChangeLog => Set<ChangeLog>();
public DbSet<ChangeLogCommit> ChangeLogAction => Set<ChangeLogCommit>();
public DbSet<TechMessage> TechMessage => Set<TechMessage>(); public DbSet<TechMessage> TechMessage => Set<TechMessage>();
public DbSet<ParameterData> ParameterData => Set<ParameterData>(); public DbSet<ParameterData> ParameterData => Set<ParameterData>();

View File

@ -18,12 +18,15 @@ public class ChangeLogRepository : IChangeLogRepository
this.db = db; this.db = db;
} }
public async Task<int> AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> AddRange(Guid idDiscriminator, ChangeLogCommitDto commitDto, CancellationToken token)
{ {
var commit = CreateCommit(commitDto);
db.Set<ChangeLogCommit>().Add(commit);
var entities = new List<ChangeLog>(); var entities = new List<ChangeLog>();
foreach (var dto in dtos) foreach (var values in commitDto.ChangeLogItems)
{ {
var entity = CreateEntityFromDto(idAuthor, idDiscriminator, dto); var entity = CreateChangeLogFromDto(idDiscriminator, commit.Id, commit.IdCommitAuthor, values);
entities.Add(entity); entities.Add(entity);
} }
db.Set<ChangeLog>().AddRange(entities); db.Set<ChangeLog>().AddRange(entities);
@ -71,20 +74,20 @@ public class ChangeLogRepository : IChangeLogRepository
foreach (var entity in entities) foreach (var entity in entities)
{ {
entity.Obsolete = updateTime; entity.Obsolete = updateTime;
entity.IdEditor = idEditor; //entity.IdEditor = idEditor;
} }

Это не правда

Это не правда
return await db.SaveChangesAsync(token); return await db.SaveChangesAsync(token);
} }
public async Task<int> ClearAndAddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> ClearAndAddRange(Guid idDiscriminator, ChangeLogCommitDto commitDto, CancellationToken token)
{ {
var result = 0; var result = 0;

Этот метод должен помечать все записи относящиеся к дискриминатору как удаленные и добавлять новые.

Этот метод должен помечать все записи относящиеся к дискриминатору как удаленные и добавлять новые.
using var transaction = await db.Database.BeginTransactionAsync(token); using var transaction = await db.Database.BeginTransactionAsync(token);
result += await MarkAsDeleted(idAuthor, idDiscriminator, token); result += await MarkAsDeleted(commitDto.IdAuthor, idDiscriminator, token);
result += await AddRange(idAuthor, idDiscriminator, dtos, token); result += await AddRange(idDiscriminator, commitDto, token);
Review

эта логкальная переменная дальше нигде не используется. Зачем она здесь?

эта логкальная переменная дальше нигде не используется. Зачем она здесь?
await transaction.CommitAsync(token); await transaction.CommitAsync(token);
@ -92,19 +95,26 @@ public class ChangeLogRepository : IChangeLogRepository
return result; return result;
} }
public async Task<int> UpdateRange(Guid idEditor, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> UpdateRange(ChangeLogCommitDto commitDto, CancellationToken token)
{ {
var dbSet = db.Set<ChangeLog>(); var dbSet = db.Set<ChangeLog>();
var updatedIds = dtos.Select(d => d.Id); var updatedIds = commitDto.ChangeLogItems.Select(d => d.Id);
var updatedEntities = dbSet var updatedEntities = dbSet
.Where(s => updatedIds.Contains(s.Id)) .Where(s => updatedIds.Contains(s.Id))
.ToDictionary(s => s.Id); .ToDictionary(s => s.Id);
var result = 0; var result = 0;
using var transaction = await db.Database.BeginTransactionAsync(token);
foreach (var dto in dtos) var commit = CreateCommit(commitDto);
db.Set<ChangeLogCommit>().Add(commit);

Linq очень не оптимально материализует в словари.
Тут лучше материализовать сперва в массив, а затем массив в словарь.

  • Используй асинхронные методы материализации в асинхронных методах репозиториев
Linq очень не оптимально материализует в словари. Тут лучше материализовать сперва в массив, а затем массив в словарь. + Используй асинхронные методы материализации в асинхронных методах репозиториев
db.SaveChanges();
//using var transaction = await db.Database.BeginTransactionAsync(token);
foreach (var dto in commitDto.ChangeLogItems)
{ {
var updatedEntity = updatedEntities.GetValueOrDefault(dto.Id); var updatedEntity = updatedEntities.GetValueOrDefault(dto.Id);
if (updatedEntity is null) if (updatedEntity is null)
@ -112,16 +122,15 @@ 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 = CreateChangeLogFromDto(commitDto.IdAuthor, updatedEntity.IdDiscriminator, commit.Id, dto);
dbSet.Add(newEntity); dbSet.Add(newEntity);
updatedEntity.IdNext = newEntity.Id; updatedEntity.IdNext = newEntity.Id;
updatedEntity.Obsolete = DateTimeOffset.UtcNow; updatedEntity.Obsolete = DateTimeOffset.UtcNow;
updatedEntity.IdEditor = idEditor;
} }
result = await db.SaveChangesAsync(token); result = await db.SaveChangesAsync(token);
await transaction.CommitAsync(token); //await transaction.CommitAsync(token);
return result; return result;
@ -195,7 +204,7 @@ public class ChangeLogRepository : IChangeLogRepository
return datesOnly; return datesOnly;
} }
private static ChangeLog CreateEntityFromDto(Guid idAuthor, Guid idDiscriminator, ChangeLogValuesDto dto) private static ChangeLog CreateChangeLogFromDto(Guid idDiscriminator, Guid idCommit, Guid idAuthor, ChangeLogValuesDto dto)
{ {
var entity = new ChangeLog() var entity = new ChangeLog()
{ {
@ -203,14 +212,23 @@ public class ChangeLogRepository : IChangeLogRepository
Creation = DateTimeOffset.UtcNow, Creation = DateTimeOffset.UtcNow,
IdAuthor = idAuthor, IdAuthor = idAuthor,
IdDiscriminator = idDiscriminator, IdDiscriminator = idDiscriminator,
IdEditor = idAuthor, Value = dto.Value,
IdCommit = idCommit,
Value = dto.Value
}; };
return entity; return entity;
} }
private static ChangeLogCommit CreateCommit(ChangeLogCommitDto commitDto)
{
return new ChangeLogCommit()
{
Comment = commitDto.Comment,
Creation = DateTimeOffset.UtcNow,
IdCommitAuthor = commitDto.IdAuthor
};
}
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();

View File

@ -0,0 +1,33 @@
namespace DD.Persistence.Models.Requests;
/// <summary>
/// Модель коммита с изменениями
/// </summary>
public class ChangeLogCommitDto
{
/// <summary>
/// Пользователь, совершающий коммит
/// </summary>
public Guid IdAuthor { get; set; }

По правильному стоит разделить эту Dto на 2. Сделать отдельную dto для создания нового коммита, так как это поле генерируется внутри репозитория.

По правильному стоит разделить эту Dto на 2. Сделать отдельную dto для создания нового коммита, так как это поле генерируется внутри репозитория.
/// <summary>
/// Комментарий
/// </summary>
public string Comment { get; set; }
/// <summary>
/// Набор изменений
/// </summary>
public IEnumerable<ChangeLogValuesDto> ChangeLogItems { get; set; }
/// <summary>
///
/// </summary>
public ChangeLogCommitDto(Guid idAuthor, string comment, IEnumerable<ChangeLogValuesDto> changeLogItems)
{
IdAuthor = idAuthor;
Comment = comment;
ChangeLogItems = changeLogItems;
}
}

View File

@ -14,9 +14,10 @@ public interface IChangeLogApi : ISyncWithDiscriminatorApi<ChangeLogValuesDto>
/// </summary> /// </summary>
/// <param name="idDiscriminator"></param> /// <param name="idDiscriminator"></param>
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="comment"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<IActionResult> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Получение данных на текущую дату (с пагинацией) /// Получение данных на текущую дату (с пагинацией)
@ -52,34 +53,38 @@ public interface IChangeLogApi : ISyncWithDiscriminatorApi<ChangeLogValuesDto>
/// </summary> /// </summary>
/// <param name="idDiscriminator"></param> /// <param name="idDiscriminator"></param>
/// <param name="dto"></param> /// <param name="dto"></param>
/// <param name="comment">комментарий</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> Add(Guid idDiscriminator, ChangeLogValuesDto dto, CancellationToken token); Task<IActionResult> Add(Guid idDiscriminator, ChangeLogValuesDto dto, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Добавить несколько записей /// Добавить несколько записей
/// </summary> /// </summary>
/// <param name="idDiscriminator"></param> /// <param name="idDiscriminator"></param>
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="comment">комментарий</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<IActionResult> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Обновить одну запись /// Обновить одну запись
/// </summary> /// </summary>
/// <param name="dto"></param> /// <param name="dto"></param>
/// <param name="comment"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> Update(ChangeLogValuesDto dto, CancellationToken token); Task<IActionResult> Update(ChangeLogValuesDto dto, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Обновить несколько записей /// Обновить несколько записей
/// </summary> /// </summary>
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="comment">комментарий</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<IActionResult> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Удалить одну запись /// Удалить одну запись

View File

@ -1,24 +1,23 @@
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;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
namespace DD.Persistence.Repositories; namespace DD.Persistence.Repositories;
/// <summary> /// <summary>
/// Интерфейс для работы с историческими данными /// Интерфейс для работы с историческими данными
/// </summary> /// </summary>
/// <typeparam name="TDto"></typeparam>
public interface IChangeLogRepository : ISyncWithDiscriminatorRepository<ChangeLogValuesDto> public interface IChangeLogRepository : ISyncWithDiscriminatorRepository<ChangeLogValuesDto>
{ {
/// <summary> /// <summary>
/// Добавление записей /// Добавление записей
/// </summary> /// </summary>
/// <param name="idAuthor">пользователь, который добавляет</param>
/// <param name="idDiscriminator">ключ справочника</param> /// <param name="idDiscriminator">ключ справочника</param>
/// <param name="dtos"></param> /// <param name="dto">коммит с изменениями</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<int> AddRange(Guid idDiscriminator, ChangeLogCommitDto dto, CancellationToken token);
/// <summary> /// <summary>
/// Пометить записи как удаленные /// Пометить записи как удаленные
@ -41,21 +40,19 @@ public interface IChangeLogRepository : ISyncWithDiscriminatorRepository<ChangeL
/// <summary> /// <summary>
/// Очистить и добавить новые /// Очистить и добавить новые
/// </summary> /// </summary>
/// <param name="idAuthor"></param>
/// <param name="idDiscriminator"></param> /// <param name="idDiscriminator"></param>
/// <param name="dtos"></param> /// <param name="commitDto">коммит с изменениями</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> ClearAndAddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<int> ClearAndAddRange(Guid idDiscriminator, ChangeLogCommitDto commitDto, CancellationToken token);
/// <summary> /// <summary>
/// Редактирование записей /// Редактирование записей
/// </summary> /// </summary>
/// <param name="idEditor">пользователь, который редактирует</param> /// <param name="commitDto">коммит с изменениями</param>
/// <param name="dtos"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> UpdateRange(Guid idEditor, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<int> UpdateRange(ChangeLogCommitDto commitDto, CancellationToken token);
/// <summary> /// <summary>
/// Получение актуальных записей на определенный момент времени (с пагинацией) /// Получение актуальных записей на определенный момент времени (с пагинацией)