#127 Хранение технологических сообщений #4

Merged
on.nemtina merged 14 commits from TechMessages into master 2024-12-04 16:51:51 +05:00
15 changed files with 355 additions and 142 deletions
Showing only changes of commit 1e87523ab9 - Show all commits

View File

@ -11,7 +11,7 @@ namespace Persistence.API.Controllers;
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class TechMessagesController : ControllerBase, ITechMessages
public class TechMessagesController : ControllerBase
{
private readonly ITechMessagesRepository techMessagesRepository;
@ -41,8 +41,8 @@ public class TechMessagesController : ControllerBase, ITechMessages
/// <param name="autoDrillingSystem"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("statistics")]
public async Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
[HttpGet("statistics/{autoDrillingSystem}")]
public async Task<ActionResult<int>> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token)

Возвращаемый тип Dictionary<string, int>

Возвращаемый тип Dictionary<string, int>
{
var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
Review

Лучше все переменные и свойства importantId переименовать на categoryId или на categoryImportantId. Нужно, чтобы слово "категория" была, а то не очень понятно.

Лучше все переменные и свойства importantId переименовать на categoryId или на categoryImportantId. Нужно, чтобы слово "категория" была, а то не очень понятно.

View File

@ -1,9 +1,12 @@
using System.Reflection;
using System.Text.Json.Nodes;
using Mapster;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Persistence.Database.Entity;
using Persistence.Models;
using Persistence.Models.Configurations;
using Swashbuckle.AspNetCore.SwaggerGen;
@ -11,6 +14,12 @@ namespace Persistence.API;
public static class DependencyInjection
{
public static void MapsterSetup()
{
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TechMessageDto, TechMessage>()
.Ignore(dest => dest.System, dest => dest.SystemId);
}
public static void AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(c =>

View File

@ -23,6 +23,9 @@ public class Startup
services.AddInfrastructure();
services.AddPersistenceDbContext(Configuration);
services.AddJWTAuthentication(Configuration);
services.AddMemoryCache();
DependencyInjection.MapsterSetup();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

View File

@ -1,59 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Persistence.Database.Postgres.Migrations
{
/// <inheritdoc />
public partial class TechMessageMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTimeOffset>(
name: "Created",
table: "Setpoint",
type: "timestamp with time zone",
nullable: false,
comment: "Дата создания уставки",
oldClrType: typeof(DateTimeOffset),
oldType: "timestamp with time zone",
oldComment: "Дата изменения уставки");
migrationBuilder.CreateTable(
name: "TechMessage",
columns: table => new
{
EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
ImportantId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
OccurrenceDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"),
MessageText = table.Column<string>(type: "varchar(512)", nullable: true, comment: "Текст сообщения"),
AutoDrillingSystem = table.Column<string>(type: "varchar(256)", nullable: true, comment: "Система автобурения, к которой относится сообщение"),
UserId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика")
},
constraints: table =>
{
table.PrimaryKey("PK_TechMessage", x => x.EventId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TechMessage");
migrationBuilder.AlterColumn<DateTimeOffset>(
name: "Created",
table: "Setpoint",
type: "timestamp with time zone",
nullable: false,
comment: "Дата изменения уставки",
oldClrType: typeof(DateTimeOffset),
oldType: "timestamp with time zone",
oldComment: "Дата создания уставки");
}
}
}

View File

@ -12,7 +12,7 @@ using Persistence.Database.Model;
namespace Persistence.Database.Postgres.Migrations
{
[DbContext(typeof(PersistenceDbContext))]
[Migration("20241126044756_TechMessageMigration")]
[Migration("20241127123045_TechMessageMigration")]
partial class TechMessageMigration
{
/// <inheritdoc />
@ -27,6 +27,27 @@ namespace Persistence.Database.Postgres.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Persistence.Database.Entity.ADSystem", 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("ADSystem");
});
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{
b.Property<Guid>("EventId")
@ -34,10 +55,6 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("uuid")
.HasComment("Id события");
b.Property<string>("AutoDrillingSystem")
.HasColumnType("varchar(256)")
.HasComment("Система автобурения, к которой относится сообщение");
b.Property<double?>("Depth")
.HasColumnType("double precision")
.HasComment("Глубина забоя");
@ -47,6 +64,7 @@ namespace Persistence.Database.Postgres.Migrations
.HasComment("Id Категории важности");
b.Property<string>("MessageText")
.IsRequired()
.HasColumnType("varchar(512)")
.HasComment("Текст сообщения");
@ -54,15 +72,44 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("Дата возникновения");
b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы автобурения, к которой относится сообщение");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasComment("Id пользователя за пультом бурильщика");
b.HasKey("EventId");
b.HasIndex("SystemId");
b.ToTable("TechMessage");
});
modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b =>
{
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid")
.HasComment("Дискриминатор ссылка на тип сохраняемых данных");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone")
.HasComment("Отметка времени, строго в UTC");
b.Property<string>("Set")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Набор сохраняемых данных");
b.HasKey("IdDiscriminator", "Timestamp");
b.ToTable("TimestampedSets", t =>
{
t.HasComment("Общая таблица данных временных рядов");
});
});
modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
{
b.Property<DateTimeOffset>("Date")
@ -169,6 +216,17 @@ namespace Persistence.Database.Postgres.Migrations
b.ToTable("Setpoint");
});
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{
b.HasOne("Persistence.Database.Entity.ADSystem", "System")
.WithMany()
.HasForeignKey("SystemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("System");
});
#pragma warning restore 612, 618
}
}

View File

@ -0,0 +1,83 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Persistence.Database.Postgres.Migrations
{
/// <inheritdoc />
public partial class TechMessageMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ADSystem",
columns: table => new
{
SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения"),
Name = table.Column<string>(type: "varchar(256)", nullable: false, comment: "Наименование системы автобурения"),
Description = table.Column<string>(type: "text", nullable: true, comment: "Описание системы автобурения")
},
constraints: table =>
{
table.PrimaryKey("PK_ADSystem", x => x.SystemId);
});
migrationBuilder.CreateTable(
name: "TimestampedSets",
columns: table => new
{
IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"),
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"),
Set = table.Column<string>(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных")
},
constraints: table =>
{
table.PrimaryKey("PK_TimestampedSets", x => new { x.IdDiscriminator, x.Timestamp });
},
comment: "Общая таблица данных временных рядов");
migrationBuilder.CreateTable(
name: "TechMessage",
columns: table => new
{
EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
ImportantId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
OccurrenceDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"),
MessageText = table.Column<string>(type: "varchar(512)", nullable: false, comment: "Текст сообщения"),
SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения, к которой относится сообщение"),
UserId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика")
},
constraints: table =>
{
table.PrimaryKey("PK_TechMessage", x => x.EventId);
table.ForeignKey(
name: "FK_TechMessage_ADSystem_SystemId",
column: x => x.SystemId,
principalTable: "ADSystem",
principalColumn: "SystemId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_TechMessage_SystemId",
table: "TechMessage",
column: "SystemId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TechMessage");
migrationBuilder.DropTable(
name: "TimestampedSets");
migrationBuilder.DropTable(
name: "ADSystem");
}
}
}

View File

@ -24,6 +24,27 @@ namespace Persistence.Database.Postgres.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Persistence.Database.Entity.ADSystem", 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("ADSystem");
});
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{
b.Property<Guid>("EventId")
@ -31,10 +52,6 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("uuid")
.HasComment("Id события");
b.Property<string>("AutoDrillingSystem")
.HasColumnType("varchar(256)")
.HasComment("Система автобурения, к которой относится сообщение");
b.Property<double?>("Depth")
.HasColumnType("double precision")
.HasComment("Глубина забоя");
@ -44,6 +61,7 @@ namespace Persistence.Database.Postgres.Migrations
.HasComment("Id Категории важности");
b.Property<string>("MessageText")
.IsRequired()
.HasColumnType("varchar(512)")
.HasComment("Текст сообщения");
@ -51,15 +69,44 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("Дата возникновения");
b.Property<Guid>("SystemId")
.HasColumnType("uuid")
.HasComment("Id системы автобурения, к которой относится сообщение");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasComment("Id пользователя за пультом бурильщика");
b.HasKey("EventId");
b.HasIndex("SystemId");
b.ToTable("TechMessage");
});
modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b =>
{
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid")
.HasComment("Дискриминатор ссылка на тип сохраняемых данных");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone")
.HasComment("Отметка времени, строго в UTC");
b.Property<string>("Set")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Набор сохраняемых данных");
b.HasKey("IdDiscriminator", "Timestamp");
b.ToTable("TimestampedSets", t =>
{
t.HasComment("Общая таблица данных временных рядов");
});
});
modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
{
b.Property<DateTimeOffset>("Date")
@ -166,6 +213,17 @@ namespace Persistence.Database.Postgres.Migrations
b.ToTable("Setpoint");
});
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{
b.HasOne("Persistence.Database.Entity.ADSystem", "System")
.WithMany()
.HasForeignKey("SystemId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("System");
});
#pragma warning restore 612, 618
}
}

View File

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Persistence.Database.Entity;
public class ADSystem
{
[Key, Comment("Id системы автобурения")]
public Guid SystemId { get; set; }
[Required, Column(TypeName = "varchar(256)"), Comment("Наименование системы автобурения")]
public required string Name { get; set; }
[Comment("Описание системы автобурения")]
public string? Description { get; set; }
}

View File

@ -19,10 +19,13 @@ namespace Persistence.Database.Entity
public double? Depth { get; set; }
[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
public string? MessageText { get; set; }
public required string MessageText { get; set; }
[Column(TypeName = "varchar(256)"), Comment("Система автобурения, к которой относится сообщение")]
public string? AutoDrillingSystem { get; set; }
[Required, Comment("Id системы автобурения, к которой относится сообщение")]
public required Guid SystemId { get; set; }
[Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")]
public virtual required ADSystem System { get; set; }
[Comment("Id пользователя за пультом бурильщика")]
public Guid UserId { get; set; }

View File

@ -29,7 +29,7 @@ namespace Persistence.IntegrationTests.Controllers
{
Skip = 1,
Take = 2,
SortSettings = nameof(TechMessageDto.ImportantId)
SortSettings = nameof(TechMessageDto.CategoryId)
};
//act
@ -53,7 +53,7 @@ namespace Persistence.IntegrationTests.Controllers
{
Skip = 0,
Take = 2,
SortSettings = nameof(TechMessageDto.ImportantId)
SortSettings = nameof(TechMessageDto.CategoryId)
};
//act
@ -90,7 +90,7 @@ namespace Persistence.IntegrationTests.Controllers
//arrange
var dtos = await InsertRange();
var systems = dtos
.Select(e => e.AutoDrillingSystem)
.Select(e => e.System)
.Distinct()
.ToArray();
@ -110,7 +110,7 @@ namespace Persistence.IntegrationTests.Controllers
//arrange
dbContext.CleanupDbSet<TechMessage>();
var imortantId = 1;
var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem);
var autoDrillingSystem = nameof(TechMessageDto.System);
//act
var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
@ -125,9 +125,9 @@ namespace Persistence.IntegrationTests.Controllers
{
//arrange
var imortantId = 1;
var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem);
var autoDrillingSystem = nameof(TechMessageDto.System);
var dtos = await InsertRange();
var filteredDtos = dtos.Where(e => e.ImportantId == imortantId && e.AutoDrillingSystem == e.AutoDrillingSystem);
var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System);
//act
var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
@ -145,21 +145,21 @@ namespace Persistence.IntegrationTests.Controllers
new TechMessageDto()
{
EventId = Guid.NewGuid(),
Review

Для тестов, которые что-то возвращают, необходимо предварительно записывать данные. И сравнивать уже получаемые данные с теми, что туда записывались.
Например, в данном методе нужно было предварительно что-то записать в базу, на основании чего бы рассчиталась статистика, а потом этот расчет уже сравнить с тем, что ты ожидаешь получить.

Сейчас здесь просто проверка на то, что метод вернул 200 ответ и что пришла пустая коллекция. Это верно, потому что в базе ничего нет. Но не факт, что тест бы прошел, если бы в базе что-то было.

Для тестов, которые что-то возвращают, необходимо предварительно записывать данные. И сравнивать уже получаемые данные с теми, что туда записывались. Например, в данном методе нужно было предварительно что-то записать в базу, на основании чего бы рассчиталась статистика, а потом этот расчет уже сравнить с тем, что ты ожидаешь получить. Сейчас здесь просто проверка на то, что метод вернул 200 ответ и что пришла пустая коллекция. Это верно, потому что в базе ничего нет. Но не факт, что тест бы прошел, если бы в базе что-то было.
Review

У каждого returns_success теста есть аналог с AfterSave - в котором, как раз таки, проверяются сохраненные данные. Так сделано для тестирования работоспособности отдельных методов вне зависимости от метода сохранения.

У каждого returns_success теста есть аналог с AfterSave - в котором, как раз таки, проверяются сохраненные данные. Так сделано для тестирования работоспособности отдельных методов вне зависимости от метода сохранения.
ImportantId = 1,
OccurrenceDate = DateTimeOffset.UtcNow,
CategoryId = 1,
Timestamp = DateTimeOffset.UtcNow,
Depth = 1.11,
MessageText = nameof(TechMessageDto.MessageText),
AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem),
System = nameof(TechMessageDto.System),
UserId = Guid.NewGuid()
},
new TechMessageDto()
{
EventId = Guid.NewGuid(),
ImportantId = 2,
OccurrenceDate = DateTimeOffset.UtcNow,
CategoryId = 2,
Timestamp = DateTimeOffset.UtcNow,
Depth = 2.22,
MessageText = nameof(TechMessageDto.MessageText),
AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem),
System = nameof(TechMessageDto.System),

Не очень понятно, что такое e.System == e.System, оно же всегда будет true давать?

Не очень понятно, что такое e.System == e.System, оно же всегда будет true давать?
UserId = Guid.NewGuid()
}
};

View File

@ -1,5 +1,7 @@
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json.Linq;
using Persistence.Database.Entity;
using Persistence.Models;
using Persistence.Repositories;
@ -9,9 +11,13 @@ namespace Persistence.Repository.Repositories
{
public class TechMessagesRepository : ITechMessagesRepository
{
private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey";
private readonly IMemoryCache memoryCache;
private DbContext db;
public TechMessagesRepository(DbContext db)
public TechMessagesRepository(DbContext db, IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
this.db = db;
}
@ -36,36 +42,75 @@ namespace Persistence.Repository.Repositories
return dto;
}
public async Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
public async Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token)
{
var query = GetQueryReadOnly();
var count = await query

Название переменной желательно поправить. Можно назвать result.

Название переменной желательно поправить. Можно назвать result.
.Where(e => e.ImportantId == importantId && e.AutoDrillingSystem == autoDrillingSystem)
.CountAsync();
.Where(e => importantId == null || e.ImportantId == importantId)
.Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem)

Вообще, думаю, что во всех местах, где есть поиск или фильтрация по имени, необходимо обе сравниваемые переменные приводить к одному регистру и убирать пробелы.

Вообще, думаю, что во всех местах, где есть поиск или фильтрация по имени, необходимо обе сравниваемые переменные приводить к одному регистру и убирать пробелы.
.GroupBy(e => e.System.Name)
.ToDictionaryAsync(e => e.Key, v => v.Count());
return count;
}
public async Task<IEnumerable<string>> GetSystems(CancellationToken token)
public async Task<IEnumerable<ADSystemDto>> GetSystems(CancellationToken token)
{
var query = GetQueryReadOnly();
var entities = await query
.Select(e => e.AutoDrillingSystem ?? string.Empty)
.Distinct()
.ToArrayAsync(token);
var dtos = entities.Order();
var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f =>

Нужно токен передать внутрь приватного метода

Нужно токен передать внутрь приватного метода
{
f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);

Здесь можно не писать ?? [], по идее в systems не может быть null, там может быть только пустая коллекция

Здесь можно не писать ```?? []```, по идее в systems не может быть null, там может быть только пустая коллекция
return dtos;
var query = db.Set<ADSystem>();
var entities = await query.ToListAsync();
var dtos = entities.Select(e => e.Adapt<ADSystemDto>());
return dtos;
});
return systems ?? [];
}

Здесь тоже нужно токен передать

Здесь тоже нужно токен передать
public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)

Здесь точно e.Name == dto.System необходимо приводить к одному регистру и убирать пробелы.
Пользователь может передать dto.System, например, с пробелом. Тогда поиск по системам не найдет необходимого элемента и создаст новый с таким же названием и с пробелом.

Также при создании системы CreateSystem лучше предварительно убирать пробелы.

Здесь точно e.Name == dto.System необходимо приводить к одному регистру и убирать пробелы. Пользователь может передать dto.System, например, с пробелом. Тогда поиск по системам не найдет необходимого элемента и создаст новый с таким же названием и с пробелом. Также при создании системы CreateSystem лучше предварительно убирать пробелы.
{
var entities = dtos.Select(d => d.Adapt<TechMessage>());
var entities = dtos.Select(dto =>
{
var task = Task.Run(async () =>
{
var entity = dto.Adapt<TechMessage>();
var systems = await GetSystems(token);
var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId
?? await CreateSystem(dto.System);
entity.SystemId = systemId;
return entity;
});
task.Wait();
return task.Result;
});

60 минут - лучше в отдельное поле вынести

60 минут - лучше в отдельное поле вынести
await db.Set<TechMessage>().AddRangeAsync(entities, token);
var result = await db.SaveChangesAsync(token);
Review

ToArrayAsync и туда внутрь еще токен передать....

ToArrayAsync и туда внутрь еще токен передать....
return result;
}
private async Task<Guid> CreateSystem(string name)
{

Здесь тоже можно убрать ?? [], так как внутри systems не может быть null.
Чтобы гарантированно это обозначить, можно написать так: return systems!

Здесь тоже можно убрать ``` ?? []```, так как внутри systems не может быть null. Чтобы гарантированно это обозначить, можно написать так: return systems!
memoryCache.Remove(SystemCacheKey);
var systemId = Guid.NewGuid();
var entity = new ADSystem()
{
SystemId = systemId,

Здесь можно дополнительную переменную не создавать

Здесь можно дополнительную переменную не создавать
Name = name
};
await db.Set<ADSystem>().AddAsync(entity);
await db.SaveChangesAsync();
return Guid.NewGuid();
}
}
}

View File

@ -1,35 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Persistence.Models;
namespace Persistence.API
{
/// <summary>
/// Интерфейс для API сообщений о состояниях работы систем автобурения (АБ)
/// </summary>
public interface ITechMessages : ITableDataApi<TechMessageDto, RequestDto>
{
/// <summary>
/// Добавление новых сообщений
/// </summary>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<ActionResult<int>> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
/// <summary>
/// Получение списка систем АБ
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token);
/// <summary>
/// Получение статистики
/// </summary>
/// <param name="importantId">Id Категории важности</param>
/// <param name="autoDrillingSystem">Система АБ</param>
/// <param name="token"></param>
/// <returns></returns>
Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token);
}
}

View File

@ -0,0 +1,22 @@
namespace Persistence.Models;
/// <summary>
/// Модель системы автобурения
/// </summary>
public class ADSystemDto

Сокращения не желательны.
Предлагаю название DrillingSystem (без auto, так как системы необязательно должны быть автоматизированые)

Сокращения не желательны. Предлагаю название DrillingSystem (без auto, так как системы необязательно должны быть автоматизированые)
{
/// <summary>
/// Ключ
/// </summary>
public Guid SystemId { get; set; }
/// <summary>
/// Наименование
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Описание
/// </summary>
public string? Description { get; set; }
}

View File

@ -1,4 +1,6 @@
namespace Persistence.Models
using System.ComponentModel.DataAnnotations;
namespace Persistence.Models
{
/// <summary>
/// Модель технологического сообщения
@ -8,32 +10,39 @@
/// <summary>
/// Id события
/// </summary>
[Required]
public Guid EventId { get; set; }
/// <summary>
/// Id Категории важности
/// </summary>
public int ImportantId { get; set; }
[Range(0, int.MaxValue, ErrorMessage = "Id Категории важности не может быть меньше 0")]
public int CategoryId { get; set; }
/// <summary>
/// Дата возникновения
/// </summary>
public DateTimeOffset OccurrenceDate { get; set; }
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Глубина забоя
/// </summary>
[Range(0, double.MaxValue, ErrorMessage = "Глубина забоя не может быть меньше 0")]
public double? Depth { get; set; }
/// <summary>
/// Текст сообщения
/// </summary>
public string? MessageText { get; set; }
[Required]
[StringLength(512, MinimumLength = 1, ErrorMessage = "Допустимая длина текста сообщения от 1 до 512 символов")]
public required string MessageText { get; set; }
/// <summary>
/// Система автобурения, к которой относится сообщение
/// </summary>
public string? AutoDrillingSystem { get; set; }
[Required]
[StringLength(256, MinimumLength = 1, ErrorMessage = "Допустимая длина наименования системы АБ от 1 до 256 символов")]
public required string System { get; set; }
/// <summary>
/// Id пользователя за пультом бурильщика

View File

@ -1,4 +1,5 @@
using Persistence.Models;
using System.Threading.Tasks;
using Persistence.Models;
namespace Persistence.Repositories
{
@ -28,7 +29,7 @@ namespace Persistence.Repositories
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<string>> GetSystems(CancellationToken token);
Task<IEnumerable<ADSystemDto>> GetSystems(CancellationToken token);
/// <summary>
/// Получение количества сообщений по категориям и системам автобурения
@ -37,6 +38,6 @@ namespace Persistence.Repositories
/// <param name="autoDrillingSystem">Система автобурения</param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token);
Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token);

Аргументы лучше местами поменять, чтобы их порядок быть от более крупной сущности к более мелкой (Плюс тогда сигнатура будет больше похожа на сигнатуру веб-апи).

В запросе от пользователя также лучше сразу предусмотреть возможность множественного выбора систем и категорий. Прим.: IEnumerable<int>? CategoriesIds

Возвращаемый Dictionary<string, int> не особо понятно, что именно возвращает.
Предлагаю возвращать:

[
	{ 
		"system": "System_1",
		"categories": [
			{"1": 1}, // Аварии
			{"2": 5}, // Предупреждения
			{"3": 15},
			...
		]
	},
	...
]
Аргументы лучше местами поменять, чтобы их порядок быть от более крупной сущности к более мелкой (Плюс тогда сигнатура будет больше похожа на сигнатуру веб-апи). В запросе от пользователя также лучше сразу предусмотреть возможность множественного выбора систем и категорий. Прим.: `IEnumerable<int>? CategoriesIds` Возвращаемый Dictionary<string, int> не особо понятно, что именно возвращает. Предлагаю возвращать: ``` [ { "system": "System_1", "categories": [ {"1": 1}, // Аварии {"2": 5}, // Предупреждения {"3": 15}, ... ] }, ... ] ```
}
}