Compare commits

..

99 Commits
dev ... master

Author SHA1 Message Date
7330f9b6db Merge pull request ' Отделить логику маппинга от клиентов + Добавить множественный маппинг сущностей в TimeStampedClient' () from feature-mapping into master
Reviewed-on: 
Reviewed-by: on.nemtina <on.nemtina@digitaldrilling.ru>
2025-03-03 09:44:26 +05:00
1e2fa287a0 Правки по результатам ревью 2025-02-28 13:15:27 +05:00
2dca1cc722 Исправление маппинга 2025-02-27 14:01:31 +05:00
d2c4db015b Фикс проекта с модульными тестами 2025-02-24 15:56:16 +05:00
cc4ceeff62 Merge branch 'master' into feature-mapping 2025-02-24 15:54:39 +05:00
0a0e7fa383 Правка интеграционных тестов для changeLog 2025-02-24 15:51:15 +05:00
c5d552015d Merge branch 'master' into feature-mapping 2025-02-24 15:21:48 +05:00
efd7a7e639 Добавить множественный маппинг сущностей в TimeStampedClient 2025-02-24 15:21:02 +05:00
ba0c748659 Merge pull request 'Добавить таблицу для учета комментариев и действий пользователя для вывода статистики по ChangeLog' () from feature/#956-change-log-table-comment into master
Reviewed-on: 
Reviewed-by: Никита Фролов <ng.frolov@digitaldrilling.ru>
2025-02-24 12:59:27 +05:00
ad3bc1a598 Merge branch 'master' into feature/#956-change-log-table-comment 2025-02-24 12:59:14 +05:00
a436b839f6 Правки по результатам ревью 2025-02-24 12:11:48 +05:00
0155624216 Merge branch 'master' into feature-mapping 2025-02-24 08:41:55 +05:00
930c4e8722 Комментарии к ChangeLogController 2025-02-21 14:22:34 +05:00
0c7006d52a нейминг-2 2025-02-21 12:28:32 +05:00
7590e8facb нейминг 2025-02-21 12:27:09 +05:00
a796024c69 Правки после ревью 2025-02-21 12:25:22 +05:00
Оля Бизюкова
4b7f55c3c8 Правка по результатам ревью 2025-02-20 18:30:56 +05:00
a5037b8967 Merge pull request ' Убрать ненужные модификаторы virtual' () from VirtualCleaning into master
Reviewed-on: 
2025-02-20 14:41:44 +05:00
Оля Бизюкова
523c15093f Мини-правки к автотестам 2025-02-20 14:40:20 +05:00
Оля Бизюкова
ba9fe675ed Дополнительные проверки в юнит-тесте UpdateRange для ChangeLog 2025-02-20 12:04:08 +05:00
c0d28efc70 Юнит-тесты 2025-02-19 17:44:48 +05:00
Оля Бизюкова
14893e9bf6 Правки по результатам ревью - 3 2025-02-18 17:21:30 +05:00
Оля Бизюкова
baba1a05c0 Правки по результатам PR - 2 2025-02-18 15:53:54 +05:00
Оля Бизюкова
fd8bfac6a6 Правки к PR - 1 2025-02-18 15:43:27 +05:00
Оля Бизюкова
0ec2e5d99f Правка после запуска интеграционных тестов 2025-02-18 11:17:04 +05:00
Оля Бизюкова
ebe4ec0cf6 Правка конфликтов по результатам мерджа из мастера 2025-02-18 11:07:41 +05:00
Оля Бизюкова
86d145c39f Merge from master 2025-02-18 11:01:21 +05:00
Оля Бизюкова
41b44efbb3 Корректный appsettings.json 2025-02-18 10:55:30 +05:00
Оля Бизюкова
da54d24745 Правки перед открытием PR к задаче по созданию таблицы с коммитами 2025-02-18 10:34:42 +05:00
05b58da1ab Интеграционные тесты 2025-02-17 17:34:07 +05:00
09cfccaa38 Приведение DD.Persistence.Repository.Test в исходное состояние 2025-02-17 11:23:43 +05:00
Оля Бизюкова
032d783d40 реализация методов ChangeLogService 2025-02-17 08:44:48 +05:00
c4b9878105 Добавить тест 2025-02-14 17:13:46 +05:00
58346b8f3e Merge branch 'master' into feature-mapping 2025-02-14 13:26:47 +05:00
3b4af1fd8d Наработки по ChangeLog 2025-02-13 17:57:43 +05:00
27b9728912 Убрать ненужные модификаторы virtual 2025-02-13 16:53:29 +05:00
93472f7933 Обновить DD.Persistence.API/Readme.md 2025-02-13 13:27:58 +05:00
0d9c61c905 Обновить DD.Persistence.API/Readme.md 2025-02-13 13:27:06 +05:00
36274b3a5e Обновить DD.Persistence.API/Readme.md 2025-02-13 13:26:45 +05:00
b30d28dbeb Обновления в файле readme.md 2025-02-13 13:23:00 +05:00
233850483c UML-диаграмма процесса пакетного редактирования 2025-02-13 13:22:28 +05:00
5a44dfb109 Автотесты 2025-02-13 12:43:37 +05:00
ca0b1f0031 Убрана ссылка на удаленный проект с репозиториями из Dockerfile 2025-02-12 16:43:34 +05:00
3cf7cbcc7c Фикс теста 2025-02-12 16:33:42 +05:00
7c2ab3f6e2 Автотесты (начало) 2025-02-12 16:19:08 +05:00
77fa9d2c46 Исправлены тесты 2025-02-12 16:17:13 +05:00
a49a0f567a Merge pull request ' Модифицировать метод Get для TimestampedValues по части применения фильтра' () from TimestampedValuesFilter into master
Reviewed-on: 
Reviewed-by: on.nemtina <on.nemtina@digitaldrilling.ru>
2025-02-12 15:16:09 +05:00
c904c117d8 Правки после ревью 2025-02-12 15:15:43 +05:00
a9d0fa57f2 Маппинг отделен от клиентов 2025-02-12 15:14:14 +05:00
ebd2cff40d Поле с комментариями в методах контроллера, отвечающие за удаление 2025-02-12 11:24:31 +05:00
Оля Бизюкова
b3c6acbd18 Таблица в БД для учета коммитов в журнале изменений и всё, что с этим связано 2025-02-11 17:58:52 +05:00
0aca5d2d43 Добавить комментарий для клиента 2025-02-11 12:41:55 +05:00
4b9a4b4db7 Merge branch 'master' into TimestampedValuesFilter 2025-02-11 12:37:04 +05:00
e1f84f3091 Перед материализацией вычитываемых сущностей применить построенный фильтр 2025-02-11 12:34:37 +05:00
2fe369d49e Перевести TimestampedValuesRepository под спецификации 2025-02-10 17:25:45 +05:00
8e2c3a3a55 Добавить парсинг дерева в Get-запрос 2025-02-10 09:27:13 +05:00
52f7bca9f1 Merge pull request ' Реализовать обход бинарного дерева и создание фильтра на основе спецификаций' () from FilterBuilder into master
Reviewed-on: 
2025-02-07 15:11:33 +05:00
2ef3efed33 Merge from dev 2025-02-07 15:11:09 +05:00
4739a043f4 Обновить README.md 2025-02-07 12:30:59 +05:00
49cc9d6e39 Обновить README.md 2025-02-07 12:15:35 +05:00
c997bfe9a4 Обновить README.md 2025-02-07 12:01:11 +05:00
fca2ccb8aa Обновить README.md 2025-02-07 12:00:23 +05:00
bfb18cab8a Обновить README.md 2025-02-07 11:59:22 +05:00
c247489477 readme-файл, как запускать контейнер с persistence 2025-02-07 11:57:07 +05:00
11eea8db67 Папка docker, где находится compose.yaml - файл для docker 2025-02-07 11:33:45 +05:00
83c744939b Merge branch 'FilterBuilder' into FilterBinder 2025-02-07 08:33:40 +05:00
43289d939b Merge pull request 'fix/mock-methods-for-change-log' () from fix/mock-methods-for-change-log into master
Reviewed-on: 
2025-02-06 17:23:32 +05:00
Оля Бизюкова
1f3df26e9a Правка по ревью 2025-02-06 17:21:28 +05:00
Оля Бизюкова
e0f0d9fdd0 Исправлен тип возвращаемых данных для GetStatisticsCountAsync и HistoryChangeLogAsync 2025-02-06 17:07:02 +05:00
Оля Бизюкова
ed6df68f7d Merge branch 'fix/mock-methods-for-change-log' of ssh://git.ddrilling.ru:2221/on.nemtina/persistence into fix/mock-methods-for-change-log 2025-02-06 16:57:31 +05:00
Оля Бизюкова
2ab8101258 Moq-данные для статистики и получения истории журнала изменений 2025-02-06 16:57:13 +05:00
d6038ca579 Merge branch 'master' into fix/mock-methods-for-change-log 2025-02-06 16:15:42 +05:00
Оля Бизюкова
86bf78f31f HistoryChangeLogDto для описания истории изменений 2025-02-06 16:14:02 +05:00
5ce5fa139b Merge pull request ' Перенести DD.Persistence.Repository + доработать схему данных' () from RepositoriesRework into master
Reviewed-on: 
Reviewed-by: on.nemtina <on.nemtina@digitaldrilling.ru>
2025-02-06 15:53:37 +05:00
272e164482 Merge branch 'master' into RepositoriesRework 2025-02-06 15:52:03 +05:00
aad8c57511 Исправить сломанный тест 2025-02-06 15:50:04 +05:00
Оля Бизюкова
8fa661b605 Merge from dev 2025-02-06 15:38:56 +05:00
Оля Бизюкова
979a651328 Merge from dev 2025-02-06 15:37:39 +05:00
baf04ae3a6 Merge pull request ' Реализовать построение дерева из строки' () from TreeBuilder into master
Reviewed-on: 
2025-02-06 15:20:57 +05:00
598056c6d7 Merge branch 'RepositoriesRework' into FilterBuilder 2025-02-06 12:39:52 +05:00
c5da82c210 Правки по ревью 2025-02-06 12:39:40 +05:00
bcb9749b1a Правки по ревью 2025-02-06 12:32:28 +05:00
44bc335151 Merge branch 'RepositoriesRework' into FilterBuilder 2025-02-06 09:31:20 +05:00
7d973ba859 Правки по ревью 2025-02-06 09:31:10 +05:00
5abfcc0d50 Merge branch 'RepositoriesRework' into FilterBuilder 2025-02-06 09:25:08 +05:00
0c27a0148d Merge branch 'TreeBuilder' into FilterBuilder 2025-02-06 09:15:48 +05:00
a340fbe23b Наработка 2025-02-05 18:04:36 +05:00
9ca49cb1b5 Правки по ревью 2025-02-05 17:20:18 +05:00
431c7278cb Правки по ревью 2025-02-05 14:30:36 +05:00
4513de06fa Перенести содержимое проекта DD.Persistence.Repository в DD.Persistence.Database 2025-02-05 12:19:45 +05:00
63e6816e35 Правки по ревью 2025-02-05 12:10:01 +05:00
560073ed07 Правки по ревью 2025-02-05 11:45:47 +05:00
e3c1d02650 Правки по ревью 2025-02-05 10:58:26 +05:00
4b5477207d Реализовать обход бинарного дерева и создание фильтра на основе спецификаций 2025-02-05 10:40:34 +05:00
87264fd8db Merge branch 'TreeBuilder' into FilterBuilder 2025-02-05 10:17:08 +05:00
1d0921c3e8 Доработать схему данных по части хранения типов полей в соответствии с индексацией 2025-02-05 09:56:49 +05:00
f955aab218 Перенести содержимое проекта DD.Persistence.Repository в DD.Persistence.Database.Postgres 2025-02-05 09:16:28 +05:00
cb0508afe9 Убрать лишний комментарий 2025-02-05 08:48:20 +05:00
287d6e7111 Реализовать построение дерева из строки 2025-02-05 08:46:08 +05:00
126 changed files with 4092 additions and 1027 deletions
.docker
DD.Persistence.API
DD.Persistence.App
DD.Persistence.Client
DD.Persistence.Database.Postgres
DD.Persistence.Database
DD.Persistence.IntegrationTests/Controllers
DD.Persistence.Models
DD.Persistence.Repository.Test
DD.Persistence.Repository
DD.Persistence.Test
DD.Persistence.sln
DD.Persistence
API
Filter/Models/Abstractions

25
.docker/appsettings.json Normal file
View File

@ -0,0 +1,25 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Host=db:5432;Database=persistence;Username=postgres;Password=postgres;Persist Security Info=True"
},
"AllowedHosts": "*",
"NeedUseKeyCloak": false,
"KeyCloakAuthentication": {
"Audience": "account",
"Host": "http://192.168.0.10:8321/realms/Persistence"
},
"AuthUser": {
"username": "myuser",
"password": 12345,
"clientId": "webapi",
"grantType": "password",
"http://schemas.xmlsoap.org/ws/2005/05/identity /claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6"
},
"ClientUrl": "http://localhost:5000/"
}

31
.docker/compose.yaml Normal file
View File

@ -0,0 +1,31 @@
networks:
persistence:
external: false
services:
db:
image: timescale/timescaledb:latest-pg16
container_name: some-timescaledb-16
restart: always
environment:
- POSTGRES_PASSWORD=postgres
networks:
- persistence
ports:
- "5462:5432"
volumes:
- ./db:/var/lib/postgresql/data
persistence:
image: git.ddrilling.ru/ddrilling/persistence:latest
container_name: persistence
restart: always
depends_on:
- db
networks:
- persistence
ports:
- "1111:8080"
volumes:
- ./appsettings.json:/app/appsettings.json

View File

@ -1,107 +1,124 @@
using DD.Persistence.API;
using DD.Persistence.API.Services;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using DD.Persistence.Models;
using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories;
using System.Net; using System.Net;
using DD.Persistence.Models.Common; using UuidExtensions;
namespace DD.Persistence.API.Controllers;
/// <summary>
/// Контроллер по работе с журналом изменений
/// </summary>
[ApiController] [ApiController]
[Authorize] [Authorize]
[Route("api/[controller]")] [Route("api/[controller]")]
public class ChangeLogController : ControllerBase, IChangeLogApi public class ChangeLogController : ControllerBase, IChangeLogApi
{ {
private readonly IChangeLogRepository repository; private readonly ChangeLogService service;
public ChangeLogController(IChangeLogRepository repository) /// <summary>
/// ctor
/// </summary>
/// <param name="service"></param>
public ChangeLogController(ChangeLogService service)
{ {
this.repository = repository; this.service = service;
} }
/// <summary>
/// Добавить записи в журнал изменений по дискриминатору
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dtos"></param>
/// <param name="comment"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("{idDiscriminator}")] [HttpPost("{idDiscriminator}")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
public async Task<IActionResult> Add(
[FromRoute] Guid idDiscriminator,
[FromBody] ChangeLogValuesDto dto,
CancellationToken token)
{
var userId = User.GetUserId<Guid>();
var result = await repository.AddRange(userId, idDiscriminator, [dto], token);
return CreatedAtAction(nameof(Add), result);
}
[HttpPost("range/{idDiscriminator}")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
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 changeLogCommitRequest = new CreateChangeLogCommitRequest(Uuid7.Guid(), comment);
var result = await service.AddRange(idDiscriminator, changeLogCommitRequest, dtos, token);
return CreatedAtAction(nameof(AddRange), result); return CreatedAtAction(nameof(AddRange), result);
} }
/// <summary>
/// Удалить записи в журнале изменений
/// </summary>
/// <param name="ids"></param>
/// <param name="comment"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpDelete] [HttpDelete]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Delete(Guid id, CancellationToken token) public async Task<IActionResult> DeleteRange(IEnumerable<Guid> ids, string comment, CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); var userId = User.GetUserId<Guid>();
var result = await repository.MarkAsDeleted(userId, [id], token); var changeLogCommitRequest = new CreateChangeLogCommitRequest(userId, comment);
var result = await service.MarkAsDeleted(ids, changeLogCommitRequest, token);
return Ok(result); return Ok(result);
} }
[HttpDelete("range")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> DeleteRange(IEnumerable<Guid> ids, CancellationToken token)
{
var userId = User.GetUserId<Guid>();
var result = await repository.MarkAsDeleted(userId, ids, token);
return Ok(result);
}
/// <summary>
/// Очистить все записи в журнале изменений (по дискриминатору) и добавить новые
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dtos"></param>
/// <param name="comment"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("replace/{idDiscriminator}")] [HttpPost("replace/{idDiscriminator}")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
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 changeLogCommitRequest = new CreateChangeLogCommitRequest(userId, comment);
var result = await service.ClearAndAddRange(idDiscriminator, changeLogCommitRequest, dtos, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// сохранить изменения в записях журнала изменений
/// </summary>
/// <param name="dtos"></param>
/// <param name="comment"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPut] [HttpPut]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Update(
ChangeLogValuesDto dto,
CancellationToken token)
{
var userId = User.GetUserId<Guid>();
var result = await repository.UpdateRange(userId, [dto], token);
return Ok(result);
}
[HttpPut("range")]
[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 changeLogCommitRequest = new CreateChangeLogCommitRequest(userId, comment);
var result = await service.UpdateRange(changeLogCommitRequest, dtos, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получение актуальных записей (с пагинацией)
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="paginationRequest"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("{idDiscriminator}")] [HttpGet("{idDiscriminator}")]
[ProducesResponseType(typeof(PaginationContainer<ChangeLogValuesDto>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(PaginationContainer<ChangeLogValuesDto>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetCurrent( public async Task<IActionResult> GetCurrent(
@ -110,11 +127,19 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
CancellationToken token) CancellationToken token)
{ {
var moment = new DateTimeOffset(3000, 1, 1, 0, 0, 0, TimeSpan.Zero); var moment = new DateTimeOffset(3000, 1, 1, 0, 0, 0, TimeSpan.Zero);
var result = await repository.GetByDate(idDiscriminator, moment, paginationRequest, token); var result = await service.GetByDate(idDiscriminator, moment, paginationRequest, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получение записей на определенный момент времени (с пагинацией)
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="moment"></param>
/// <param name="paginationRequest"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("moment/{idDiscriminator}")] [HttpGet("moment/{idDiscriminator}")]
[ProducesResponseType(typeof(PaginationContainer<ChangeLogValuesDto>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(PaginationContainer<ChangeLogValuesDto>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetByDate( public async Task<IActionResult> GetByDate(
@ -123,11 +148,19 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
[FromQuery] PaginationRequest paginationRequest, [FromQuery] PaginationRequest paginationRequest,
CancellationToken token) CancellationToken token)
{ {
var result = await repository.GetByDate(idDiscriminator, moment, paginationRequest, token); var result = await service.GetByDate(idDiscriminator, moment, paginationRequest, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получение измененных записей за период времени
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dateBegin"></param>
/// <param name="dateEnd"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("history/{idDiscriminator}")] [HttpGet("history/{idDiscriminator}")]
[ProducesResponseType(typeof(IEnumerable<ChangeLogDto>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<ChangeLogDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NoContent)]
@ -137,41 +170,144 @@ public class ChangeLogController : ControllerBase, IChangeLogApi
DateTimeOffset dateEnd, DateTimeOffset dateEnd,
CancellationToken token) CancellationToken token)
{ {
var result = await repository.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, token); var result = await service.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени)
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("datesChange/{idDiscriminator}")] [HttpGet("datesChange/{idDiscriminator}")]
[ProducesResponseType(typeof(IEnumerable<DateOnly>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<DateOnly>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<IActionResult> GetDatesChange([FromRoute] Guid idDiscriminator, CancellationToken token) public async Task<IActionResult> GetDatesChange([FromRoute] Guid idDiscriminator, CancellationToken token)
{ {
var result = await repository.GetDatesChange(idDiscriminator, token); var result = await service.GetDatesChange(idDiscriminator, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получение данных, начиная с определенной даты
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dateBegin"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("part/{idDiscriminator}")] [HttpGet("part/{idDiscriminator}")]
[ProducesResponseType(typeof(IEnumerable<ChangeLogValuesDto>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<ChangeLogValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<ChangeLogValuesDto>>> GetPart([FromRoute] Guid idDiscriminator, DateTimeOffset dateBegin, int take = 86400, CancellationToken token = default) public async Task<ActionResult<IEnumerable<ChangeLogValuesDto>>> GetPart([FromRoute] Guid idDiscriminator, DateTimeOffset dateBegin, int take = 86400, CancellationToken token = default)
{ {
var result = await repository.GetGtDate(idDiscriminator, dateBegin, token); var result = await service.GetGtDate(idDiscriminator, dateBegin, token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("datesRange/{idDiscriminator}")] [HttpGet("datesRange/{idDiscriminator}")]
[ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync([FromRoute] Guid idDiscriminator, CancellationToken token) public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync([FromRoute] Guid idDiscriminator, CancellationToken token)
{ {
var result = await repository.GetDatesRange(idDiscriminator, token); var result = await service.GetDatesRange(idDiscriminator, token);
if (result is null) if (result is null)
return NoContent(); return NoContent();
return Ok(result); return Ok(result);
} }
/// <summary>
/// Метод, который возвращает статистику по количеству изменений в разрезе дней
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("statistics")]
public async Task<ActionResult<IEnumerable<StatisticsChangeLogDto>>> GetStatisticsCountAsync([FromQuery] ChangeLogRequest request, CancellationToken token)
{
var result = new List<StatisticsChangeLogDto>() {
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-60), ChangesCount = 10},
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-50), ChangesCount = 2},
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-25), ChangesCount = 560},
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-2), ChangesCount = 78},
new() { DateTime = DateTimeOffset.UtcNow.AddDays(-1), ChangesCount = 39},
};
return Ok(result);
}
/// <summary>
/// Метод, который возвращает историю изменений в разрезе дней
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("history")]
public async Task<ActionResult<IEnumerable<HistoryChangeLogDto>>> HistoryChangeLogAsync([FromQuery] ChangeLogRequest request, CancellationToken token)
{
var userId = Guid.CreateVersion7();
var changeLogItemCurrentId = Guid.CreateVersion7();
var changeLogItemCreation = DateTimeOffset.UtcNow;
var changeLogItems = new List<ChangeLogDto>()
{
new ChangeLogDto()
{
Id = changeLogItemCurrentId,
Creation = changeLogItemCreation,
IdAuthor = userId,
IdEditor = userId,
Obsolete = null,
Value = new ChangeLogValuesDto(){
Id = Guid.CreateVersion7(),
Value = new Dictionary<string, object>() {
["1"] = new { id = 1, caption = "Изменение 1 (c правкой)" },
["2"] = new { id = 2, caption = "Изменение 2 (с правкой)" },
}
}
},
new ChangeLogDto()
{
Id = Guid.CreateVersion7(),
Creation = DateTimeOffset.UtcNow.AddDays(-10),
IdAuthor = userId,
IdEditor = userId,
IdNext = changeLogItemCurrentId,
Obsolete = DateTimeOffset.UtcNow.AddDays(-5),
Value = new ChangeLogValuesDto(){
Id = Guid.CreateVersion7(),
Value = new Dictionary<string, object>() {
["1"] = new { id = 1, caption = "Изменение 1" },
["2"] = new { id = 2, caption = "Изменение 2" },
}
}
}
};
var result = new List<HistoryChangeLogDto>() {
new() {
Comment = "Петров И. Ю. попросил внести изменения",
DateTime = changeLogItemCreation,
DiscriminatorId = Guid.CreateVersion7(),
User = new UserDto()
{
Id = userId,
DisplayName = "Иванов И. И"
},
ChangeLogItems = changeLogItems
},
};
return Ok(result);
}
} }

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models; using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces; using DD.Persistence.Services.Interfaces;
@ -45,6 +46,7 @@ public class TimestampedValuesController : ControllerBase
/// </summary> /// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов</param> /// <param name="discriminatorIds">Набор дискриминаторов</param>
/// <param name="timestampBegin">Фильтр позднее даты</param> /// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="filterTree">Кастомный фильтр по набору значений</param>
/// <param name="columnNames">Фильтр свойств набора</param> /// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param> /// <param name="skip"></param>
/// <param name="take"></param> /// <param name="take"></param>
@ -52,9 +54,14 @@ public class TimestampedValuesController : ControllerBase
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromQuery] IEnumerable<Guid> discriminatorIds, DateTimeOffset? timestampBegin, [FromQuery] string[]? columnNames, int skip, int take, CancellationToken token) public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromQuery] IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin,
[FromQuery] TNode? filterTree,
[FromQuery] string[]? columnNames,
int skip, int take,
CancellationToken token)
{ {
var result = await timestampedValuesService.Get(discriminatorIds, timestampBegin, columnNames, skip, take, token); var result = await timestampedValuesService.Get(discriminatorIds, timestampBegin, filterTree, columnNames, skip, take, token);
return result.Any() ? Ok(result) : NoContent(); return result.Any() ? Ok(result) : NoContent();
} }

View File

@ -25,8 +25,11 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" /> <ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" /> <ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
<ProjectReference Include="..\DD.Persistence.Repository\DD.Persistence.Repository.csproj" />
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" /> <ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Docs\" />
</ItemGroup>
</Project> </Project>

View File

@ -1,16 +1,16 @@
using Mapster; using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models.Configurations;
using DD.Persistence.Services;
using DD.Persistence.Services.Interfaces;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using DD.Persistence.Services;
using DD.Persistence.Services.Interfaces;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection; using System.Reflection;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.API.Services;
namespace DD.Persistence.API; namespace DD.Persistence.API;
@ -30,6 +30,7 @@ public static class DependencyInjection
new OpenApiSchema {Type = "number", Format = "float" } new OpenApiSchema {Type = "number", Format = "float" }
] ]
}); });
c.MapType<TNode>(() => new OpenApiSchema { Type = "string" });
c.CustomOperationIds(e => c.CustomOperationIds(e =>
{ {
@ -54,6 +55,7 @@ public static class DependencyInjection
{ {
services.AddTransient<IWitsDataService, WitsDataService>(); services.AddTransient<IWitsDataService, WitsDataService>();
services.AddTransient<ITimestampedValuesService, TimestampedValuesService>(); services.AddTransient<ITimestampedValuesService, TimestampedValuesService>();
services.AddTransient<ChangeLogService>();
} }
#region Authentication #region Authentication

View File

@ -0,0 +1,359 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36" version="24.8.3">
<diagram name="Страница — 1" id="7k5Wemfp-yc9piGHxsiE">
<mxGraphModel dx="2049" dy="1054" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1300" pageHeight="1050" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="_dnhcZFeje3u91oS2JwL-1" value="" style="endArrow=none;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="355" y="930" as="sourcePoint" />
<mxPoint x="355" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-2" value="" style="endArrow=none;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="555" y="930" as="sourcePoint" />
<mxPoint x="555" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-4" value="&lt;b&gt;FRONT&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="325" y="180" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-5" value="&lt;b&gt;BACK&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="525" y="180" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-7" value="&lt;b&gt;CACHE&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="725" y="180" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-8" value="" style="endArrow=none;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="955" y="930" as="sourcePoint" />
<mxPoint x="955" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-9" value="&lt;b&gt;COMMIT REPOSITORY&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="880" y="180" width="155" height="30" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-32" value="" style="endArrow=none;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;align=center;" edge="1" parent="1" target="_dnhcZFeje3u91oS2JwL-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="755" y="930" as="sourcePoint" />
<mxPoint x="756.5000000000002" y="300" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-52" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="300" as="sourcePoint" />
<mxPoint x="755" y="300" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-53" value="&lt;span style=&quot;font-size: 12px; text-wrap-mode: wrap;&quot;&gt;GetOrCreate(userId, message)&lt;/span&gt;" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-52">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-67" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="755" y="310" as="sourcePoint" />
<mxPoint x="955" y="310.83" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-68" value="CreateCommit(userId, message)" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-67">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-69" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="955" y="330" as="sourcePoint" />
<mxPoint x="755" y="330" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-70" value="CommitId" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-69">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-88" value="" style="endArrow=none;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1155" y="930" as="sourcePoint" />
<mxPoint x="1155" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-89" value="&lt;b&gt;CHANGE LOG REPOSITORY&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="1060" y="180" width="185" height="30" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-90" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="755" y="340" as="sourcePoint" />
<mxPoint x="555" y="340" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-91" value="CommitId" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-90">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-93" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="380" as="sourcePoint" />
<mxPoint x="1155" y="380" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-94" value="AddRange(items, commitId)" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-93">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-96" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1155" y="410" as="sourcePoint" />
<mxPoint x="555" y="410" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-97" value="CREATED" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-96">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-100" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="420" as="sourcePoint" />
<mxPoint x="355" y="420" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-101" value="CREATED" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-100">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-102" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=9;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="555" y="420" as="sourcePoint" />
<mxPoint x="555" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-104" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="355" y="290" as="sourcePoint" />
<mxPoint x="555" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-105" value="Insert(items, message) [POST]" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-104">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-107" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=9;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="354.13" y="420" as="sourcePoint" />
<mxPoint x="354.13" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-108" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="355" y="520" as="sourcePoint" />
<mxPoint x="555" y="520" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-109" value="Update(items, message) [PUT]" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-108">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-110" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="530" as="sourcePoint" />
<mxPoint x="755" y="530" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-111" value="&lt;span style=&quot;font-size: 12px; text-wrap-mode: wrap;&quot;&gt;GetOrCreate(userId, message)&lt;/span&gt;" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-110">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-112" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="755" y="560" as="sourcePoint" />
<mxPoint x="555" y="560" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-113" value="CommitId" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-112">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-114" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=9;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="554.5699999999999" y="650" as="sourcePoint" />
<mxPoint x="554.5699999999999" y="520" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-115" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="610" as="sourcePoint" />
<mxPoint x="1155" y="610" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-116" value="UpdateRange(items, commitId)" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-115">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-117" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1155" y="640" as="sourcePoint" />
<mxPoint x="555" y="640" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-118" value="OK" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-117">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-119" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="650" as="sourcePoint" />
<mxPoint x="355" y="650" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-120" value="OK" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-119">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-121" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="755" y="530" as="sourcePoint" />
<mxPoint x="755" y="560" as="targetPoint" />
<Array as="points">
<mxPoint x="775" y="550" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-122" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="355" y="760" as="sourcePoint" />
<mxPoint x="555" y="760" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-123" value="Delete(items, message) [DELETE]" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-122">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-124" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="770" as="sourcePoint" />
<mxPoint x="755" y="770" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-125" value="&lt;span style=&quot;font-size: 12px; text-wrap-mode: wrap;&quot;&gt;GetOrCreate(userId, message)&lt;/span&gt;" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-124">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-126" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="755" y="800" as="sourcePoint" />
<mxPoint x="555" y="800" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-127" value="CommitId" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-126">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-128" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=9;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="554.5699999999999" y="890" as="sourcePoint" />
<mxPoint x="554.5699999999999" y="760" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-129" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="850" as="sourcePoint" />
<mxPoint x="1155" y="850" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-130" value="UpdateRange(items, commitId)" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-129">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-131" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1155" y="880" as="sourcePoint" />
<mxPoint x="555" y="880" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-132" value="OK" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-131">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-133" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="555" y="890" as="sourcePoint" />
<mxPoint x="355" y="890" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-134" value="OK" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-133">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-135" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="755" y="770" as="sourcePoint" />
<mxPoint x="755" y="800" as="targetPoint" />
<Array as="points">
<mxPoint x="775" y="790" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-136" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=9;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="354.57" y="650" as="sourcePoint" />
<mxPoint x="354.57" y="520" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-137" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=9;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="354.57" y="890" as="sourcePoint" />
<mxPoint x="354.57" y="760" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-138" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=10;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1155" y="411" as="sourcePoint" />
<mxPoint x="1155" y="381" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-139" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=10;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1155" y="641" as="sourcePoint" />
<mxPoint x="1155" y="611" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-140" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=10;curved=1;targetPerimeterSpacing=3;fillColor=#f8cecc;gradientColor=#ea6b66;strokeColor=#b85450;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1154.75" y="881" as="sourcePoint" />
<mxPoint x="1154.75" y="851" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-141" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=10;curved=1;targetPerimeterSpacing=3;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;opacity=50;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="755" y="830" as="sourcePoint" />
<mxPoint x="754.93" y="312" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-142" value="" style="endArrow=none;html=1;rounded=0;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=10;opacity=50;gradientColor=#7ea6e0;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="165" y="882" as="sourcePoint" />
<mxPoint x="165.22" y="292" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-143" value="&lt;font color=&quot;#330066&quot;&gt;User&lt;/font&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;align=center;" vertex="1" parent="1">
<mxGeometry x="155" y="180" width="21.5" height="43" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-146" value="" style="endArrow=classic;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="170" y="280" as="sourcePoint" />
<mxPoint x="360" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-147" value="SAVE" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;labelBackgroundColor=none;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-146">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-148" value="" style="endArrow=none;html=1;rounded=0;align=center;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="165.22" y="930" as="sourcePoint" />
<mxPoint x="165.22" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-149" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;align=center;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="355" y="892" as="sourcePoint" />
<mxPoint x="165" y="892" as="targetPoint" />
<Array as="points">
<mxPoint x="265" y="892" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-150" value="OK" style="edgeLabel;resizable=0;html=1;;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="_dnhcZFeje3u91oS2JwL-149">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-152" value="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;UML-диаграмма процесса пакетного редактирования&lt;/h1&gt;&lt;p&gt;.&lt;/p&gt;" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;align=center;" vertex="1" parent="1">
<mxGeometry x="75" y="50" width="1150" height="70" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-154" value="frame" style="shape=umlFrame;whiteSpace=wrap;html=1;pointerEvents=0;" vertex="1" parent="1">
<mxGeometry x="120" y="250" width="1120" height="680" as="geometry" />
</mxCell>
<mxCell id="_dnhcZFeje3u91oS2JwL-156" value="&lt;p style=&quot;line-height: 120%;&quot;&gt;Время жизни кеша задается внутри проекта&lt;/p&gt;" style="shape=note;size=20;whiteSpace=wrap;html=1;labelBackgroundColor=none;fillColor=#d0cee2;strokeColor=#56517e;opacity=50;" vertex="1" parent="1">
<mxGeometry x="760" y="660" width="120" height="100" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -48,4 +48,7 @@ Password: 12345
} }
``` ```
## Пакетное редактирование (на примере ChangeLog)
UML-диаграмма процесса редактирования находится по [ссылке](https://git.ddrilling.ru/on.nemtina/persistence/src/branch/master/DD.Persistence.API/Docs/ChangeLog_actions.drawio.xml)

View File

@ -0,0 +1,192 @@
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories;
using Microsoft.Extensions.Caching.Memory;
namespace DD.Persistence.API.Services;
/// <summary>
/// Сервис по работе с журналом изменений
/// </summary>
public class ChangeLogService
{
private readonly IMemoryCache memoryCache;
private readonly IChangeLogCommitRepository commitRepository;
private readonly IChangeLogRepository repository;
private readonly TimeSpan? AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
/// <summary>
/// ctor
/// </summary>
/// <param name="memoryCache"></param>
/// <param name="commitRepository"></param>
/// <param name="repository"></param>
public ChangeLogService(
IMemoryCache memoryCache,
IChangeLogCommitRepository commitRepository,
IChangeLogRepository repository)
{
this.memoryCache = memoryCache;
this.commitRepository = commitRepository;
this.repository = repository;
}
/// <summary>
/// Чтение или создание нового коммита
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
private async Task<ChangeLogCommitDto> GetOrCreateCommitAsync(CreateChangeLogCommitRequest request, CancellationToken token)
{
var key = (request.IdAuthor, request.Comment);
var commit = await memoryCache.GetOrCreateAsync(key, async (cacheEntry) =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = AbsoluteExpirationRelativeToNow;
var commit = await commitRepository.Add(request, token);
return commit;
});
return commit!;
}
/// <summary>
/// Добавление записи в журнал изменений
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="request"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<int> AddRange(Guid idDiscriminator, CreateChangeLogCommitRequest request, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{
var commit = await GetOrCreateCommitAsync(request, token);
var result = await repository.AddRange(idDiscriminator, commit, dtos, token);
return result;
}
/// <summary>
/// Пометить запись журнала изменений как удаленную
/// </summary>
/// <param name="ids"></param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<int> MarkAsDeleted(IEnumerable<Guid> ids, CreateChangeLogCommitRequest request, CancellationToken token)
{
var commit = await GetOrCreateCommitAsync(request, token);
var result = await repository.MarkAsDeleted(ids, commit, token);
return result;
}
/// <summary>
/// Очистить старые и добавить новые записи в журнал изменений
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="request"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<int> ClearAndAddRange(Guid idDiscriminator, CreateChangeLogCommitRequest request, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{
var commit = await GetOrCreateCommitAsync(request, token);
var result = await repository.ClearAndAddRange(idDiscriminator, commit, dtos, token);
return result;
}
/// <summary>
/// Обновить записи в журнале изменений
/// </summary>
/// <param name="commitRequest"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<int> UpdateRange(CreateChangeLogCommitRequest commitRequest, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{
var commit = await GetOrCreateCommitAsync(commitRequest, token);
var result = await repository.UpdateRange(commit, dtos, token);
return result;
}
/// <summary>
/// Получение актуальных записей на определенный момент времени (с пагинацией)
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="momentUtc"></param>
/// <param name="paginationRequest"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<PaginationContainer<ChangeLogValuesDto>> GetByDate(
Guid idDiscriminator,
DateTimeOffset momentUtc,
PaginationRequest paginationRequest,
CancellationToken token)
{
var result = await repository.GetByDate(idDiscriminator, momentUtc, paginationRequest, token);
return result;
}
/// <summary>
/// Получение измененных записей за период времени
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dateBegin"></param>
/// <param name="dateEnd"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<IEnumerable<ChangeLogDto>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
{
var result = await repository.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, token);
return result;
}
/// <summary>
/// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени)
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<IEnumerable<DateOnly>> GetDatesChange(Guid idDiscriminator, CancellationToken token)
{
var result = await repository.GetDatesChange(idDiscriminator, token);
return result;
}
/// <summary>
/// Получить данные, начиная с определенной даты
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dateBegin"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<IEnumerable<ChangeLogValuesDto>> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token)
{
var result = await repository.GetGtDate(idDiscriminator, dateBegin, token);
return result;
}
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token)
{
var result = await repository.GetDatesRange(idDiscriminator, token);
return result;
}
}

View File

@ -1,6 +1,6 @@
using DD.Persistence.Database.Model; using DD.Persistence.Database;
using DD.Persistence.Database.Model;
using DD.Persistence.Database.Postgres.Extensions; using DD.Persistence.Database.Postgres.Extensions;
using DD.Persistence.Repository;
namespace DD.Persistence.API; namespace DD.Persistence.API;
@ -14,7 +14,7 @@ public class Startup
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
// Add services to the container. // AddRange services to the container.
services.AddControllers(); services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

View File

@ -28,7 +28,6 @@ COPY ["DD.Persistence/DD.Persistence.csproj", "DD.Persistence/"]
COPY ["DD.Persistence.Database/DD.Persistence.Database.csproj", "DD.Persistence.Database/"] COPY ["DD.Persistence.Database/DD.Persistence.Database.csproj", "DD.Persistence.Database/"]
COPY ["DD.Persistence.Database.Postgres/DD.Persistence.Database.Postgres.csproj", "DD.Persistence.Database.Postgres/"] COPY ["DD.Persistence.Database.Postgres/DD.Persistence.Database.Postgres.csproj", "DD.Persistence.Database.Postgres/"]
COPY ["DD.Persistence.Models/DD.Persistence.Models.csproj", "DD.Persistence.Models/"] COPY ["DD.Persistence.Models/DD.Persistence.Models.csproj", "DD.Persistence.Models/"]
COPY ["DD.Persistence.Repository/DD.Persistence.Repository.csproj", "DD.Persistence.Repository/"]
RUN dotnet restore "./DD.Persistence.App/DD.Persistence.App.csproj" RUN dotnet restore "./DD.Persistence.App/DD.Persistence.App.csproj"

View File

@ -21,5 +21,5 @@
"grantType": "password", "grantType": "password",
"http://schemas.xmlsoap.org/ws/2005/05/identity /claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6" "http://schemas.xmlsoap.org/ws/2005/05/identity /claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6"
}, },
"PeristenceClientUrl": "http://localhost:5000/" "ClientUrl": "http://localhost:5000/"
} }

View File

@ -19,10 +19,10 @@ public class ChangeLogClient : BaseClient, IChangeLogClient
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<int> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token)
{ {
var result = await ExecuteGetResponse( var result = await ExecuteGetResponse(
async () => await refitChangeLogClient.ClearAndAddRange(idDiscriminator, dtos, token), token); async () => await refitChangeLogClient.ClearAndAddRange(idDiscriminator, dtos, comment, token), token);
return result; return result;
} }
@ -47,55 +47,28 @@ public class ChangeLogClient : BaseClient, IChangeLogClient
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<int> Add(Guid idDiscriminator, ChangeLogValuesDto dto, CancellationToken token) public async Task<int> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token)
{ {
var result = await ExecutePostResponse( var result = await ExecutePostResponse(
async () => await refitChangeLogClient.Add(idDiscriminator, dto, token), token); async () => await refitChangeLogClient.AddRange(idDiscriminator, dtos, comment, token), token);
return result; return result;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<int> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token)
{ {
var result = await ExecutePostResponse( var result = await ExecutePostResponse(
async () => await refitChangeLogClient.AddRange(idDiscriminator, dtos, token), token); async () => await refitChangeLogClient.UpdateRange(dtos, comment, token), token);
return result; return result;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<int> Update(ChangeLogValuesDto dto, CancellationToken token) public async Task<int> DeleteRange(IEnumerable<Guid> ids, string comment, CancellationToken token)
{ {
var result = await ExecutePostResponse( var result = await ExecutePostResponse(
async () => await refitChangeLogClient.Update(dto, token), token); async () => await refitChangeLogClient.DeleteRange(ids, comment, token), token);
return result;
}
/// <inheritdoc/>
public async Task<int> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{
var result = await ExecutePostResponse(
async () => await refitChangeLogClient.UpdateRange(dtos, token), token);
return result;
}
/// <inheritdoc/>
public async Task<int> Delete(Guid id, CancellationToken token)
{
var result = await ExecutePostResponse(
async () => await refitChangeLogClient.Delete(id, token), token);
return result;
}
/// <inheritdoc/>
public async Task<int> DeleteRange(IEnumerable<Guid> ids, CancellationToken token)
{
var result = await ExecutePostResponse(
async () => await refitChangeLogClient.DeleteRange(ids, token), token);
return result; return result;
} }

View File

@ -9,48 +9,34 @@ namespace DD.Persistence.Client.Clients.Interfaces;
/// </summary> /// </summary>
public interface IChangeLogClient : IDisposable public interface IChangeLogClient : IDisposable
{ {
/// <summary>
/// Добавить одну запись
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Add(Guid idDiscriminator, ChangeLogValuesDto dto, 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<int> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<int> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, 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<int> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<int> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary>
/// Удалить одну запись
/// </summary>
/// <param name="id"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Delete(Guid id, CancellationToken token);
/// <summary> /// <summary>
/// Удалить несколько записей /// Удалить несколько записей
/// </summary> /// </summary>
/// <param name="ids"></param> /// <param name="ids"></param>
/// <param name="comment"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<int> DeleteRange(IEnumerable<Guid> ids, CancellationToken token); Task<int> DeleteRange(IEnumerable<Guid> ids, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Получение актуальных данных на определенную дату (с пагинацией) /// Получение актуальных данных на определенную дату (с пагинацией)
@ -80,19 +66,12 @@ public interface IChangeLogClient : IDisposable
/// <returns></returns> /// <returns></returns>
Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token); Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token);
/// <summary>
/// Обновить одну запись
/// </summary>
/// <param name="dto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Update(ChangeLogValuesDto dto, 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<int> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<int> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
} }

View File

@ -1,4 +1,4 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients.Interfaces; namespace DD.Persistence.Client.Clients.Interfaces;
@ -24,28 +24,19 @@ public interface ITimestampedValuesClient : IDisposable
/// </summary> /// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов (идентификаторов)</param> /// <param name="discriminatorIds">Набор дискриминаторов (идентификаторов)</param>
/// <param name="timestampBegin">Фильтр позднее даты</param> /// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="filterTree">Кастомный фильтр по набору значений</param>
/// <param name="columnNames">Фильтр свойств набора</param> /// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param> /// <param name="skip"></param>
/// <param name="take"></param> /// <param name="take"></param>
/// <param name="token"></param> /// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin, DateTimeOffset? timestampBegin,
string? filterTree,
IEnumerable<string>? columnNames, IEnumerable<string>? columnNames,
int skip, int skip,
int take, int take,
CancellationToken token); CancellationToken token);
/// <summary>
/// Получить данные с фильтрацией для нескольких систем. Значение фильтра null - отключен
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="geTimestamp"></param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<T>> Get<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary> /// <summary>
/// Получить данные, начиная с заданной отметки времени /// Получить данные, начиная с заданной отметки времени
/// </summary> /// </summary>
@ -94,13 +85,6 @@ public interface ITimestampedValuesClient : IDisposable
/// <param name="token"></param> /// <param name="token"></param>
Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token); Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token);
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="idDiscriminator"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<T>> GetLast<T>(Guid idDiscriminator, int take, CancellationToken token);
} }

View File

@ -16,7 +16,7 @@ public interface IRefitChangeLogClient : IRefitClient, IDisposable
/// Импорт с заменой: удаление старых строк и добавление новых /// Импорт с заменой: удаление старых строк и добавление новых
/// </summary> /// </summary>
[Post($"{BaseRoute}/replace/{{idDiscriminator}}")] [Post($"{BaseRoute}/replace/{{idDiscriminator}}")]
Task<IApiResponse<int>> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<IApiResponse<int>> ClearAndAddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Получение актуальных данных на определенную дату (с пагинацией) /// Получение актуальных данных на определенную дату (с пагинацией)
@ -34,41 +34,23 @@ public interface IRefitChangeLogClient : IRefitClient, IDisposable
[Get($"{BaseRoute}/history/{{idDiscriminator}}")] [Get($"{BaseRoute}/history/{{idDiscriminator}}")]
Task<IApiResponse<IEnumerable<ChangeLogDto>>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token); Task<IApiResponse<IEnumerable<ChangeLogDto>>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token);
/// <summary>
/// Добавить одну запись
/// </summary>
[Post($"{BaseRoute}/{{idDiscriminator}}")]
Task<IApiResponse<int>> Add(Guid idDiscriminator, ChangeLogValuesDto dto, CancellationToken token);
/// <summary> /// <summary>
/// Добавить несколько записей /// Добавить несколько записей
/// </summary> /// </summary>
[Post($"{BaseRoute}/range/{{idDiscriminator}}")] [Post($"{BaseRoute}/{{idDiscriminator}}")]
Task<IApiResponse<int>> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<IApiResponse<int>> AddRange(Guid idDiscriminator, IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary>
/// Обновить одну запись
/// </summary>
[Put($"{BaseRoute}")]
Task<IApiResponse<int>> Update(ChangeLogValuesDto dto, CancellationToken token);
/// <summary> /// <summary>
/// Обновить несколько записей /// Обновить несколько записей
/// </summary> /// </summary>
[Put($"{BaseRoute}/range")] [Put($"{BaseRoute}")]
Task<IApiResponse<int>> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token); Task<IApiResponse<int>> UpdateRange(IEnumerable<ChangeLogValuesDto> dtos, string comment, CancellationToken token);
/// <summary>
/// Удалить одну запись
/// </summary>
[Delete($"{BaseRoute}")]
Task<IApiResponse<int>> Delete(Guid id, CancellationToken token);
/// <summary> /// <summary>
/// Удалить несколько записей /// Удалить несколько записей
/// </summary> /// </summary>
[Delete($"{BaseRoute}/range")] [Delete($"{BaseRoute}")]
Task<IApiResponse<int>> DeleteRange([Body] IEnumerable<Guid> ids, CancellationToken token); Task<IApiResponse<int>> DeleteRange([Body] IEnumerable<Guid> ids, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени) /// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени)

View File

@ -23,6 +23,7 @@ public interface IRefitTimestampedValuesClient : IRefitClient, IDisposable
[Get($"{baseUrl}")] [Get($"{baseUrl}")]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> Get([Query(CollectionFormat.Multi)] IEnumerable<Guid> discriminatorIds, Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> Get([Query(CollectionFormat.Multi)] IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin, DateTimeOffset? timestampBegin,
[Query] string? filterTree,
[Query(CollectionFormat.Multi)] IEnumerable<string>? columnNames, [Query(CollectionFormat.Multi)] IEnumerable<string>? columnNames,
int skip, int skip,
int take, int take,

View File

@ -0,0 +1,6 @@
namespace DD.Persistence.Client.Clients.Mapping.Abstractions;
internal interface IMapperStorage
{
TimestampedSetMapper GetMapper<T>(Guid idDiscriminator);
TimestampedSetMapper? GetMapper(Guid idDiscriminator);
}

View File

@ -0,0 +1,6 @@
using DD.Persistence.Client.Clients.Interfaces;
namespace DD.Persistence.Client.Clients.Mapping.Abstractions;
public interface ISetpointMappingClient : ISetpointClient
{
}

View File

@ -0,0 +1,45 @@
using DD.Persistence.Client.Clients.Interfaces;
namespace DD.Persistence.Client.Clients.Mapping.Abstractions;
/// <summary>
/// Маппинг - обертка для клиента по работе с данными
/// </summary>
public interface ITimestampedMappingClient : ITimestampedValuesClient
{
/// <summary>
/// Получить данные с преобразованием к заданному типу
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="geTimestamp"></param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="filterTree"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<T>> GetMapped<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получить набор данных, преобразованных к соответствующим типам из заданного конфига
/// </summary>
/// <param name="discriminatorIds"></param>
/// <param name="timestampBegin"></param>
/// <param name="filterTree"></param>
/// <param name="columnNames"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IDictionary<Guid, IEnumerable<object>>> GetMultiMapped(IEnumerable<Guid> discriminatorIds, DateTimeOffset? timestampBegin, string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получить данные с конца с преобразованием к заданному типу
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="idDiscriminator"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<T>> GetLastMapped<T>(Guid idDiscriminator, int take, CancellationToken token);
}

View File

@ -0,0 +1,103 @@
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Configurations;
using System.Text.Json;
namespace DD.Persistence.Client.Clients.Mapping.Clients;
/// <inheritdoc/>
public class SetpointMappingClient : ISetpointMappingClient
{
private readonly ISetpointClient setpointClient;
private readonly MappingConfig mappingConfigs;
/// <inheritdoc/>
public SetpointMappingClient(ISetpointClient setpointClient, MappingConfig mappingConfigs)
{
this.setpointClient = setpointClient;
this.mappingConfigs = mappingConfigs;
}
/// <inheritdoc/>
public async Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token)
=> (await setpointClient.GetCurrent(setpointKeys, token))
.Select(x => new SetpointValueDto
{
Key = x.Key,
Value = DeserializeValue(x.Key, (JsonElement)x.Value)
});
/// <inheritdoc/>
public async Task<Dictionary<Guid, object>> GetCurrentDictionary(IEnumerable<Guid> setpointConfigs, CancellationToken token)
{
var result = (await setpointClient.GetCurrent(setpointConfigs, token))
.ToDictionary(x => x.Key, x => DeserializeValue(x.Key, (JsonElement)x.Value));
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<SetpointValueDto>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment, CancellationToken token)
{
var result = await setpointClient.GetHistory(setpointKeys, historyMoment, token);
foreach (var dto in result)
dto.Value = DeserializeValue(dto.Key, (JsonElement)dto.Value);
return result;
}
/// <inheritdoc/>
public async Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token)
{
var result = await setpointClient.GetLog(setpointKeys, token);
foreach (var item in result)
DeserializeList(result[item.Key]);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<SetpointLogDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
{
var result = await setpointClient.GetPart(dateBegin, take, token);
DeserializeList(result);
return result;
}
/// <inheritdoc/>
public async Task Add(Guid setpointKey, object newValue, CancellationToken token)
=> await setpointClient.Add(setpointKey, newValue, token);
/// <inheritdoc/>
public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token)
=> await setpointClient.GetDatesRangeAsync(token);
/// <inheritdoc/>
public void Dispose()
{
setpointClient.Dispose();
}
private object DeserializeValue(Guid key, JsonElement value)
{
if (mappingConfigs.TryGetValue(key, out var type))
return value.Deserialize(type)!;
return value;
}
private void DeserializeList(IEnumerable<SetpointLogDto>? result)
{
if (result is null)
return;
foreach (var log in result)
log.Value = DeserializeValue(log.Key, (JsonElement)log.Value);
}
}

View File

@ -0,0 +1,107 @@
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using Microsoft.Extensions.Logging;
namespace DD.Persistence.Client.Clients.Mapping.Clients;
/// <inheritdoc/>
internal class TimestampedMappingClient : ITimestampedMappingClient
{
private readonly ITimestampedValuesClient client;
private readonly IMapperStorage mapperStorage;
public TimestampedMappingClient(ITimestampedValuesClient client, IMapperStorage mapperStorage)
{
this.client = client;
this.mapperStorage = mapperStorage;
}
/// <inheritdoc/>
public async Task<IEnumerable<T>> GetMapped<T>(Guid discriminatorId, DateTimeOffset? geTimestamp,
string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var data = await Get([discriminatorId], geTimestamp, filterTree, columnNames, skip, take, token);
var mapper = mapperStorage.GetMapper<T>(discriminatorId);
var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType<T>();
return mappedDtos;
}
/// <inheritdoc/>
public async Task<IEnumerable<T>> GetLastMapped<T>(Guid idDiscriminator, int take, CancellationToken token)
{
var data = await GetLast(idDiscriminator, take, token);
var mapper = mapperStorage.GetMapper<T>(idDiscriminator);
var mappedDtos = data.Select(mapper.DeserializeTimeStampedData).OfType<T>();
return mappedDtos;
}
/// <inheritdoc/>
public async Task<IDictionary<Guid, IEnumerable<object>>> GetMultiMapped(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp,
string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var data = await client.Get(discriminatorIds, geTimestamp, filterTree, columnNames, skip, take, token);
var result = discriminatorIds
.ToDictionary(discriminatorId => discriminatorId, discriminatorId =>
{
var mapper = mapperStorage.GetMapper(discriminatorId);
ArgumentNullException.ThrowIfNull(mapper);
var mappedDtos = data
.Where(e => e.DiscriminatorId == discriminatorId)
.Select(mapper.DeserializeTimeStampedData);
return mappedDtos;
});
return result;
}
/// <inheritdoc/>
public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
=> await client.AddRange(discriminatorId, dtos, token);
/// <inheritdoc/>
public async Task<int> Count(Guid discriminatorId, CancellationToken token)
=> await client.Count(discriminatorId, token);
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? timestampBegin,
string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
=> await client.Get(discriminatorIds, timestampBegin, filterTree, columnNames, skip, take, token);
/// <inheritdoc/>
public async Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
=> await client.GetDatesRange(discriminatorId, token);
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int take, CancellationToken token)
=> await client.GetFirst(discriminatorId, take, token);
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
=> await client.GetGtDate(discriminatorId, timestampBegin, token);
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int take, CancellationToken token)
=> await client.GetLast(discriminatorId, take, token);
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetResampledData(Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default)
=> await client.GetResampledData(discriminatorId, timestampBegin, intervalSec, approxPointsCount, token);
/// <inheritdoc/>
public void Dispose()
{
client.Dispose();
}
}

View File

@ -0,0 +1,33 @@
using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Models.Configurations;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
namespace DD.Persistence.Client.Clients.Mapping;
internal class MapperStorage : IMapperStorage
{
private readonly ConcurrentDictionary<Guid, TimestampedSetMapper> mapperCache = new();
private readonly MappingConfig mappingConfigs;
private readonly ILogger<TimestampedSetMapper> logger;
public MapperStorage(MappingConfig mappingConfigs, ILogger<TimestampedSetMapper> logger)
{
this.mappingConfigs = mappingConfigs;
this.logger = logger;
}
public TimestampedSetMapper? GetMapper(Guid idDiscriminator)
{
if (mappingConfigs.TryGetValue(idDiscriminator, out var type))
return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, type, logger));
return null;
}
public TimestampedSetMapper GetMapper<T>(Guid idDiscriminator)
{
return mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper(idDiscriminator, typeof(T), logger));
}
}

View File

@ -1,65 +1,62 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
namespace DD.Persistence.Client; namespace DD.Persistence.Client.Clients.Mapping;
internal abstract class TimestampedSetMapperBase internal class TimestampedSetMapper
{ {
public abstract object Map(TimestampedValuesDto data); private readonly Type entityType;
private readonly ILogger<TimestampedSetMapper> logger;
}
internal class TimestampedSetMapper<T> : TimestampedSetMapperBase
{
private readonly Type entityType = typeof(T);
public Guid IdDiscriminator { get; } public Guid IdDiscriminator { get; }
private readonly ConcurrentDictionary<string, PropertyInfo?> PropertyCache = new(); private readonly ConcurrentDictionary<string, PropertyInfo?> PropertyCache = new();
public TimestampedSetMapper(Guid idDiscriminator) public TimestampedSetMapper(Guid idDiscriminator, Type entityType, ILogger<TimestampedSetMapper> logger)
{ {
IdDiscriminator = idDiscriminator; IdDiscriminator = idDiscriminator;
this.entityType = entityType;
this.logger = logger;
} }
public override object Map(TimestampedValuesDto data) public object DeserializeTimeStampedData(TimestampedValuesDto data)
{ {
return DeserializeTimeStampedData(data)!;
}
public T DeserializeTimeStampedData(TimestampedValuesDto data)
{
if (entityType.IsValueType) if (entityType.IsValueType)
return MapStruct(data); return MapStruct(data);
else
return MapClass(data); return MapClass(data);
} }
private T MapClass(TimestampedValuesDto data) private object MapClass(TimestampedValuesDto data)
{ {
var entity = (T)RuntimeHelpers.GetUninitializedObject(typeof(T)); var entity = RuntimeHelpers.GetUninitializedObject(entityType);
foreach (var (propertyName, value) in data.Values) foreach (var (propertyName, value) in data.Values)
{ {
if (value is JsonElement jsonElement) if (value is JsonElement jsonElement)
SetPropertyValueFromJson(ref entity, propertyName, jsonElement); SetPropertyValueFromJson(ref entity, propertyName, jsonElement);
} }
SetPropertyValue(ref entity, "Timestamp", data.Timestamp); SetPropertyValue(ref entity, nameof(TimestampedValuesDto.Timestamp), data.Timestamp);
SetPropertyValue(ref entity, nameof(TimestampedValuesDto.DiscriminatorId), data.DiscriminatorId);
return entity; return entity;
} }
private T MapStruct(TimestampedValuesDto data) private object MapStruct(TimestampedValuesDto data)
{ {
var entity = Activator.CreateInstance<T>(); var entity = Activator.CreateInstance(entityType);
object boxedEntity = entity!; object boxedEntity = entity!;
foreach (var (propertyName, value) in data.Values) foreach (var (propertyName, value) in data.Values)
{ {
if (value is JsonElement jsonElement) if (value is JsonElement jsonElement)
SetPropertyValueForStructFromJson(ref boxedEntity, propertyName, jsonElement); SetPropertyValueForStructFromJson(ref boxedEntity, propertyName, jsonElement);
} }
SetPropertyValueForStruct(ref boxedEntity, "Timestamp", data.Timestamp); SetPropertyValueForStruct(ref boxedEntity, nameof(TimestampedValuesDto.Timestamp), data.Timestamp);
SetPropertyValueForStruct(ref boxedEntity, nameof(TimestampedValuesDto.DiscriminatorId), data.DiscriminatorId);
return (T)boxedEntity; return boxedEntity;
} }
private void SetPropertyValueForStructFromJson(ref object entity, string propertyName, JsonElement element) private void SetPropertyValueForStructFromJson(ref object entity, string propertyName, JsonElement element)
@ -75,6 +72,7 @@ internal class TimestampedSetMapper<T> : TimestampedSetMapperBase
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex.Message);
} }
} }
private void SetPropertyValueForStruct(ref object entity, string propertyName, object value) private void SetPropertyValueForStruct(ref object entity, string propertyName, object value)
@ -90,11 +88,12 @@ internal class TimestampedSetMapper<T> : TimestampedSetMapperBase
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex.Message);
} }
} }
private void SetPropertyValueFromJson(ref T entity, string propertyName, JsonElement jsonElement) private void SetPropertyValueFromJson(ref object entity, string propertyName, JsonElement jsonElement)
{ {
var property = GetPropertyInfo(propertyName); var property = GetPropertyInfo(propertyName);
@ -108,11 +107,11 @@ internal class TimestampedSetMapper<T> : TimestampedSetMapperBase
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex.Message);
} }
} }
private void SetPropertyValue(ref T entity, string propertyName, object value) private void SetPropertyValue(ref object entity, string propertyName, object value)
{ {
var property = GetPropertyInfo(propertyName); var property = GetPropertyInfo(propertyName);
if (property is null) if (property is null)
@ -125,6 +124,7 @@ internal class TimestampedSetMapper<T> : TimestampedSetMapperBase
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex.Message);
} }
} }

View File

@ -3,9 +3,6 @@ using DD.Persistence.Client.Clients.Base;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Models; using DD.Persistence.Models;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Globalization;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients; namespace DD.Persistence.Client.Clients;
@ -13,15 +10,12 @@ namespace DD.Persistence.Client.Clients;
public class SetpointClient : BaseClient, ISetpointClient public class SetpointClient : BaseClient, ISetpointClient
{ {
private readonly IRefitSetpointClient refitSetpointClient; private readonly IRefitSetpointClient refitSetpointClient;
private readonly ISetpointConfigStorage setpointConfigStorage;
public SetpointClient( public SetpointClient(
IRefitClientFactory<IRefitSetpointClient> refitSetpointClientFactory, IRefitClientFactory<IRefitSetpointClient> refitSetpointClientFactory,
ISetpointConfigStorage setpointConfigStorage,
ILogger<SetpointClient> logger) : base(logger) ILogger<SetpointClient> logger) : base(logger)
{ {
this.refitSetpointClient = refitSetpointClientFactory.Create(); this.refitSetpointClient = refitSetpointClientFactory.Create();
this.setpointConfigStorage = setpointConfigStorage;
} }
public async Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token) public async Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token)
@ -31,7 +25,7 @@ public class SetpointClient : BaseClient, ISetpointClient
return result!.Select(x => new SetpointValueDto { return result!.Select(x => new SetpointValueDto {
Key = x.Key, Key = x.Key,
Value = DeserializeValue(x.Key, x.Value) Value = x.Value
}); });
} }
@ -43,7 +37,7 @@ public class SetpointClient : BaseClient, ISetpointClient
async () => await refitSetpointClient.GetCurrent(setpointConfigs, token), token); async () => await refitSetpointClient.GetCurrent(setpointConfigs, token), token);
return result!.ToDictionary(x => x.Key,x => DeserializeValue(x.Key,x.Value)); return result!.ToDictionary(x => x.Key,x => (object)x.Value);
} }
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)
@ -51,9 +45,6 @@ public class SetpointClient : BaseClient, ISetpointClient
var result = await ExecuteGetResponse( var result = await ExecuteGetResponse(
async () => await refitSetpointClient.GetHistory(setpointKeys, historyMoment, token), token); async () => await refitSetpointClient.GetHistory(setpointKeys, historyMoment, token), token);
foreach(var dto in result)
dto.Value = DeserializeValue(dto.Key, (JsonElement)dto.Value);
return result!; return result!;
} }
@ -63,9 +54,6 @@ public class SetpointClient : BaseClient, ISetpointClient
var result = await ExecuteGetResponse( var result = await ExecuteGetResponse(
async () => await refitSetpointClient.GetLog(setpointKeys, token), token); async () => await refitSetpointClient.GetLog(setpointKeys, token), token);
foreach(var item in result)
DeserializeList(result[item.Key]);
return result!; return result!;
} }
@ -82,8 +70,6 @@ public class SetpointClient : BaseClient, ISetpointClient
var result = await ExecuteGetResponse( var result = await ExecuteGetResponse(
async () => await refitSetpointClient.GetPart(dateBegin, take, token), token); async () => await refitSetpointClient.GetPart(dateBegin, take, token), token);
DeserializeList(result);
return result!; return result!;
} }
@ -101,21 +87,4 @@ public class SetpointClient : BaseClient, ISetpointClient
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private object DeserializeValue(Guid key, JsonElement value)
{
if (setpointConfigStorage.TryGetType(key, out var type))
return value.Deserialize(type)!;
return value;
}
private void DeserializeList(IEnumerable<SetpointLogDto>? result)
{
foreach (var log in result)
log.Value = DeserializeValue(log.Key, (JsonElement)log.Value);
}
} }

View File

@ -1,4 +1,4 @@
using DD.Persistence.Client.Clients.Base; using DD.Persistence.Client.Clients.Base;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Models; using DD.Persistence.Models;
@ -19,8 +19,7 @@ public class TimestampedValuesClient : BaseClient, ITimestampedValuesClient
this.refitTimestampedSetClient = refitTimestampedSetClientFactory.Create(); this.refitTimestampedSetClient = refitTimestampedSetClientFactory.Create();
} }
/// <inheritdoc/>
private readonly ConcurrentDictionary<Guid, TimestampedSetMapperBase> mapperCache = new();
/// <inheritdoc/> /// <inheritdoc/>
public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> sets, CancellationToken token) public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> sets, CancellationToken token)
@ -32,22 +31,13 @@ public class TimestampedValuesClient : BaseClient, ITimestampedValuesClient
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token) public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{ {
var result = await ExecuteGetResponse( var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.Get(discriminatorIds, geTimestamp, columnNames, skip, take, token), token); async () => await refitTimestampedSetClient.Get(discriminatorIds, geTimestamp, filterTree, columnNames, skip, take, token), token);
return result!; return result!;
} }
/// <inheritdoc/>
public async Task<IEnumerable<T>> Get<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var data = await Get([discriminatorId], geTimestamp, columnNames, skip, take, token);
var mapper = GetMapper<T>(discriminatorId);
return data.Select(mapper.DeserializeTimeStampedData);
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token) public async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
{ {
@ -102,20 +92,7 @@ public class TimestampedValuesClient : BaseClient, ITimestampedValuesClient
return result; return result;
} }
/// <inheritdoc/>
public async Task<IEnumerable<T>> GetLast<T>(Guid idDiscriminator, int take, CancellationToken token)
{
var data = await GetLast(idDiscriminator, take, token);
var mapper = GetMapper<T>(idDiscriminator);
return data.Select(mapper.DeserializeTimeStampedData);
}
/// <inheritdoc/>
private TimestampedSetMapper<T> GetMapper<T>(Guid idDiscriminator)
{
return (TimestampedSetMapper<T>)mapperCache.GetOrAdd(idDiscriminator, name => new TimestampedSetMapper<T>(idDiscriminator));
}
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DD.Persistence.Client.CustomExceptions;
/// <summary>
/// Ошибка, которая выбрасывается в случае отсутствия необходимого свойства в appsettings.json
/// </summary>
public class AppSettingsPropertyNotFoundException : Exception
{
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public AppSettingsPropertyNotFoundException(string message)
: base(message) { }
}

View File

@ -52,6 +52,7 @@
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" /> <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageReference Include="Refit" Version="8.0.0" /> <PackageReference Include="Refit" Version="8.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" /> <PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,6 +1,10 @@
using DD.Persistence.Client.Clients; using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Mapping;
using DD.Persistence.Client.Clients.Mapping.Abstractions;
using DD.Persistence.Client.Clients.Mapping.Clients;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace DD.Persistence.Client; namespace DD.Persistence.Client;
@ -15,7 +19,7 @@ public static class DependencyInjection
/// </summary> /// </summary>
/// <param name="services"></param> /// <param name="services"></param>
/// <returns></returns> /// <returns></returns>
public static IServiceCollection AddPersistenceClients(this IServiceCollection services, Dictionary<Guid, Type>? setpointTypeConfigs = null) public static IServiceCollection AddPersistenceClients(this IServiceCollection services)
{ {
services.AddTransient(typeof(IRefitClientFactory<>), typeof(RefitClientFactory<>)); services.AddTransient(typeof(IRefitClientFactory<>), typeof(RefitClientFactory<>));
services.AddTransient<IChangeLogClient, ChangeLogClient>(); services.AddTransient<IChangeLogClient, ChangeLogClient>();
@ -25,10 +29,17 @@ public static class DependencyInjection
services.AddTransient<ITimestampedValuesClient, TimestampedValuesClient>(); services.AddTransient<ITimestampedValuesClient, TimestampedValuesClient>();
services.AddTransient<IWitsDataClient, WitsDataClient>(); services.AddTransient<IWitsDataClient, WitsDataClient>();
services.AddSingleton<ISetpointConfigStorage, SetpointConfigStorage>(provider => return services;
}
public static IServiceCollection AddPersistenceMapping(this IServiceCollection services, MappingConfig mappingConfigs)
{ {
return new SetpointConfigStorage(setpointTypeConfigs); services.AddSingleton(mappingConfigs);
}); services.AddSingleton<IMapperStorage, MapperStorage>();
services.AddTransient<ISetpointMappingClient, SetpointMappingClient>();
services.AddTransient<ITimestampedMappingClient, TimestampedMappingClient>();
return services; return services;
} }
} }

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DD.Persistence.Client;
public interface ISetpointConfigStorage
{
bool TryGetType(Guid id, out Type type);
}

View File

@ -1,8 +1,9 @@
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.CustomExceptions; using DD.Persistence.Client.Helpers;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Refit; using Refit;
using System.Configuration;
using System.Text.Json; using System.Text.Json;
namespace DD.Persistence.Client; namespace DD.Persistence.Client;
@ -22,10 +23,10 @@ public class RefitClientFactory<T> : IRefitClientFactory<T> where T : IRefitClie
//this.client = factory.CreateClient(); //this.client = factory.CreateClient();
this.client = client; this.client = client;
var baseUrl = configuration.GetSection("PeristenceClientUrl").Get<string>(); var baseUrl = configuration.GetSection("ClientUrl").Get<string>();
if (String.IsNullOrEmpty(baseUrl)) if (String.IsNullOrEmpty(baseUrl))
{ {
var exception = new AppSettingsPropertyNotFoundException("В настройках конфигурации не указан адрес Persistence сервиса."); var exception = new SettingsPropertyNotFoundException("В настройках конфигурации не указан адрес Persistence сервиса.");
logger.LogError(exception.Message); logger.LogError(exception.Message);

View File

@ -1,20 +0,0 @@
namespace DD.Persistence.Client;
internal class SetpointConfigStorage : ISetpointConfigStorage
{
private readonly Dictionary<Guid, Type> setpointTypeConfigs;
public SetpointConfigStorage(Dictionary<Guid, Type>? setpointTypeConfigs)
{
this.setpointTypeConfigs = setpointTypeConfigs?? new Dictionary<Guid, Type>();
}
public bool TryGetType(Guid id, out Type type)
{
return setpointTypeConfigs.TryGetValue(id, out type);
}
public void AddOrReplace(Guid id, Type type)
{
setpointTypeConfigs[id] = type;
}
}

View File

@ -1,6 +1,13 @@
using Microsoft.EntityFrameworkCore; using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Postgres.Repositories;
using DD.Persistence.Database.Postgres.RepositoriesCached;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace DD.Persistence.Database.Model; namespace DD.Persistence.Database.Model;

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("20250203061429_Init")] [Migration("20250221053248_Init")]
partial class Init partial class Init
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -37,22 +37,22 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи"); .HasComment("Дата создания записи");
b.Property<Guid>("IdAuthor") b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Дискриминатор таблицы"); .HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor") b.Property<Guid>("IdCreatedCommit")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Редактор"); .HasComment("Id коммита на создание записи");
b.Property<Guid?>("IdNext") b.Property<Guid?>("IdNext")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Id заменяющей записи"); .HasComment("Id заменяющей записи");
b.Property<Guid?>("IdObsoletedCommit")
.HasColumnType("uuid")
.HasComment("Id коммита на устаревание записи");
b.Property<DateTimeOffset?>("Obsolete") b.Property<DateTimeOffset?>("Obsolete")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата устаревания (например при удалении)"); .HasComment("Дата устаревания (например при удалении)");
@ -64,24 +64,36 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("IdCreatedCommit");
b.HasIndex("IdObsoletedCommit");
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b => modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{ {
b.Property<Guid>("DiscriminatorId") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Идентификатор схемы данных"); .HasComment("Id коммита");
b.Property<string>("PropNames") b.Property<string>("Comment")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("text")
.HasComment("Наименования полей в порядке индексации"); .HasComment("Комментарий к коммиту");
b.HasKey("DiscriminatorId"); b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания коммита");
b.ToTable("data_scheme"); b.Property<Guid>("IdAuthor")
.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 =>
@ -129,6 +141,30 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("parameter_data"); 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 => modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("Key")
@ -207,6 +243,23 @@ 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", "CreatedCommit")
.WithMany("ChangeLogCreatedItems")
.HasForeignKey("IdCreatedCommit")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DD.Persistence.Database.Entity.ChangeLogCommit", "ObsoletedCommit")
.WithMany("ChangeLogObsoletedItems")
.HasForeignKey("IdObsoletedCommit");
b.Navigation("CreatedCommit");
b.Navigation("ObsoletedCommit");
});
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")
@ -218,15 +271,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); b.Navigation("System");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b => modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{ {
b.HasOne("DD.Persistence.Database.Entity.DataScheme", "DataScheme") b.Navigation("ChangeLogCreatedItems");
.WithMany()
.HasForeignKey("DiscriminatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("DataScheme"); b.Navigation("ChangeLogObsoletedItems");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }

View File

@ -13,33 +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: "Дискриминатор таблицы"), IdAuthor = 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(
name: "data_scheme",
columns: table => new
{
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"),
PropNames = table.Column<string>(type: "jsonb", nullable: false, comment: "Наименования полей в порядке индексации")
},
constraints: table =>
{
table.PrimaryKey("PK_data_scheme", x => x.DiscriminatorId);
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
@ -69,6 +53,20 @@ namespace DD.Persistence.Database.Postgres.Migrations
table.PrimaryKey("PK_parameter_data", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp }); table.PrimaryKey("PK_parameter_data", x => new { x.DiscriminatorId, x.ParameterId, x.Timestamp });
}); });
migrationBuilder.CreateTable(
name: "scheme_property",
columns: table => new
{
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"),
Index = table.Column<int>(type: "integer", nullable: false, comment: "Индекс поля"),
PropertyName = table.Column<string>(type: "text", nullable: false, comment: "Наименования индексируемого поля"),
PropertyKind = table.Column<byte>(type: "smallint", nullable: false, comment: "Тип индексируемого поля")
},
constraints: table =>
{
table.PrimaryKey("PK_scheme_property", x => new { x.DiscriminatorId, x.Index });
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "setpoint", name: "setpoint",
columns: table => new columns: table => new
@ -94,12 +92,35 @@ namespace DD.Persistence.Database.Postgres.Migrations
constraints: table => constraints: table =>
{ {
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: "Ключ записи"),
DiscriminatorId = 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: "Значение"),
IdCreatedCommit = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id коммита на создание записи"),
IdObsoletedCommit = table.Column<Guid>(type: "uuid", nullable: true, comment: "Id коммита на устаревание записи")
},
constraints: table =>
{
table.PrimaryKey("PK_change_log", x => x.Id);
table.ForeignKey( table.ForeignKey(
name: "FK_timestamped_values_data_scheme_DiscriminatorId", name: "FK_change_log_change_log_commit_IdCreatedCommit",
column: x => x.DiscriminatorId, column: x => x.IdCreatedCommit,
principalTable: "data_scheme", principalTable: "change_log_commit",
principalColumn: "DiscriminatorId", principalColumn: "Id",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_change_log_change_log_commit_IdObsoletedCommit",
column: x => x.IdObsoletedCommit,
principalTable: "change_log_commit",
principalColumn: "Id");
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
@ -124,6 +145,16 @@ namespace DD.Persistence.Database.Postgres.Migrations
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); });
migrationBuilder.CreateIndex(
name: "IX_change_log_IdCreatedCommit",
table: "change_log",
column: "IdCreatedCommit");
migrationBuilder.CreateIndex(
name: "IX_change_log_IdObsoletedCommit",
table: "change_log",
column: "IdObsoletedCommit");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_tech_message_SystemId", name: "IX_tech_message_SystemId",
table: "tech_message", table: "tech_message",
@ -139,6 +170,9 @@ namespace DD.Persistence.Database.Postgres.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "parameter_data"); name: "parameter_data");
migrationBuilder.DropTable(
name: "scheme_property");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "setpoint"); name: "setpoint");
@ -149,10 +183,10 @@ namespace DD.Persistence.Database.Postgres.Migrations
name: "timestamped_values"); name: "timestamped_values");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "data_source_system"); name: "change_log_commit");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "data_scheme"); name: "data_source_system");
} }
} }
} }

View File

@ -34,22 +34,22 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи"); .HasComment("Дата создания записи");
b.Property<Guid>("IdAuthor") b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Дискриминатор таблицы"); .HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor") b.Property<Guid>("IdCreatedCommit")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Редактор"); .HasComment("Id коммита на создание записи");
b.Property<Guid?>("IdNext") b.Property<Guid?>("IdNext")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Id заменяющей записи"); .HasComment("Id заменяющей записи");
b.Property<Guid?>("IdObsoletedCommit")
.HasColumnType("uuid")
.HasComment("Id коммита на устаревание записи");
b.Property<DateTimeOffset?>("Obsolete") b.Property<DateTimeOffset?>("Obsolete")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата устаревания (например при удалении)"); .HasComment("Дата устаревания (например при удалении)");
@ -61,24 +61,36 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("IdCreatedCommit");
b.HasIndex("IdObsoletedCommit");
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b => modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{ {
b.Property<Guid>("DiscriminatorId") b.Property<Guid>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Идентификатор схемы данных"); .HasComment("Id коммита");
b.Property<string>("PropNames") b.Property<string>("Comment")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("text")
.HasComment("Наименования полей в порядке индексации"); .HasComment("Комментарий к коммиту");
b.HasKey("DiscriminatorId"); b.Property<DateTimeOffset>("Creation")
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания коммита");
b.ToTable("data_scheme"); b.Property<Guid>("IdAuthor")
.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 =>
@ -126,6 +138,30 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("parameter_data"); 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 => modelBuilder.Entity("DD.Persistence.Database.Entity.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("Key")
@ -204,6 +240,23 @@ 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", "CreatedCommit")
.WithMany("ChangeLogCreatedItems")
.HasForeignKey("IdCreatedCommit")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DD.Persistence.Database.Entity.ChangeLogCommit", "ObsoletedCommit")
.WithMany("ChangeLogObsoletedItems")
.HasForeignKey("IdObsoletedCommit");
b.Navigation("CreatedCommit");
b.Navigation("ObsoletedCommit");
});
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")
@ -215,15 +268,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); b.Navigation("System");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b => modelBuilder.Entity("DD.Persistence.Database.Entity.ChangeLogCommit", b =>
{ {
b.HasOne("DD.Persistence.Database.Entity.DataScheme", "DataScheme") b.Navigation("ChangeLogCreatedItems");
.WithMany()
.HasForeignKey("DiscriminatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("DataScheme"); b.Navigation("ChangeLogObsoletedItems");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
@ -7,11 +7,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="UuidExtensions" Version="1.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,15 +1,19 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Postgres.Repositories;
using DD.Persistence.Database.Postgres.RepositoriesCached;
using DD.Persistence.Database.Repositories;
using DD.Persistence.Database.RepositoriesCached;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Repository.Repositories;
using DD.Persistence.Repository.RepositoriesCached;
using Mapster; using Mapster;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Reflection; using System.Reflection;
namespace DD.Persistence.Repository; namespace DD.Persistence.Database;
public static class DependencyInjection public static class DependencyInjection
{ {
// ToDo: перенести в другой файл
public static void MapsterSetup() public static void MapsterSetup()
{ {
TypeAdapterConfig.GlobalSettings.Default.Config TypeAdapterConfig.GlobalSettings.Default.Config
@ -22,6 +26,12 @@ public static class DependencyInjection
Value = src.Value, Value = src.Value,
Id = src.Id Id = src.Id
}); });
TypeAdapterConfig<KeyValuePair<Guid, SchemePropertyDto>, SchemeProperty>.NewConfig()
.Map(dest => dest.DiscriminatorId, src => src.Key)
.Map(dest => dest.Index, src => src.Value.Index)
.Map(dest => dest.PropertyKind, src => src.Value.PropertyKind)
.Map(dest => dest.PropertyName, src => src.Value.PropertyName);
} }
public static IServiceCollection AddInfrastructure(this IServiceCollection services) public static IServiceCollection AddInfrastructure(this IServiceCollection services)
@ -32,13 +42,16 @@ public static class DependencyInjection
MapsterSetup(); MapsterSetup();
//services.AddTransient(typeof(PersistenceRepository<TimestampedValues>));
services.AddTransient<ISetpointRepository, SetpointRepository>(); services.AddTransient<ISetpointRepository, SetpointRepository>();
services.AddTransient<IChangeLogCommitRepository, ChangeLogCommitRepository>();
services.AddTransient<IChangeLogRepository, ChangeLogRepository>(); services.AddTransient<IChangeLogRepository, ChangeLogRepository>();
services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>(); services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>();
services.AddTransient<ITechMessagesRepository, TechMessagesRepository>(); services.AddTransient<ITechMessagesRepository, TechMessagesRepository>();
services.AddTransient<IParameterRepository, ParameterRepository>(); services.AddTransient<IParameterRepository, ParameterRepository>();
services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>(); services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>();
services.AddTransient<IDataSchemeRepository, DataSchemeCachedRepository>(); services.AddTransient<ISchemePropertyRepository, SchemePropertyCachedRepository>();
return services; return services;
} }

View File

@ -1,6 +1,5 @@
using DD.Persistence.Database.EntityAbstractions; using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.ModelsAbstractions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -11,23 +10,23 @@ namespace DD.Persistence.Database.Entity;
/// Часть записи, описывающая изменение /// Часть записи, описывающая изменение
/// </summary> /// </summary>
[Table("change_log")] [Table("change_log")]
public class ChangeLog : IChangeLog public class ChangeLog : IDiscriminatorItem, IChangeLog
{ {
[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("Автор изменения")]
public Guid IdAuthor { get; set; }
[Comment("Редактор")]
public Guid? IdEditor { get; set; }
/// <summary>
/// то же самое поле присутствует в связанной таблице change_log_commit (поле creation)
/// </summary>
[Comment("Дата создания записи")] [Comment("Дата создания записи")]
public DateTimeOffset Creation { get; set; } public DateTimeOffset Creation { get; set; }
/// <summary>
/// то же самое поле присутствует в связанной таблице change_log_commit (поле creation)
/// </summary>
[Comment("Дата устаревания (например при удалении)")] [Comment("Дата устаревания (например при удалении)")]
public DateTimeOffset? Obsolete { get; set; } public DateTimeOffset? Obsolete { get; set; }
@ -36,4 +35,22 @@ 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 IdCreatedCommit { get; set; }
[Comment("Id коммита на устаревание записи")]
public Guid? IdObsoletedCommit { get; set; }
/// <summary>
/// коммит для актуальной записи
/// </summary>
[Required, ForeignKey(nameof(IdCreatedCommit)), Comment("Коммит пользователя на создание записи")]
public virtual ChangeLogCommit CreatedCommit { get; set; } = null!;
/// <summary>
/// коммит для устаревшей записи
/// </summary>
[ForeignKey(nameof(IdObsoletedCommit)), Comment("Коммит пользователя на устаревание записи")]
public virtual ChangeLogCommit? ObsoletedCommit { get; set; }
} }

View File

@ -0,0 +1,35 @@
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 IdAuthor { get; set; }
[Comment("Дата создания коммита")]
public DateTimeOffset Creation { get; set; }
[Comment("Комментарий к коммиту")]
public required string Comment { get; set; }
[Required, InverseProperty(nameof(ChangeLog.CreatedCommit)), Comment("Записи, добавленные в журнал изменений")]
public virtual ICollection<ChangeLog> ChangeLogCreatedItems { get; set; } = null!;
[InverseProperty(nameof(ChangeLog.ObsoletedCommit)), Comment("Устаревшие записи в журнале изменений")]
public virtual ICollection<ChangeLog>? ChangeLogObsoletedItems { get; set; } = null!;
}

View File

@ -1,15 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("data_scheme")]
public class DataScheme
{
[Key, Comment("Идентификатор схемы данных"),]
public Guid DiscriminatorId { get; set; }
[Comment("Наименования полей в порядке индексации"), Column(TypeName = "jsonb")]
public string[] PropNames { get; set; } = [];
}

View File

@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
[Table("parameter_data")] [Table("parameter_data")]
[PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))] [PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))]
public class ParameterData : ITimestampedItem public class ParameterData : IDiscriminatorItem, ITimestampedItem
{ {
[Required, Comment("Дискриминатор системы")] [Required, Comment("Дискриминатор системы")]
public Guid DiscriminatorId { get; set; } public Guid DiscriminatorId { get; set; }

View File

@ -0,0 +1,24 @@
using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace DD.Persistence.Database.Entity;
[Table("scheme_property")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Index))]
public class SchemeProperty : IDiscriminatorItem
{
[Comment("Идентификатор схемы данных")]
public Guid DiscriminatorId { get; set; }
[Comment("Индекс поля")]
public int Index { get; set; }
[Comment("Наименования индексируемого поля")]
public required string PropertyName { get; set; }
[Comment("Тип индексируемого поля")]
public required JsonValueKind PropertyKind { get; set; }
}

View File

@ -28,4 +28,4 @@ public class TechMessage : ITimestampedItem
[Comment("Статус события")] [Comment("Статус события")]
public int EventState { get; set; } public int EventState { get; set; }
} }

View File

@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
[Table("timestamped_values")] [Table("timestamped_values")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))] [PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
public class TimestampedValues : ITimestampedItem public class TimestampedValues : IDiscriminatorItem, ITimestampedItem, IValuesItem
{ {
[Comment("Временная отметка"), Key] [Comment("Временная отметка"), Key]
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
@ -17,7 +17,4 @@ public class TimestampedValues : ITimestampedItem
[Comment("Данные"), Column(TypeName = "jsonb")] [Comment("Данные"), Column(TypeName = "jsonb")]
public required object[] Values { get; set; } public required object[] Values { get; set; }
[Required, ForeignKey(nameof(DiscriminatorId)), Comment("Идентификаторы")]
public virtual DataScheme? DataScheme { get; set; }
} }

View File

@ -10,16 +10,6 @@ public interface IChangeLog
/// </summary> /// </summary>
public Guid Id { get; set; } public Guid Id { get; set; }
/// <summary>
/// Автор изменения
/// </summary>
public Guid IdAuthor { get; set; }
/// <summary>
/// Редактор
/// </summary>
public Guid? IdEditor { get; set; }
/// <summary> /// <summary>
/// Дата создания записи /// Дата создания записи
/// </summary> /// </summary>
@ -38,7 +28,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,8 @@
namespace DD.Persistence.Database.EntityAbstractions;
public interface IDiscriminatorItem
{
/// <summary>
/// Дискриминатор
/// </summary>
Guid DiscriminatorId { get; set; }
}

View File

@ -0,0 +1,14 @@
using DD.Persistence.Database.Entity;
namespace DD.Persistence.Database.EntityAbstractions;
/// <summary>
/// Сущность с данными, принадлежащими к определенной схеме
/// </summary>
public interface IValuesItem
{
/// <summary>
/// Значения
/// </summary>
object[] Values { get; set; }
}

View File

@ -2,7 +2,7 @@ using System.Collections.Concurrent;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace DD.Persistence.Repository.Extensions; namespace DD.Persistence.Database.Extensions;
public static class EFExtensionsSortBy public static class EFExtensionsSortBy
{ {

View File

@ -0,0 +1,38 @@
using Ardalis.Specification;
using DD.Persistence.Database.Helpers;
using System.Linq.Expressions;
namespace DD.Persistence.Database.Postgres.Extensions;
public static class SpecificationExtensions
{
public static Expression<Func<T, bool>>? Or<T>(this ISpecification<T> spec, ISpecification<T> otherSpec)
{
var parameter = Expression.Parameter(typeof(T), "x");
var exprSpec1 = CombineWhereExpressions(spec.WhereExpressions, parameter);
var exprSpec2 = CombineWhereExpressions(otherSpec.WhereExpressions, parameter);
Expression? orExpression = exprSpec1 is not null && exprSpec2 is not null
? Expression.OrElse(exprSpec1, exprSpec2)
: exprSpec1 ?? exprSpec2;
if (orExpression is null)
return null;
var lambdaExpr = Expression.Lambda<Func<T, bool>>(orExpression, parameter);
return lambdaExpr;
}
public static Expression? CombineWhereExpressions<T>(IEnumerable<WhereExpressionInfo<T>> whereExpressions, ParameterExpression parameter)
{
Expression? newExpr = null;
foreach (var where in whereExpressions)
{
var expr = ParameterReplacerVisitor.Replace(where.Filter.Body, where.Filter.Parameters[0], parameter);
newExpr = newExpr is null ? expr : Expression.AndAlso(newExpr, expr);
}
return newExpr;
}
}

View File

@ -1,6 +1,6 @@
using System.Collections; using System.Collections;
namespace DD.Persistence.Repository; namespace DD.Persistence.Database.Postgres.Helpers;
/// <summary> /// <summary>
/// Цикличный массив /// Цикличный массив
/// </summary> /// </summary>

View File

@ -0,0 +1,116 @@
using Ardalis.Specification;
using Ardalis.Specification.EntityFrameworkCore;
using DD.Persistence.Database.Entity;
using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Database.Specifications.Operation;
using DD.Persistence.Database.Specifications.ValuesItem;
using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Filter.Models.Enumerations;
using DD.Persistence.Filter.Visitors;
using DD.Persistence.Models;
using System.Text.Json;
namespace DD.Persistence.Database.Postgres.Helpers;
public static class FilterBuilder
{
public static IQueryable<TEntity> ApplyFilter<TEntity>(this IQueryable<TEntity> query, DataSchemeDto dataSchemeDto, TNode root)
where TEntity : class, IValuesItem
{
var filterSpec = dataSchemeDto.BuildFilter<TEntity>(root);
if (filterSpec != null)
return query.WithSpecification(filterSpec);
return query;
}
private static ISpecification<TEntity>? BuildFilter<TEntity>(this DataSchemeDto dataSchemeDto, TNode root)
where TEntity : IValuesItem
{
var result = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(root);
return result;
}
private static ISpecification<TEntity>? BuildSpecificationByNextNode<TEntity>(this DataSchemeDto dataSchemeDto, TNode node)
where TEntity : IValuesItem
{
var visitor = new NodeVisitor<ISpecification<TEntity>?>(
dataSchemeDto.VertexProcessing<TEntity>,
dataSchemeDto.LeafProcessing<TEntity>
);
var result = node.AcceptVisitor(visitor);
return result;
}
private static ISpecification<TEntity>? VertexProcessing<TEntity>(this DataSchemeDto dataSchemeDto, TVertex vertex)
where TEntity : IValuesItem
{
var leftSpecification = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(vertex.Left);
var rigthSpecification = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(vertex.Rigth);
if (leftSpecification is null)
return rigthSpecification;
if (rigthSpecification is null)
return leftSpecification;
ISpecification<TEntity>? result = null;
switch (vertex.Operation)
{
case OperationEnum.And:
result = new AndSpec<TEntity>(leftSpecification, rigthSpecification);
break;
case OperationEnum.Or:
result = new OrSpec<TEntity>(leftSpecification, rigthSpecification);
break;
}
return result;
}
private static ISpecification<TEntity>? LeafProcessing<TEntity>(this DataSchemeDto dataSchemeDto, TLeaf leaf)
where TEntity : IValuesItem
{
var schemeProperty = dataSchemeDto.FirstOrDefault(e => e.PropertyName.Equals(leaf.PropName));
if (schemeProperty is null)
throw new ArgumentException($"Свойство {leaf.PropName} не найдено в схеме данных");
ISpecification<TEntity>? result = null;
switch (schemeProperty.PropertyKind)
{
case JsonValueKind.String:
var stringValue = Convert.ToString(leaf.Value);
var stringSpecifications = StringSpecifications<TEntity>();
result = stringSpecifications[leaf.Operation](schemeProperty.Index, stringValue);
break;
case JsonValueKind.Number:
var doubleValue = Convert.ToDouble(leaf.Value);
var doubleSpecifications = DoubleSpecifications<TEntity>();
result = doubleSpecifications[leaf.Operation](schemeProperty.Index, doubleValue);
break;
}
return result;
}
private static Dictionary<OperationEnum, Func<int, string?, ISpecification<TEntity>>> StringSpecifications<TEntity>()
where TEntity : IValuesItem => new()
{
{ OperationEnum.Equal, (int index, string? value) => new ValueEqualSpec<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, string? value) => new ValueNotEqualSpec<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, string? value) => new ValueGreateSpec<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, string? value) => new ValueGreateOrEqualSpec<TEntity>(index, value) },
{ OperationEnum.Less, (int index, string? value) => new ValueLessSpec<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, string? value) => new ValueLessOrEqualSpec<TEntity>(index, value) }
};
private static Dictionary<OperationEnum, Func<int, double?, ISpecification<TEntity>>> DoubleSpecifications<TEntity>()
where TEntity : IValuesItem => new()
{
{ OperationEnum.Equal, (int index, double? value) => new ValueEqualSpec<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, double? value) => new ValueNotEqualSpec<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, double? value) => new ValueGreateSpec<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, double? value) => new ValueGreateOrEqualSpec<TEntity>(index, value) },
{ OperationEnum.Less, (int index, double? value) => new ValueLessSpec<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, double? value) => new ValueLessOrEqualSpec<TEntity>(index, value) }
};
}

View File

@ -0,0 +1,20 @@
using System.Linq.Expressions;
namespace DD.Persistence.Database.Helpers;
public class ParameterReplacerVisitor : ExpressionVisitor
{
private readonly Expression _newExpression;
private readonly ParameterExpression _oldParameter;
private ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression)
{
_oldParameter = oldParameter;
_newExpression = newExpression;
}
internal static Expression Replace(Expression expression, ParameterExpression oldParameter, Expression newExpression)
=> new ParameterReplacerVisitor(oldParameter, newExpression).Visit(expression);
protected override Expression VisitParameter(ParameterExpression p)
=> p == _oldParameter ? _newExpression : p;
}

View File

@ -1,11 +1,10 @@
using Microsoft.EntityFrameworkCore; using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Models.Requests;
using DD.Persistence.Models.Common;
using DD.Persistence.ModelsAbstractions;
using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Extensions; using DD.Persistence.Extensions;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository; namespace DD.Persistence.Database.Postgres.Helpers;
/// <summary> /// <summary>
/// класс с набором методов, необходимых для фильтрации записей /// класс с набором методов, необходимых для фильтрации записей
@ -24,7 +23,6 @@ public static class QueryBuilders
return query; return query;
} }
public static async Task<PaginationContainer<TDto>> ApplyPagination<TEntity, TDto>( public static async Task<PaginationContainer<TDto>> ApplyPagination<TEntity, TDto>(
this IQueryable<TEntity> query, this IQueryable<TEntity> query,
PaginationRequest request, PaginationRequest request,

View File

@ -10,12 +10,14 @@ public class PersistenceDbContext : DbContext
{ {
public DbSet<Setpoint> Setpoint => Set<Setpoint>(); public DbSet<Setpoint> Setpoint => Set<Setpoint>();
public DbSet<DataScheme> DataSchemes => Set<DataScheme>(); public DbSet<SchemeProperty> SchemeProperty => Set<SchemeProperty>();
public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>(); public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>();
public DbSet<ChangeLog> ChangeLog => Set<ChangeLog>(); public DbSet<ChangeLog> ChangeLog => Set<ChangeLog>();
public DbSet<ChangeLogCommit> ChangeLogCommit => 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>();
@ -30,10 +32,6 @@ public class PersistenceDbContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<DataScheme>()
.Property(e => e.PropNames)
.HasJsonConversion();
modelBuilder.Entity<TimestampedValues>() modelBuilder.Entity<TimestampedValues>()
.Property(e => e.Values) .Property(e => e.Values)
.HasJsonConversion(); .HasJsonConversion();

View File

@ -0,0 +1,41 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UuidExtensions;
namespace DD.Persistence.Database.Repositories;
public class ChangeLogCommitRepository : IChangeLogCommitRepository
{
private DbContext db;
public ChangeLogCommitRepository(DbContext db)
{
this.db = db;
}
public async Task<ChangeLogCommitDto> Add(CreateChangeLogCommitRequest commitRequest, CancellationToken token)
{
var commit = new ChangeLogCommit()
{
Id = Uuid7.Guid(),
IdAuthor = commitRequest.IdAuthor,
Comment = commitRequest.Comment ?? string.Empty,
Creation = commitRequest.Creation,
};
db.Add(commit);
await db.SaveChangesAsync();
var commitDto = commit.Adapt<ChangeLogCommitDto>();
return commitDto;
}
}

View File

@ -1,13 +1,15 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Postgres.Helpers;
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 DD.Persistence.Repositories; using DD.Persistence.Repositories;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using UuidExtensions; using UuidExtensions;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Repositories;
public class ChangeLogRepository : IChangeLogRepository public class ChangeLogRepository : IChangeLogRepository
{ {
private readonly DbContext db; private readonly DbContext db;
@ -17,12 +19,20 @@ 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, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{ {
var entities = new List<ChangeLog>(); var entities = new List<ChangeLog>();
foreach (var dto in dtos) foreach (var values in dtos)
{ {
var entity = CreateEntityFromDto(idAuthor, idDiscriminator, dto); var entity = new ChangeLog()
{
Id = Uuid7.Guid(),
Creation = commitDto.Creation,
DiscriminatorId = idDiscriminator,
Value = values.Value,
IdCreatedCommit = commitDto.Id,
};
entities.Add(entity); entities.Add(entity);
} }
db.Set<ChangeLog>().AddRange(entities); db.Set<ChangeLog>().AddRange(entities);
@ -32,7 +42,7 @@ public class ChangeLogRepository : IChangeLogRepository
return result; return result;
} }
public async Task<int> MarkAsDeleted(Guid idEditor, IEnumerable<Guid> ids, CancellationToken token) public async Task<int> MarkAsDeleted(IEnumerable<Guid> ids, ChangeLogCommitDto commit, CancellationToken token)
{ {
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(s => ids.Contains(s.Id)) .Where(s => ids.Contains(s.Id))
@ -45,53 +55,51 @@ public class ChangeLogRepository : IChangeLogRepository
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
var result = await MarkAsObsolete(idEditor, entities, token);
return result;
}
public async Task<int> MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token)
{
var query = db.Set<ChangeLog>()
.Where(s => s.IdDiscriminator == idDiscriminator)
.Where(e => e.Obsolete == null);
var entities = await query.ToArrayAsync(token);
var result = await MarkAsObsolete(idEditor, entities, token);
return result;
}
private async Task<int> MarkAsObsolete(Guid idEditor, IEnumerable<ChangeLog> entities, CancellationToken token)
{
var updateTime = DateTimeOffset.UtcNow;
foreach (var entity in entities) foreach (var entity in entities)
{ {
entity.Obsolete = updateTime; entity.Obsolete = commit.Creation;
entity.IdEditor = idEditor; entity.IdObsoletedCommit = commit.Id;
} }
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> MarkAsDeleted(Guid idDiscriminator, ChangeLogCommitDto commit, CancellationToken token)
{
var query = db.Set<ChangeLog>()
.Where(s => s.DiscriminatorId == idDiscriminator)
.Where(e => e.Obsolete == null);
var entities = await query.ToArrayAsync(token);
foreach (var entity in entities)
{
entity.Obsolete = commit.Creation;
entity.DiscriminatorId = commit.Id;
}
return await db.SaveChangesAsync(token);
}
public async Task<int> ClearAndAddRange(Guid idDiscriminator, ChangeLogCommitDto commitDto, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{ {
var result = 0; var result = 0;
var changeLogIds = dtos.Select(c => c.Id);
var comment = commitDto.Comment;
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(idDiscriminator, commitDto, token);
result += await AddRange(idAuthor, idDiscriminator, dtos, token); result += await AddRange(idDiscriminator, commitDto, dtos, token);
await transaction.CommitAsync(token); await transaction.CommitAsync(token);
return result; return result;
} }
public async Task<int> UpdateRange(Guid idEditor, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token) public async Task<int> UpdateRange(ChangeLogCommitDto commitDto, IEnumerable<ChangeLogValuesDto> dtos, CancellationToken token)
{ {
var dbSet = db.Set<ChangeLog>(); var dbSet = db.Set<ChangeLog>();
@ -100,7 +108,6 @@ public class ChangeLogRepository : IChangeLogRepository
.Where(s => updatedIds.Contains(s.Id)) .Where(s => updatedIds.Contains(s.Id))
.ToDictionary(s => s.Id); .ToDictionary(s => s.Id);
var result = 0;
using var transaction = await db.Database.BeginTransactionAsync(token); using var transaction = await db.Database.BeginTransactionAsync(token);
foreach (var dto in dtos) foreach (var dto in dtos)
@ -111,20 +118,25 @@ 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 = new ChangeLog()
{
Id = Uuid7.Guid(),
Creation = commitDto.Creation,
DiscriminatorId = updatedEntity.DiscriminatorId,
Value = dto.Value,
IdCreatedCommit = commitDto.Id,
};
dbSet.Add(newEntity); dbSet.Add(newEntity);
updatedEntity.IdNext = newEntity.Id; updatedEntity.IdNext = newEntity.Id;
updatedEntity.Obsolete = DateTimeOffset.UtcNow; updatedEntity.Obsolete = commitDto.Creation;
updatedEntity.IdEditor = idEditor; updatedEntity.IdObsoletedCommit = commitDto.Id;
} }
result = await db.SaveChangesAsync(token); var result = await db.SaveChangesAsync(token);
await transaction.CommitAsync(token); await transaction.CommitAsync(token);
return result; return result;
} }
public async Task<PaginationContainer<ChangeLogValuesDto>> GetByDate( public async Task<PaginationContainer<ChangeLogValuesDto>> GetByDate(
@ -143,14 +155,14 @@ public class ChangeLogRepository : IChangeLogRepository
private IQueryable<ChangeLog> CreateQuery(Guid idDiscriminator) private IQueryable<ChangeLog> CreateQuery(Guid idDiscriminator)
{ {
var query = db.Set<ChangeLog>().Where(e => e.IdDiscriminator == idDiscriminator); var query = db.Set<ChangeLog>().Where(e => e.DiscriminatorId == idDiscriminator);
return query; 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 query = db.Set<ChangeLog>().Where(s => s.DiscriminatorId == idDiscriminator);
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);
@ -170,7 +182,7 @@ 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 query = db.Set<ChangeLog>().Where(e => e.DiscriminatorId == idDiscriminator);
var datesCreateQuery = query var datesCreateQuery = query
.Select(e => e.Creation) .Select(e => e.Creation)
@ -185,7 +197,7 @@ public class ChangeLogRepository : IChangeLogRepository
var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); var datesUpdate = await datesUpdateQuery.ToArrayAsync(token);
var dates = Enumerable.Concat(datesCreate, datesUpdate); var dates = datesCreate.Concat(datesUpdate);
var datesOnly = dates var datesOnly = dates
.Select(d => new DateOnly(d.Year, d.Month, d.Day)) .Select(d => new DateOnly(d.Year, d.Month, d.Day))
.Distinct() .Distinct()
@ -194,27 +206,11 @@ public class ChangeLogRepository : IChangeLogRepository
return datesOnly; return datesOnly;
} }
private static ChangeLog CreateEntityFromDto(Guid idAuthor, Guid idDiscriminator, ChangeLogValuesDto dto)
{
var entity = new ChangeLog()
{
Id = Uuid7.Guid(),
Creation = DateTimeOffset.UtcNow,
IdAuthor = idAuthor,
IdDiscriminator = idDiscriminator,
IdEditor = idAuthor,
Value = dto.Value
};
return entity;
}
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 query = this.db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.DiscriminatorId == idDiscriminator)
.Where(e => e.Creation >= date || e.Obsolete >= date); .Where(e => e.Creation >= date || e.Obsolete >= date);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
@ -227,12 +223,12 @@ 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>() var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.DiscriminatorId == idDiscriminator)
.GroupBy(e => 1) .GroupBy(e => 1)
.Select(group => new .Select(group => new
{ {
Min = group.Min(e => e.Creation), Min = group.Min(e => e.Creation),
Max = group.Max(e => (e.Obsolete.HasValue && e.Obsolete > e.Creation) Max = group.Max(e => e.Obsolete.HasValue && e.Obsolete > e.Creation
? e.Obsolete.Value ? e.Obsolete.Value
: e.Creation), : e.Creation),
}); });

View File

@ -4,7 +4,7 @@ using DD.Persistence.Repositories;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Postgres.Repositories;
public class DataSourceSystemRepository : IDataSourceSystemRepository public class DataSourceSystemRepository : IDataSourceSystemRepository
{ {
protected DbContext db; protected DbContext db;
@ -12,7 +12,7 @@ public class DataSourceSystemRepository : IDataSourceSystemRepository
{ {
this.db = db; this.db = db;
} }
protected virtual IQueryable<DataSourceSystem> GetQueryReadOnly() => db.Set<DataSourceSystem>(); protected IQueryable<DataSourceSystem> GetQueryReadOnly() => db.Set<DataSourceSystem>();
public virtual async Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token) public virtual async Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token)
{ {

View File

@ -5,7 +5,7 @@ using DD.Persistence.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Postgres.Repositories;
public class ParameterRepository : IParameterRepository public class ParameterRepository : IParameterRepository
{ {
private DbContext db; private DbContext db;
@ -15,7 +15,7 @@ public class ParameterRepository : IParameterRepository
this.db = db; this.db = db;
} }
protected virtual IQueryable<ParameterData> GetQueryReadOnly() => db.Set<ParameterData>(); protected IQueryable<ParameterData> GetQueryReadOnly() => db.Set<ParameterData>();
public async Task<DatesRangeDto> GetDatesRangeAsync(Guid idDiscriminator, CancellationToken token) public async Task<DatesRangeDto> GetDatesRangeAsync(Guid idDiscriminator, CancellationToken token)
{ {

View File

@ -0,0 +1,43 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Database.Repositories;
public class SchemePropertyRepository : ISchemePropertyRepository
{
protected DbContext db;
public SchemePropertyRepository(DbContext db)
{
this.db = db;
}
protected IQueryable<SchemeProperty> GetQueryReadOnly() => db.Set<SchemeProperty>();
public virtual async Task AddRange(DataSchemeDto dataSchemeDto, CancellationToken token)
{
var entities = dataSchemeDto.Select(e =>
KeyValuePair.Create(dataSchemeDto.DiscriminatorId, e)
.Adapt<SchemeProperty>()
);
await db.Set<SchemeProperty>().AddRangeAsync(entities, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<DataSchemeDto?> Get(Guid dataSchemeId, CancellationToken token)
{
var query = GetQueryReadOnly()
.Where(e => e.DiscriminatorId == dataSchemeId);
var entities = await query.ToArrayAsync(token);
DataSchemeDto? result = null;
if (entities.Length != 0)
{
var properties = entities.Select(e => e.Adapt<SchemePropertyDto>()).ToArray();
result = new DataSchemeDto(dataSchemeId, properties);
}
return result;
}
}

View File

@ -6,7 +6,7 @@ using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text.Json; using System.Text.Json;
namespace DD.Persistence.Repository.Repositories namespace DD.Persistence.Database.Postgres.Repositories
{ {
public class SetpointRepository : ISetpointRepository public class SetpointRepository : ISetpointRepository
{ {
@ -16,7 +16,7 @@ namespace DD.Persistence.Repository.Repositories
this.db = db; this.db = db;
} }
protected virtual IQueryable<Setpoint> GetQueryReadOnly() => db.Set<Setpoint>(); protected IQueryable<Setpoint> GetQueryReadOnly() => db.Set<Setpoint>();
public async Task<IEnumerable<SetpointValueDto>> GetCurrent( public async Task<IEnumerable<SetpointValueDto>> GetCurrent(
IEnumerable<Guid> setpointKeys, IEnumerable<Guid> setpointKeys,

View File

@ -7,7 +7,7 @@ using DD.Persistence.Repositories;
using Mapster; using Mapster;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories namespace DD.Persistence.Database.Postgres.Repositories
{ {
public class TechMessagesRepository : ITechMessagesRepository public class TechMessagesRepository : ITechMessagesRepository
{ {
@ -20,7 +20,7 @@ namespace DD.Persistence.Repository.Repositories
this.sourceSystemRepository = sourceSystemRepository; this.sourceSystemRepository = sourceSystemRepository;
} }
protected virtual IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>() protected IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>()
.Include(e => e.System); .Include(e => e.System);
public async Task<PaginationContainer<TechMessageDto>> GetPage(PaginationRequest request, CancellationToken token) public async Task<PaginationContainer<TechMessageDto>> GetPage(PaginationRequest request, CancellationToken token)

View File

@ -1,22 +1,25 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Postgres.Helpers;
using DD.Persistence.Filter.Models.Abstractions;
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;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Database.Postgres.Repositories;
public class TimestampedValuesRepository : ITimestampedValuesRepository public class TimestampedValuesRepository : ITimestampedValuesRepository
{ {
private readonly DbContext db; private readonly DbContext db;
private readonly ISchemePropertyRepository schemePropertyRepository;
public TimestampedValuesRepository(DbContext db) public TimestampedValuesRepository(DbContext db, ISchemePropertyRepository schemePropertyRepository)
{ {
this.db = db; this.db = db;
this.schemePropertyRepository = schemePropertyRepository;
} }
protected virtual IQueryable<TimestampedValues> GetQueryReadOnly() => this.db.Set<TimestampedValues>(); protected IQueryable<TimestampedValues> GetQueryReadOnly() => db.Set<TimestampedValues>();
public async virtual Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token) public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
{ {
var timestampedValuesEntities = dtos.Select(dto => new TimestampedValues() var timestampedValuesEntities = dtos.Select(dto => new TimestampedValues()
{ {
@ -25,35 +28,46 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
Values = dto.Values.Values.ToArray() Values = dto.Values.Values.ToArray()
}); });
await db.Set<TimestampedValues>().AddRangeAsync(timestampedValuesEntities, token); await db.AddRangeAsync(timestampedValuesEntities, token);
var result = await db.SaveChangesAsync(token); var result = await db.SaveChangesAsync(token);
return result; return result;
} }
public async virtual Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> discriminatorIds, public async Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin, DateTimeOffset? geTimestamp,
TNode? filterTree,
IEnumerable<string>? columnNames, IEnumerable<string>? columnNames,
int skip, int skip,
int take, int take,
CancellationToken token) CancellationToken token)
{ {
var query = GetQueryReadOnly() var resultQuery = Array.Empty<TimestampedValues>().AsQueryable();
.Where(entity => discriminatorIds.Contains(entity.DiscriminatorId)); foreach (var discriminatorId in discriminatorIds)
// Фильтрация по дате
if (timestampBegin.HasValue)
{ {
query = ApplyGeTimestamp(query, timestampBegin.Value); var scheme = await schemePropertyRepository.Get(discriminatorId, token);
if (scheme == null)
throw new NotSupportedException($"Для переданного дискриминатора {discriminatorId} не была обнаружена схема данных");
var geTimestampUtc = geTimestamp!.Value.ToUniversalTime();
var query = GetQueryReadOnly()
.Where(e => e.DiscriminatorId == discriminatorId)
.Where(entity => entity.Timestamp >= geTimestampUtc);
if (filterTree != null)
query = query.ApplyFilter(scheme, filterTree);
resultQuery = resultQuery.Any() ? resultQuery.Union(query) : query;
} }
var groupedQuery = resultQuery!
// Группировка отсортированных значений по DiscriminatorId
var groupQuery = query
.GroupBy(e => e.DiscriminatorId) .GroupBy(e => e.DiscriminatorId)
.Select(g => KeyValuePair.Create(g.Key, g.OrderBy(i => i.Timestamp).Skip(skip).Take(take))); .Select(g => KeyValuePair.Create(
var entities = await groupQuery.ToArrayAsync(token); g.Key,
g.OrderBy(i => i.Timestamp).Skip(skip).Take(take))
);
var entities = await groupedQuery.ToArrayAsync(token);
var result = entities.ToDictionary(k => k.Key, v => v.Value.Select(e => ( var result = entities.ToDictionary(k => k.Key, v => v.Value.Select(e => (
e.Timestamp, e.Timestamp,
e.Values e.Values
@ -62,7 +76,7 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; return result;
} }
public async virtual Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token) public async Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.OrderBy(e => e.Timestamp) .OrderBy(e => e.Timestamp)
@ -77,7 +91,7 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; return result;
} }
public async virtual Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token) public async Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.OrderByDescending(e => e.Timestamp) .OrderByDescending(e => e.Timestamp)
@ -93,7 +107,7 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
} }
// ToDo: прореживание должно осуществляться до материализации // ToDo: прореживание должно осуществляться до материализации
public async virtual Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetResampledData( public async Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetResampledData(
Guid discriminatorId, Guid discriminatorId,
DateTimeOffset dateBegin, DateTimeOffset dateBegin,
double intervalSec = 600d, double intervalSec = 600d,
@ -114,10 +128,11 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; return result;
} }
public async virtual Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token) public async Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetGtDate(Guid discriminatorId, DateTimeOffset gtTimestamp, CancellationToken token)
{ {
var gtTimestampUtc = gtTimestamp.ToUniversalTime();
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.Where(e => e.Timestamp > timestampBegin); .Where(entity => entity.Timestamp > gtTimestampUtc);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
var result = entities.Select(e => ( var result = entities.Select(e => (
@ -128,7 +143,7 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; return result;
} }
public async virtual Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token) public async Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.GroupBy(entity => entity.DiscriminatorId) .GroupBy(entity => entity.DiscriminatorId)
@ -153,26 +168,12 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return dto; return dto;
} }
public virtual Task<int> Count(Guid discriminatorId, CancellationToken token) public async Task<int> Count(Guid discriminatorId, CancellationToken token)
{ {
var dbSet = db.Set<TimestampedValues>(); var query = GetQueryReadOnly()
var query = dbSet.Where(entity => entity.DiscriminatorId == discriminatorId); .Where(e => e.DiscriminatorId == discriminatorId);
return query.CountAsync(token); var result = await query.CountAsync(token);
}
/// <summary>
/// Применить фильтр по дате
/// </summary>
/// <param name="query"></param>
/// <param name="timestampBegin"></param>
/// <returns></returns>
private IQueryable<TimestampedValues> ApplyGeTimestamp(IQueryable<TimestampedValues> query, DateTimeOffset timestampBegin)
{
var geTimestampUtc = timestampBegin.ToUniversalTime();
var result = query
.Where(entity => entity.Timestamp >= geTimestampUtc);
return result; return result;
} }

View File

@ -1,10 +1,10 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repository.Repositories; using DD.Persistence.Database.Postgres.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
namespace DD.Persistence.Repository.RepositoriesCached; namespace DD.Persistence.Database.Postgres.RepositoriesCached;
public class DataSourceSystemCachedRepository : DataSourceSystemRepository public class DataSourceSystemCachedRepository : DataSourceSystemRepository
{ {
private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey"; private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey";

View File

@ -1,21 +1,21 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repository.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using DD.Persistence.Database.Repositories;
namespace DD.Persistence.Repository.RepositoriesCached; namespace DD.Persistence.Database.RepositoriesCached;
public class DataSchemeCachedRepository : DataSchemeRepository public class SchemePropertyCachedRepository : SchemePropertyRepository
{ {
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
public DataSchemeCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db) public SchemePropertyCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db)
{ {
this.memoryCache = memoryCache; this.memoryCache = memoryCache;
} }
public override async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token) public override async Task AddRange(DataSchemeDto dataSourceSystemDto, CancellationToken token)
{ {
await base.Add(dataSourceSystemDto, token); await base.AddRange(dataSourceSystemDto, token);
memoryCache.Set(dataSourceSystemDto.DiscriminatorId, dataSourceSystemDto); memoryCache.Set(dataSourceSystemDto.DiscriminatorId, dataSourceSystemDto);
} }

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
namespace DD.Persistence.Database.Specifications.Operation;
public class AndSpec<TEntity> : Specification<TEntity>
{
public AndSpec(ISpecification<TEntity> first, ISpecification<TEntity> second)
{
if (first is null || second is null)
return;
ApplyCriteria(first);
ApplyCriteria(second);
}
private void ApplyCriteria(ISpecification<TEntity> specification)
{
foreach (var criteria in specification.WhereExpressions)
{
Query.Where(criteria.Filter);
}
}
}

View File

@ -0,0 +1,15 @@
using Ardalis.Specification;
using DD.Persistence.Database.Postgres.Extensions;
namespace DD.Persistence.Database.Specifications.Operation;
public class OrSpec<TEntity> : Specification<TEntity>
{
public OrSpec(ISpecification<TEntity> first, ISpecification<TEntity> second)
{
var orExpression = first.Or(second);
if (orExpression == null)
return;
Query.Where(orExpression);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ValuesItem;
/// <summary>
/// Спецификация эквивалентности значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueEqualSpec(int index, string? value)
{
Query.Where(e => Convert.ToString(e.Values[index]) == value);
}
public ValueEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) == value);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ValuesItem;
/// <summary>
/// Спецификация "больше либо равно" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueGreateOrEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueGreateOrEqualSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) >= 0);
}
public ValueGreateOrEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) >= value);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ValuesItem;
/// <summary>
/// Спецификация "больше" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueGreateSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueGreateSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) > 0);
}
public ValueGreateSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) > value);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ValuesItem;
/// <summary>
/// Спецификация "меньше либо равно" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueLessOrEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueLessOrEqualSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) <= 0);
}
public ValueLessOrEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) <= value);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ValuesItem;
/// <summary>
/// Спецификация "меньше" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueLessSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueLessSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) < 0);
}
public ValueLessSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) < value);
}
}

View File

@ -0,0 +1,22 @@
using Ardalis.Specification;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Specifications.ValuesItem;
/// <summary>
/// Спецификация неравенства значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueNotEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueNotEqualSpec(int index, string? value)
{
Query.Where(e => Convert.ToString(e.Values[index]) != value);
}
public ValueNotEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) != value);
}
}

View File

@ -15,6 +15,7 @@ namespace DD.Persistence.IntegrationTests.Controllers;
public class ChangeLogControllerTest : BaseIntegrationTest public class ChangeLogControllerTest : BaseIntegrationTest
{ {
private readonly IChangeLogClient client; private readonly IChangeLogClient client;
private readonly PaginationRequest paginationRequest;
private static readonly Random generatorRandomDigits = new(); private static readonly Random generatorRandomDigits = new();
public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory) public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory)
@ -25,22 +26,13 @@ public class ChangeLogControllerTest : BaseIntegrationTest
client = scope.ServiceProvider client = scope.ServiceProvider
.GetRequiredService<IChangeLogClient>(); .GetRequiredService<IChangeLogClient>();
}
[Fact] paginationRequest = new PaginationRequest()
public async Task ClearAndInsertRange_InEmptyDb()
{ {
// arrange Skip = 0,
dbContext.CleanupDbSet<ChangeLog>(); Take = 10,
SortSettings = String.Empty,
var idDiscriminator = Guid.NewGuid(); };
var dtos = Generate(2);
// act
var result = await client.ClearAndAddRange(idDiscriminator, dtos, new CancellationToken());
// assert
Assert.Equal(2, result);
} }
[Fact] [Fact]
@ -48,31 +40,24 @@ public class ChangeLogControllerTest : BaseIntegrationTest
{ {
// arrange // arrange
var insertedCount = 10; var insertedCount = 10;
var createdResult = CreateChangeLogItems(insertedCount, (-15, 15)); var newEntitiesData = await CreateAndReturnNewDtos(insertedCount, (-15, -1));
var idDiscriminator = createdResult.Item1; var idDiscriminator = newEntitiesData.Item1;
var dtos = createdResult.Item2.Select(e => e.Adapt<ChangeLogValuesDto>()); var dtos = newEntitiesData.Item2;
// act var newEntitiesData2 = await CreateAndReturnNewDtos(insertedCount, (-15, -1));
var result = await client.ClearAndAddRange(idDiscriminator, dtos, new CancellationToken()); var idDiscriminator2 = newEntitiesData2.Item1;
var dtos2 = newEntitiesData2.Item2;
//act
var result = await client.ClearAndAddRange(idDiscriminator, dtos, "Добавление новых элементов и очистка старых", CancellationToken.None);
var changeLogActualWithIdDiscriminator = await client.GetByDate(idDiscriminator2, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None);
var changeLogActualWithIdDiscriminator2 = await client.GetByDate(idDiscriminator, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None);
// assert // assert
Assert.Equal(insertedCount * 2, result); Assert.Equal(insertedCount * 2, result);
} Assert.Equal(insertedCount, changeLogActualWithIdDiscriminator.Count);
Assert.Equal(insertedCount, changeLogActualWithIdDiscriminator2.Count);
[Fact]
public async Task Add_returns_success()
{
// arrange
var count = 1;
var idDiscriminator = Guid.NewGuid();
var dtos = Generate(count);
var dto = dtos.FirstOrDefault()!;
// act
var result = await client.Add(idDiscriminator, dto, new CancellationToken());
// assert
Assert.Equal(count, result);
} }
[Fact] [Fact]
@ -82,9 +67,10 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var count = 3; var count = 3;
var idDiscriminator = Guid.NewGuid(); var idDiscriminator = Guid.NewGuid();
var dtos = Generate(count); var dtos = Generate(count);
var comment = "Создаю 3 элемента";
// act // act
var result = await client.AddRange(idDiscriminator, dtos, new CancellationToken()); var result = await client.AddRange(idDiscriminator, dtos, comment, CancellationToken.None);
// assert // assert
Assert.Equal(count, result); Assert.Equal(count, result);
@ -93,21 +79,23 @@ public class ChangeLogControllerTest : BaseIntegrationTest
[Fact] [Fact]
public async Task Update_returns_success() public async Task Update_returns_success()
{ {
// arrange //arrange
dbContext.CleanupDbSet<ChangeLog>(); dbContext.CleanupDbSet<ChangeLog>();
var idDiscriminator = Guid.NewGuid(); var idDiscriminator = Guid.NewGuid();
var dtos = Generate(1); var dtos = Generate(1);
var dto = dtos.FirstOrDefault()!; var dto = dtos.FirstOrDefault()!;
var result = await client.Add(idDiscriminator, dto, new CancellationToken()); var comment = "Создаю 1 элемент";
var result = await client.AddRange(idDiscriminator, [dto], comment, CancellationToken.None);
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>();
// act // act
result = await client.Update(dto, new CancellationToken()); comment = "Обновляю 1 элемент";
result = await client.UpdateRange([dto], comment, CancellationToken.None);
// assert // assert
Assert.Equal(2, result); Assert.Equal(2, result);
@ -139,71 +127,61 @@ public class ChangeLogControllerTest : BaseIntegrationTest
[Fact] [Fact]
public async Task UpdateRange_returns_success() public async Task UpdateRange_returns_success()
{ {
// arrange
var count = 2; var count = 2;
var idDiscriminator = Guid.NewGuid();
var dtos = Generate(count); var dtos = Generate(count);
var entities = dtos.Select(d => d.Adapt<ChangeLog>()).ToArray(); var comment = "Создаю 3 элемента";
dbContext.ChangeLog.AddRange(entities);
dbContext.SaveChanges();
dtos = entities.Select(c => new ChangeLogValuesDto()
{
Id = c.Id,
Value = c.Value
}).ToArray();
// act // act
var result = await client.UpdateRange(dtos, new CancellationToken()); var result = await client.AddRange(idDiscriminator, dtos, comment, CancellationToken.None);
var paginatedResult = await client.GetByDate(idDiscriminator, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None);
// act
comment = "Обновляю 3 элемента";
result = await client.UpdateRange(paginatedResult.Items, comment, CancellationToken.None);
// assert // assert
Assert.Equal(count * 2, result); Assert.Equal(count * 2, result);
} }
[Fact]
public async Task Delete_returns_success()
{
// arrange
var dtos = Generate(1);
var dto = dtos.FirstOrDefault()!;
var entity = dto.Adapt<ChangeLog>();
dbContext.ChangeLog.Add(entity);
dbContext.SaveChanges();
// act
var result = await client.Delete(entity.Id, new CancellationToken());
// assert
Assert.Equal(1, result);
}
[Fact] [Fact]
public async Task DeleteRange_returns_success() public async Task DeleteRange_returns_success()
{ {
// arrange // arrange
var count = 10; var insertedCount = 10;
var dtos = Generate(count); var newEntitiesData = await CreateAndReturnNewDtos(insertedCount, (-15, -1));
var entities = dtos.Select(d => d.Adapt<ChangeLog>()).ToArray(); var idDiscriminator = newEntitiesData.Item1;
dbContext.ChangeLog.AddRange(entities); var dtos = newEntitiesData.Item2;
dbContext.SaveChanges();
var newEntitiesData2 = await CreateAndReturnNewDtos(insertedCount, (-15, -1));
var idDiscriminator2 = newEntitiesData2.Item1;
var dtos2 = newEntitiesData2.Item2;
// act // act
var ids = entities.Select(e => e.Id); var ids = dtos.Select(e => e.Id);
var result = await client.DeleteRange(ids, new CancellationToken()); var result = await client.DeleteRange(ids, "Удаление нескольких записей", CancellationToken.None);
// assert // assert
Assert.Equal(count, result); Assert.Equal(insertedCount, result);
// act
var actualWithIdDescriminator = await client.GetByDate(idDiscriminator, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None);
var actualWithIdDescriminator2 = await client.GetByDate(idDiscriminator2, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None);
// assert
Assert.Equal(0, actualWithIdDescriminator.Count);
Assert.Equal(insertedCount, actualWithIdDescriminator2.Count);
} }
[Fact] [Fact]
public async Task GetDatesRange_returns_success() public async Task GetDatesRange_returns_success()
{ {
// arrange //arrange
var changeLogItems = CreateChangeLogItems(3, (-15, 15)); var changeLogItems = await CreateAndReturnNewEntities(3, (-15, -1));
var idDiscriminator = changeLogItems.Item1; var idDiscriminator = changeLogItems.Item1;
var entities = changeLogItems.Item2.OrderBy(e => e.Creation); var entities = changeLogItems.Item2.OrderBy(c => c.Creation);
// act // act
var result = await client.GetDatesRange(idDiscriminator, new CancellationToken()); var result = await client.GetDatesRange(idDiscriminator, CancellationToken.None);
// assert // assert
Assert.NotNull(result); Assert.NotNull(result);
@ -228,7 +206,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
//создаем записи //создаем записи
var count = 5; var count = 5;
var changeLogItems = CreateChangeLogItems(count, (-15, 15)); var changeLogItems = await CreateAndReturnNewDtos(count, (-15, -1));
var idDiscriminator = changeLogItems.Item1; var idDiscriminator = changeLogItems.Item1;
var entities = changeLogItems.Item2; var entities = changeLogItems.Item2;
@ -237,14 +215,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var ids = entities.Select(e => e.Id); var ids = entities.Select(e => e.Id);
var idsToDelete = ids.Skip(2); var idsToDelete = ids.Skip(2);
var deletedCount = await client.DeleteRange(idsToDelete, new CancellationToken()); var deletedCount = await client.DeleteRange(idsToDelete, "Удаление нескольких записей", CancellationToken.None);
var paginationRequest = new PaginationRequest()
{
Skip = 0,
Take = 10,
SortSettings = String.Empty,
};
var moment = DateTimeOffset.UtcNow.AddDays(16); var moment = DateTimeOffset.UtcNow.AddDays(16);
var result = await client.GetByDate(idDiscriminator, moment, paginationRequest, new CancellationToken()); var result = await client.GetByDate(idDiscriminator, moment, paginationRequest, new CancellationToken());
@ -260,8 +231,8 @@ public class ChangeLogControllerTest : BaseIntegrationTest
} }
[Theory] [Theory]
[InlineData(5, -15, 15, -20, 20, 10)] [InlineData(5, -15, -5, -20, 20, 10)]
[InlineData(5, -15, -10, -16, -9, 5)] [InlineData(5, -15, -10, -16, 9, 10)]
public async Task GetChangeLogForInterval_returns_success( public async Task GetChangeLogForInterval_returns_success(
int insertedCount, int insertedCount,
int daysBeforeNowChangeLog, int daysBeforeNowChangeLog,
@ -276,17 +247,16 @@ public class ChangeLogControllerTest : BaseIntegrationTest
//создаем записи //создаем записи
var count = insertedCount; var count = insertedCount;
var daysRange = (daysBeforeNowChangeLog, daysAfterNowChangeLog); var daysRange = (daysBeforeNowChangeLog, daysAfterNowChangeLog);
var changeLogItems = CreateChangeLogItems(count, daysRange); var changeLogItems = await CreateAndReturnNewDtos(count, daysRange);
var idDiscriminator = changeLogItems.Item1; var idDiscriminator = changeLogItems.Item1;
var entities = changeLogItems.Item2; var dtos = changeLogItems.Item2;
var dtos = entities.Select(e => e.Adapt<ChangeLogValuesDto>()).ToArray(); await client.UpdateRange(dtos, "Обновляем несколько записей", CancellationToken.None);
await client.UpdateRange(dtos, new CancellationToken());
//act //act
var dateBegin = DateTimeOffset.UtcNow.AddDays(daysBeforeNowFilter); var dateBegin = DateTimeOffset.UtcNow.AddDays(daysBeforeNowFilter);
var dateEnd = DateTimeOffset.UtcNow.AddDays(daysAfterNowFilter); var dateEnd = DateTimeOffset.UtcNow.AddDays(daysAfterNowFilter);
var result = await client.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, new CancellationToken()); var result = await client.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, CancellationToken.None);
//assert //assert
Assert.NotNull(result); Assert.NotNull(result);
@ -308,7 +278,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
} }
private (Guid, ChangeLog[]) CreateChangeLogItems(int count, (int, int) daysRange) private async Task<(Guid, IEnumerable<ChangeLogValuesDto>)> CreateAndReturnNewDtos(int count, (int, int) daysRange)
{ {
var minDayCount = daysRange.Item1; var minDayCount = daysRange.Item1;
var maxDayCount = daysRange.Item2; var maxDayCount = daysRange.Item2;
@ -318,13 +288,48 @@ 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;
}).ToArray(); }).ToArray();
dtos = entities.Select(e => e.Adapt<ChangeLogValuesDto>());
// act
var result = await client.AddRange(idDiscriminator, dtos, "Добавление элементов", CancellationToken.None);
var paginatedResult = await client.GetByDate(idDiscriminator, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None);
return (idDiscriminator, paginatedResult.Items);
}
private async Task<(Guid, IEnumerable<ChangeLog>)> CreateAndReturnNewEntities(int count, (int, int) daysRange)
{
var commit = new ChangeLogCommit()
{
Comment = "Комментарий к коммиту",
Creation = DateTimeOffset.UtcNow,
Id = Guid.NewGuid(),
};
dbContext.ChangeLogCommit.Add(commit);
await dbContext.SaveChangesAsync();
var minDayCount = daysRange.Item1;
var maxDayCount = daysRange.Item2;
Guid idDiscriminator = Guid.NewGuid();
var dtos = Generate(count);
var entities = dtos.Select(d =>
{
var entity = d.Adapt<ChangeLog>();
entity.DiscriminatorId = idDiscriminator;
entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount));
entity.IdCreatedCommit = commit.Id;
return entity;
}).ToArray();
dbContext.ChangeLog.AddRange(entities); dbContext.ChangeLog.AddRange(entities);
dbContext.SaveChanges(); await dbContext.SaveChangesAsync();
return (idDiscriminator, entities); return (idDiscriminator, entities);
} }

View File

@ -1,10 +1,10 @@
using DD.Persistence.Client; using DD.Persistence.Client;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Clients.Mapping.Clients;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models.Configurations;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Json; using System.Text.Json;
using Xunit; using Xunit;
@ -13,17 +13,14 @@ namespace DD.Persistence.IntegrationTests.Controllers
public class SetpointControllerTest : BaseIntegrationTest public class SetpointControllerTest : BaseIntegrationTest
{ {
private readonly ISetpointClient setpointClient; private readonly ISetpointClient setpointClient;
private readonly SetpointConfigStorage configStorage;
public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory) public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
var refitClientFactory = scope.ServiceProvider var refitClientFactory = scope.ServiceProvider
.GetRequiredService<IRefitClientFactory<IRefitSetpointClient>>(); .GetRequiredService<IRefitClientFactory<IRefitSetpointClient>>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<SetpointClient>>();
setpointClient = scope.ServiceProvider setpointClient = scope.ServiceProvider
.GetRequiredService<ISetpointClient>(); .GetRequiredService<ISetpointClient>();
configStorage = (SetpointConfigStorage)scope.ServiceProvider.GetRequiredService<ISetpointConfigStorage>();
} }
@ -32,12 +29,16 @@ namespace DD.Persistence.IntegrationTests.Controllers
{ {
var id = Guid.Parse("e0fcad22-1761-476e-a729-a3c59d51ba41"); var id = Guid.Parse("e0fcad22-1761-476e-a729-a3c59d51ba41");
configStorage.AddOrReplace(id, typeof(float)); var config = new MappingConfig();
config[id] = typeof(float);
var setpointMapper = new SetpointMappingClient(setpointClient, config);
await setpointClient.Add(id, 48.3f, CancellationToken.None); await setpointClient.Add(id, 48.3f, CancellationToken.None);
//act //act
var response = await setpointClient.GetCurrent([id], CancellationToken.None); var response = await setpointMapper.GetCurrent([id], CancellationToken.None);
//assert //assert
Assert.NotNull(response); Assert.NotNull(response);

View File

@ -3,7 +3,6 @@ using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Extensions;
using DD.Persistence.Models; using DD.Persistence.Models;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -39,7 +38,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
} }
[Fact] [Fact]
public async Task Get_returns_success() public async Task Get_returns_BadRequest()
{ {
//arrange //arrange
Cleanup(); Cleanup();
@ -50,11 +49,18 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
var secondDiscriminatorId = Guid.NewGuid(); var secondDiscriminatorId = Guid.NewGuid();
discriminatorIds.Append(secondDiscriminatorId); discriminatorIds.Append(secondDiscriminatorId);
try
{
//act //act
var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId], null, null, 0, 1, CancellationToken.None); var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId], null, null, null, 0, 1, CancellationToken.None);
}
catch (Exception ex)
{
var expectedMessage = $"На сервере произошла ошибка, в результате которой он не может успешно обработать запрос";
//assert //assert
Assert.Null(response); Assert.Equal(expectedMessage, ex.Message);
}
} }
[Fact] [Fact]
@ -71,22 +77,25 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1); var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
var columnNames = new List<string>() { "A", "C" }; var columnNames = new List<string>() { "A", "C" };
var skip = 2; var skip = 0;
var take = 16; var take = 6; // Ровно столько значений будет удовлетворять фильтру (\"A\">3) (для одного дискриминатора)
var customFilter = "(\"A\">3)";
var dtos = (await AddRange(firstDiscriminatorId)).ToList(); var dtos = (await AddRange(firstDiscriminatorId)).ToList();
dtos.AddRange(await AddRange(secondDiscriminatorId)); dtos.AddRange(await AddRange(secondDiscriminatorId));
//act //act
var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId], var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId],
timestampBegin, columnNames, skip, take, CancellationToken.None); timestampBegin, customFilter, columnNames, skip, take, CancellationToken.None);
//assert //assert
Assert.NotNull(response); Assert.NotNull(response);
Assert.NotEmpty(response); Assert.NotEmpty(response);
var expectedCount = take * 2;
var actualCount = response.Count(); var actualCount = response.Count();
Assert.Equal(take, actualCount); Assert.Equal(expectedCount, actualCount);
var actualColumnNames = response.SelectMany(e => e.Values.Keys).Distinct().ToList(); var actualColumnNames = response.SelectMany(e => e.Values.Keys).Distinct().ToList();
Assert.Equal(columnNames, actualColumnNames); Assert.Equal(columnNames, actualColumnNames);
@ -379,7 +388,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
var response = await timestampedValuesClient.AddRange(discriminatorId, generatedDtos, CancellationToken.None); var response = await timestampedValuesClient.AddRange(discriminatorId, generatedDtos, CancellationToken.None);
// assert // assert
Assert.Equal(generatedDtos.Count(), response); //Assert.Equal(generatedDtos.Count(), response);
return generatedDtos; return generatedDtos;
} }
@ -407,8 +416,12 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
private void Cleanup() private void Cleanup()
{ {
foreach (var item in discriminatorIds)
{
memoryCache.Remove(item);
}
discriminatorIds = []; discriminatorIds = [];
dbContext.CleanupDbSet<TimestampedValues>(); dbContext.CleanupDbSet<TimestampedValues>();
dbContext.CleanupDbSet<DataScheme>(); dbContext.CleanupDbSet<SchemeProperty>();
} }
} }

View File

@ -0,0 +1,30 @@
namespace DD.Persistence.Models.Requests;
/// <summary>
/// Модель коммита с изменениями
/// </summary>
public class ChangeLogCommitDto : CreateChangeLogCommitRequest
{
/// <summary>
///
/// </summary>
public ChangeLogCommitDto()
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="idAuthor"></param>
/// <param name="comment"></param>
public ChangeLogCommitDto(Guid idAuthor, string? comment) : base(idAuthor, comment)
{
}
/// <summary>
/// Id
/// </summary>
public Guid Id { get; set; }
}

View File

@ -0,0 +1,4 @@
namespace DD.Persistence.Models.Configurations;
public class MappingConfig : Dictionary<Guid, Type>
{
}

View File

@ -1,9 +1,11 @@
namespace DD.Persistence.Models; using System.Collections;
namespace DD.Persistence.Models;
/// <summary> /// <summary>
/// Схема для набора данных /// Схема для набора данных
/// </summary> /// </summary>
public class DataSchemeDto public class DataSchemeDto : IEnumerable<SchemePropertyDto>, IEquatable<IEnumerable<SchemePropertyDto>>
{ {
/// <summary> /// <summary>
/// Дискриминатор /// Дискриминатор
@ -11,7 +13,30 @@ public class DataSchemeDto
public Guid DiscriminatorId { get; set; } public Guid DiscriminatorId { get; set; }
/// <summary> /// <summary>
/// Наименования полей /// Поля
/// </summary> /// </summary>
public string[] PropNames { get; set; } = []; private IEnumerable<SchemePropertyDto> Properties { get; } = [];
/// <inheritdoc/>
public DataSchemeDto(Guid discriminatorId, IEnumerable<SchemePropertyDto> Properties)
{
DiscriminatorId = discriminatorId;
this.Properties = Properties;
}
/// <inheritdoc/>
public IEnumerator<SchemePropertyDto> GetEnumerator()
=> Properties.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <inheritdoc/>
public bool Equals(IEnumerable<SchemePropertyDto>? otherProperties)
{
if (otherProperties is null)
return false;
return Properties.SequenceEqual(otherProperties);
}
} }

View File

@ -0,0 +1,32 @@
namespace DD.Persistence.Models;
/// <summary>
/// Модель, необходимая для отображения истории по журналу изменений
/// </summary>
public class HistoryChangeLogDto
{
/// <summary>
/// Дата и время изменений
/// </summary>
public DateTimeOffset DateTime { get; set; }
/// <summary>
/// Пользователь, совершивший изменение данных
/// </summary>
public required UserDto User { get; set; }
/// <summary>
/// Проект, с которым связаны изменения
/// </summary>
public Guid DiscriminatorId { get; set; }
/// <summary>
/// Список изменений
/// </summary>
public required IEnumerable<ChangeLogDto> ChangeLogItems { get; set; }
/// <summary>
/// Комментарий к изменению
/// </summary>
public required string Comment { get; set; }
}

View File

@ -0,0 +1,17 @@
namespace DD.Persistence.Models.Requests;
/// <summary>
/// Запрос, используемый для получения данных по журналу операций
/// </summary>
public class ChangeLogRequest
{
/// <summary>
/// Дискриминатор задачи
/// </summary>
public Guid DiscriminatorId { get; set; }
/// <summary>
/// Пользователь
/// </summary>
public Guid UserId { get; set; }
}

View File

@ -0,0 +1,41 @@
namespace DD.Persistence.Models.Requests;
/// <summary>
/// Модель для создания коммита
/// </summary>
public class CreateChangeLogCommitRequest
{
/// <summary>
/// Дата создания
/// </summary>
public DateTimeOffset Creation { get; set; }
/// <summary>
/// Пользователь, совершающий коммит
/// </summary>
public Guid IdAuthor { get; set; }
/// <summary>
/// Комментарий
/// </summary>
public string? Comment { get; set; }
/// <summary>
///
/// </summary>
public CreateChangeLogCommitRequest()
{
}
/// <summary>
///
/// </summary>
public CreateChangeLogCommitRequest(Guid idAuthor, string? comment)
{
IdAuthor = idAuthor;
Comment = comment ?? string.Empty;
Creation = DateTimeOffset.UtcNow;
}
}

View File

@ -0,0 +1,30 @@
using System.Text.Json;
namespace DD.Persistence.Models;
/// <summary>
/// Индексируемого поле из схемы для набора данных
/// </summary>
public class SchemePropertyDto : IEquatable<SchemePropertyDto>
{
/// <summary>
/// Индекс поля
/// </summary>
public required int Index { get; set; }
/// <summary>
/// Наименование индексируемого поля
/// </summary>
public required string PropertyName { get; set; }
/// <summary>
/// Тип индексируемого поля
/// </summary>
public required JsonValueKind PropertyKind { get; set; }
/// <inheritdoc/>
public bool Equals(SchemePropertyDto? other)
{
return Index == other?.Index && PropertyName == other?.PropertyName && PropertyKind == other?.PropertyKind;
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DD.Persistence.Models;
/// <summary>
/// Модель, необходимая для отображения статистики по журналу изменений
/// </summary>
public class StatisticsChangeLogDto
{
/// <summary>
/// Дата и время изменений
/// </summary>
public DateTimeOffset DateTime { get; set; }
/// <summary>
/// Количество изменений
/// </summary>
public int ChangesCount { get; set; }
}

View File

@ -7,6 +7,11 @@ namespace DD.Persistence.Models;
/// </summary> /// </summary>
public class TimestampedValuesDto : ITimestampAbstractDto public class TimestampedValuesDto : ITimestampAbstractDto
{ {
/// <summary>
/// Дискриминатор
/// </summary>
public Guid DiscriminatorId { get; set; }
/// <summary> /// <summary>
/// Временная отметка /// Временная отметка
/// </summary> /// </summary>

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DD.Persistence.Models;
/// <summary>
/// Класс, описывающий пользователя
/// </summary>
public class UserDto
{
/// <summary>
/// Идентификатор пользователя
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Имя пользователя для отображения
/// </summary>
public required string DisplayName { get; set; }
}

View File

@ -11,8 +11,8 @@
<PackageReference Include="coverlet.collector" Version="6.0.2" /> <PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Shouldly" Version="4.2.1" /> <PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="Testcontainers" Version="4.1.0" /> <PackageReference Include="Testcontainers" Version="4.2.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.1.0" /> <PackageReference Include="Testcontainers.PostgreSql" Version="4.2.0" />
<PackageReference Include="xunit" Version="2.9.2" /> <PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.extensibility.core" Version="2.9.2" /> <PackageReference Include="xunit.extensibility.core" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
@ -20,7 +20,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" /> <ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
<ProjectReference Include="..\DD.Persistence.Repository\DD.Persistence.Repository.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,12 +1,7 @@
using DD.Persistence.Database.Model; using DD.Persistence.Database.Model;
using DD.Persistence.Repository.Repositories; using DD.Persistence.Database.Postgres.Repositories;
using Shouldly; using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace DD.Persistence.Repository.Test; namespace DD.Persistence.Repository.Test;
public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture> public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture>
@ -29,7 +24,6 @@ public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture>
var value = GetJsonFromObject(22); var value = GetJsonFromObject(22);
await sut.Add(id, value, Guid.NewGuid(), CancellationToken.None); await sut.Add(id, value, Guid.NewGuid(), CancellationToken.None);
var t = fixture.dbContainer.GetConnectionString();
//act //act
var result = await sut.GetCurrent([id], CancellationToken.None); var result = await sut.GetCurrent([id], CancellationToken.None);

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="UuidExtensions" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
</ItemGroup>
</Project>

View File

@ -1,34 +0,0 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories;
public class DataSchemeRepository : IDataSchemeRepository
{
protected DbContext db;
public DataSchemeRepository(DbContext db)
{
this.db = db;
}
protected virtual IQueryable<DataScheme> GetQueryReadOnly() => db.Set<DataScheme>();
public virtual async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token)
{
var entity = dataSourceSystemDto.Adapt<DataScheme>();
await db.Set<DataScheme>().AddAsync(entity, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<DataSchemeDto?> Get(Guid dataSchemeId, CancellationToken token)
{
var query = GetQueryReadOnly()
.Where(e => e.DiscriminatorId == dataSchemeId);
var entity = await query.ToArrayAsync();
var dto = entity.Select(e => e.Adapt<DataSchemeDto>()).FirstOrDefault();
return dto;
}
}

View File

@ -1,103 +0,0 @@
//using DD.Persistence.Models;
//using DD.Persistence.Models.Common;
//using DD.Persistence.Repositories;
//using Microsoft.EntityFrameworkCore;
//namespace DD.Persistence.Repository.Repositories;
//public class TimestampedValuesCachedRepository : TimestampedValuesRepository
//{
// public static TimestampedValuesDto? FirstByDate { get; private set; }
// public static CyclicArray<TimestampedValuesDto> LastData { get; } = new CyclicArray<TimestampedValuesDto>(CacheItemsCount);
// private const int CacheItemsCount = 3600;
// public TimestampedValuesCachedRepository(DbContext db, IDataSourceSystemRepository<ValuesIdentityDto> relatedDataRepository) : base(db, relatedDataRepository)
// {
// //Task.Run(async () =>
// //{
// // var firstDateItem = await base.GetFirst(CancellationToken.None);
// // if (firstDateItem == null)
// // {
// // return;
// // }
// // FirstByDate = firstDateItem;
// // var dtos = await base.GetLast(CacheItemsCount, CancellationToken.None);
// // dtos = dtos.OrderBy(d => d.Timestamp);
// // LastData.AddRange(dtos);
// //}).Wait();
// }
// public override async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset dateBegin, CancellationToken token)
// {
// if (LastData.Count == 0 || LastData[0].Timestamp > dateBegin)
// {
// var dtos = await base.GetGtDate(discriminatorId, dateBegin, token);
// return dtos;
// }
// var items = LastData
// .Where(i => i.Timestamp >= dateBegin);
// return items;
// }
// public override async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
// {
// var result = await base.AddRange(discriminatorId, dtos, token);
// if (result > 0)
// {
// dtos = dtos.OrderBy(x => x.Timestamp);
// FirstByDate = dtos.First();
// LastData.AddRange(dtos);
// }
// return result;
// }
// public override async Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
// {
// if (FirstByDate == null)
// return null;
// return await Task.Run(() =>
// {
// return new DatesRangeDto
// {
// From = FirstByDate.Timestamp,
// To = LastData[^1].Timestamp
// };
// });
// }
// public override async Task<IEnumerable<TimestampedValuesDto>> GetResampledData(
// Guid discriminatorId,
// DateTimeOffset dateBegin,
// double intervalSec = 600d,
// int approxPointsCount = 1024,
// CancellationToken token = default)
// {
// var dtos = LastData.Where(i => i.Timestamp >= dateBegin);
// if (LastData.Count == 0 || LastData[0].Timestamp > dateBegin)
// {
// dtos = await base.GetGtDate(discriminatorId, dateBegin, token);
// }
// var dateEnd = dateBegin.AddSeconds(intervalSec);
// dtos = dtos
// .Where(i => i.Timestamp <= dateEnd);
// var ratio = dtos.Count() / approxPointsCount;
// if (ratio > 1)
// dtos = dtos
// .Where((_, index) => index % ratio == 0);
// return dtos;
// }
//}

View File

@ -0,0 +1,274 @@
using DD.Persistence.API.Services;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories;
using Microsoft.Extensions.Caching.Memory;
using NSubstitute;
using UuidExtensions;
namespace DD.Persistence.Test;
public class ChangeLogTest
{
private readonly IChangeLogCommitRepository changeLogCommitRepository = Substitute.For<IChangeLogCommitRepository>();
private readonly IChangeLogRepository changeLogRepository = Substitute.For<IChangeLogRepository>();
private ChangeLogService service;
public ChangeLogTest()
{
var memoryCache = new MemoryCache(new MemoryCacheOptions());
service = new ChangeLogService(memoryCache, changeLogCommitRepository, changeLogRepository);
}
[Fact]
public async Task AddRange()
{
//arrange
var discriminatorId = Uuid7.Guid();
var commitRequest = new CreateChangeLogCommitRequest(Uuid7.Guid(), "Добавление нескольких значений");
var commit = new ChangeLogCommitDto(commitRequest.IdAuthor, commitRequest.Comment);
commit.Id = Uuid7.Guid();
var dtos = GenerateChangeLogValuesDto(2);
changeLogCommitRepository.Add(Arg.Any<CreateChangeLogCommitRequest>(), Arg.Any<CancellationToken>()).Returns(commit);
changeLogRepository
.AddRange(
Arg.Any<Guid>(),
Arg.Any<ChangeLogCommitDto>(),
Arg.Any<IEnumerable<ChangeLogValuesDto>>(),
Arg.Any<CancellationToken>())
.Returns(2);
//act
var addRangeResult = await service
.AddRange(discriminatorId, commitRequest, dtos, CancellationToken.None);
addRangeResult = await service
.AddRange(discriminatorId, commitRequest, dtos, CancellationToken.None);
//assert
await changeLogCommitRepository.Received(1).Add(commitRequest, CancellationToken.None);
await changeLogRepository.Received(2).AddRange(discriminatorId, Arg.Any<ChangeLogCommitDto>(), dtos, CancellationToken.None);
}
[Fact]
public async Task UpdateRange()
{
//arrange
var discriminatorId = Uuid7.Guid();
var commitRequest = new CreateChangeLogCommitRequest(Uuid7.Guid(), "Изменение нескольких значений");
var commit = new ChangeLogCommitDto(commitRequest.IdAuthor, commitRequest.Comment);
commit.Id = Uuid7.Guid();
var dtos = GenerateChangeLogValuesDto(2);
changeLogCommitRepository.Add(Arg.Any<CreateChangeLogCommitRequest>(), Arg.Any<CancellationToken>()).Returns(commit);
changeLogRepository
.UpdateRange(
Arg.Any<ChangeLogCommitDto>(),
Arg.Any<IEnumerable<ChangeLogValuesDto>>(),
Arg.Any<CancellationToken>())
.Returns(2);
//act
var updateRangeResult = await service
.UpdateRange(commitRequest, dtos, CancellationToken.None);
updateRangeResult = await service
.UpdateRange(commitRequest, dtos, CancellationToken.None);
updateRangeResult = await service
.UpdateRange(commitRequest, dtos, CancellationToken.None);
//assert
await changeLogCommitRepository.Received(1).Add(commitRequest, CancellationToken.None);
await changeLogRepository.Received(3).UpdateRange(Arg.Any<ChangeLogCommitDto>(), dtos, CancellationToken.None);
}
[Fact]
public async Task MarkAsDeleted()
{
//arrange
var discriminatorId = Uuid7.Guid();
var commitRequest = new CreateChangeLogCommitRequest(Uuid7.Guid(), "Удаление нескольких значений");
var commit = new ChangeLogCommitDto(commitRequest.IdAuthor, commitRequest.Comment);
commit.Id = Uuid7.Guid();
var dtos = GenerateChangeLogValuesDto(2);
var dtoIds = dtos.Select(d => d.Id);
changeLogCommitRepository.Add(Arg.Any<CreateChangeLogCommitRequest>(), Arg.Any<CancellationToken>()).Returns(commit);
changeLogRepository
.MarkAsDeleted(
Arg.Any<IEnumerable<Guid>>(),
Arg.Any<ChangeLogCommitDto>(),
Arg.Any<CancellationToken>())
.Returns(2);
//act
var markAsDeletedResult = await service
.MarkAsDeleted(dtoIds, commitRequest, CancellationToken.None);
markAsDeletedResult = await service
.MarkAsDeleted(dtoIds, commitRequest, CancellationToken.None);
//assert
await changeLogCommitRepository.Received(1).Add(commitRequest, CancellationToken.None);
await changeLogRepository.Received(2).MarkAsDeleted(dtoIds, commit, CancellationToken.None);
}
[Fact]
public async Task ClearAndAddRange()
{
//arrange
var discriminatorId = Uuid7.Guid();
var commitRequest = new CreateChangeLogCommitRequest(Uuid7.Guid(), "Удаление и добавление нескольких значений");
var commit = new ChangeLogCommitDto(commitRequest.IdAuthor, commitRequest.Comment);
commit.Id = Uuid7.Guid();
var dtos = GenerateChangeLogValuesDto(2);
var dtoIds = dtos.Select(d => d.Id);
changeLogCommitRepository.Add(Arg.Any<CreateChangeLogCommitRequest>(), Arg.Any<CancellationToken>()).Returns(commit);
changeLogRepository
.ClearAndAddRange(
Arg.Any<Guid>(),
Arg.Any<ChangeLogCommitDto>(),
Arg.Any<IEnumerable<ChangeLogValuesDto>>(),
Arg.Any<CancellationToken>())
.Returns(2);
//act
var clearAndAddResult = await service
.ClearAndAddRange(discriminatorId, commitRequest, dtos, CancellationToken.None);
clearAndAddResult = await service
.ClearAndAddRange(discriminatorId, commitRequest, dtos, CancellationToken.None);
//assert
await changeLogCommitRepository.Received(1).Add(commitRequest, CancellationToken.None);
await changeLogRepository.Received(2).ClearAndAddRange(discriminatorId, Arg.Any<ChangeLogCommitDto>(), dtos, CancellationToken.None);
}
[Fact]
public async Task GetByDate()
{
//arrange
var discriminatorId = Uuid7.Guid();
var paginationRequest = new PaginationRequest()
{
Skip = 0,
Take = 1000
};
var dtos = GenerateChangeLogValuesDto(5);
var items = new PaginationContainer<ChangeLogValuesDto>()
{
Take = paginationRequest.Take,
Skip = paginationRequest.Skip,
Items = dtos,
Count = 10
};
var momentDate = DateTime.UtcNow;
changeLogRepository
.GetByDate(
Arg.Any<Guid>(),
Arg.Any<DateTimeOffset>(),
Arg.Any<PaginationRequest>(),
Arg.Any<CancellationToken>())
.Returns(items);
//act
var actualItems = await service
.GetByDate(discriminatorId, momentDate, paginationRequest, CancellationToken.None);
//assert
await changeLogRepository.Received(1).GetByDate(discriminatorId, momentDate, paginationRequest, CancellationToken.None);
}
[Fact]
public async Task GetChangeLogForInterval()
{
//arrange
var discriminatorId = Uuid7.Guid();
var dtos = GenerateChangeLogDto(5);
var dateBegin = DateTimeOffset.UtcNow.AddDays(-5);
var dateEnd = DateTimeOffset.UtcNow;
changeLogRepository
.GetChangeLogForInterval(
Arg.Any<Guid>(),
Arg.Any<DateTimeOffset>(),
Arg.Any<DateTimeOffset>(),
Arg.Any<CancellationToken>())
.Returns(dtos);
//act
var actualItems = await service
.GetChangeLogForInterval(discriminatorId, dateBegin, dateEnd, CancellationToken.None);
//assert
await changeLogRepository.Received(1).GetChangeLogForInterval(discriminatorId, dateBegin, dateEnd, CancellationToken.None);
}
[Fact]
public async Task GetDatesChange()
{
//arrange
var discriminatorId = Uuid7.Guid();
var dateBegin = DateTimeOffset.UtcNow.AddDays(-5);
var dateEnd = DateTimeOffset.UtcNow;
var dateOnlyBegin = new DateOnly(dateBegin.Year, dateBegin.Month, dateBegin.Day);
var dateOnlyEnd = new DateOnly(dateEnd.Year, dateEnd.Month, dateEnd.Day);
var dtos = new List<DateOnly>() { dateOnlyBegin, dateOnlyEnd };
changeLogRepository
.GetDatesChange(
Arg.Any<Guid>(),
Arg.Any<CancellationToken>())
.Returns(dtos);
//act
var actualItems = await service
.GetDatesChange(discriminatorId, CancellationToken.None);
//assert
await changeLogRepository.Received(1).GetDatesChange(discriminatorId, CancellationToken.None);
}
private IEnumerable<ChangeLogValuesDto> GenerateChangeLogValuesDto(int count)
{
var items = new List<ChangeLogValuesDto>();
for (int i = 0; i < count; i++)
{
items.Add(new ChangeLogValuesDto()
{
Id = Uuid7.Guid(),
Value = new Dictionary<string, object>
{
{ "1", 1 },
{ "2", 2 }
}
});
}
return items;
}
private IEnumerable<ChangeLogDto> GenerateChangeLogDto(int count)
{
var items = new List<ChangeLogDto>();
for (int i = 0; i < count; i++)
{
items.Add(new ChangeLogDto()
{
Id = Uuid7.Guid(),
});
}
return items;
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
@ -16,8 +16,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" /> <ProjectReference Include="..\DD.Persistence.API\DD.Persistence.API.csproj" />
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" /> <ProjectReference Include="..\DD.Persistence.Client\DD.Persistence.Client.csproj" />
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,278 @@
using Ardalis.Specification.EntityFrameworkCore;
using DD.Persistence.Database.Entity;
using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Enumerations;
using DD.Persistence.Models;
using DD.Persistence.Database.Postgres.Helpers;
using System.Text.Json;
namespace DD.Persistence.Test;
/// ToDo: переписать под Theory
public class FilterBuilderShould
{
private readonly SpecificationEvaluator SpecificationEvaluator;
public FilterBuilderShould()
{
this.SpecificationEvaluator = new SpecificationEvaluator();
}
[Fact]
public void TestFilterBuilding()
{
//arrange
var discriminatorId = Guid.NewGuid();
var dataSchemeProperties = new SchemePropertyDto[]
{
new SchemePropertyDto()
{
Index = 0,
PropertyName = "A",
PropertyKind = JsonValueKind.String
},
new SchemePropertyDto()
{
Index = 1,
PropertyName = "B",
PropertyKind = JsonValueKind.Number
},
new SchemePropertyDto()
{
Index = 2,
PropertyName = "C",
PropertyKind = JsonValueKind.String
}
};
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
var filterDate = DateTime.Now.AddMinutes(-1);
var root = new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.And,
new TLeaf(OperationEnum.Greate, "A", filterDate),
new TLeaf(OperationEnum.Less, "B", 2.22)
),
new TLeaf(OperationEnum.Equal, "C", "IsEqualText")
);
var queryableData = new[]
{
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { filterDate.AddMinutes(-1), 200, "IsEqualText" } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
Values = new object[] { filterDate.AddMinutes(1), 2.21, "IsNotEqualText" } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
Values = new object[] { filterDate.AddMinutes(-1), 2.22, "IsNotEqualText" } // false
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
Values = new object[] { filterDate.AddMinutes(-1), 2.21, "IsNotEqualText" } // false
}
}
.AsQueryable();
//act
queryableData = queryableData.ApplyFilter(dataScheme, root);
//assert
var result = queryableData.ToList();
Assert.NotNull(result);
Assert.NotEmpty(result);
var expectedCount = 2;
var actualCount = result.Count();
Assert.Equal(expectedCount, actualCount);
}
[Fact]
public void TestFilterOperations()
{
//arrange
var discriminatorId = Guid.NewGuid();
var dataSchemeProperties = new SchemePropertyDto[]
{
new SchemePropertyDto()
{
Index = 0,
PropertyName = "A",
PropertyKind = JsonValueKind.Number
}
};
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
var root = new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.And,
new TVertex(
OperationEnum.And,
new TVertex(
OperationEnum.And,
new TVertex(
OperationEnum.And,
new TLeaf(OperationEnum.Less, "A", 2),
new TLeaf(OperationEnum.LessOrEqual, "A", 1.99)
),
new TLeaf(OperationEnum.GreateOrEqual, "A", 1.97)
),
new TLeaf(OperationEnum.Greate, "A", 1.96)
),
new TLeaf(OperationEnum.NotEqual, "A", 1.98)
),
new TLeaf(OperationEnum.Equal, "A", 1)
);
var queryableData = new[]
{
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { 1 } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
Values = new object[] { 1.96 } // false
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
Values = new object[] { 1.97 } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
Values = new object[] { 1.98 } // false
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-5),
Values = new object[] { 1.99 } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-6),
Values = new object[] { 2 } // false
}
}
.AsQueryable();
//act
queryableData = queryableData.ApplyFilter(dataScheme, root);
//assert
var result = queryableData.ToList();
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.NotNull(result);
Assert.NotEmpty(result);
var expectedCount = 3;
var actualCount = result.Count();
Assert.Equal(expectedCount, actualCount);
}
[Fact]
public void TestFilterValues()
{
//arrange
var discriminatorId = Guid.NewGuid();
var filterDate = DateTimeOffset.Now;
var dataSchemeProperties = new SchemePropertyDto[]
{
new SchemePropertyDto()
{
Index = 0,
PropertyName = "A",
PropertyKind = JsonValueKind.Number
},
new SchemePropertyDto()
{
Index = 1,
PropertyName = "B",
PropertyKind = JsonValueKind.Number
},
new SchemePropertyDto()
{
Index = 2,
PropertyName = "C",
PropertyKind = JsonValueKind.String
},
new SchemePropertyDto()
{
Index = 3,
PropertyName = "D",
PropertyKind = JsonValueKind.String
}
};
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
var root = new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1),
new TLeaf(OperationEnum.Equal, "B", 1.11)
),
new TLeaf(OperationEnum.Equal, "C", "IsEqualText")
),
new TLeaf(OperationEnum.Equal, "D", filterDate)
);
var queryableData = new[]
{
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { 1, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
Values = new object[] { 2, 1.11, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
Values = new object[] { 2, 2.22, "IsEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
Values = new object[] { 2, 2.22, "IsNotEqualText", filterDate } // true
},
new TimestampedValues {
DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { 2, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // false
}
}
.AsQueryable();
//act
queryableData = queryableData.ApplyFilter(dataScheme, root);
//assert
var result = queryableData.ToList();
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.NotNull(result);
Assert.NotEmpty(result);
var expectedCount = 4;
var actualCount = result.Count();
Assert.Equal(expectedCount, actualCount);
}
}

View File

@ -0,0 +1,99 @@
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Mapping;
using DD.Persistence.Client.Clients.Mapping.Clients;
using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using Microsoft.Extensions.Logging;
using NSubstitute;
using System.Text.Json;
namespace DD.Persistence.Test;
public record FirstTestDto(Guid DiscriminatorId, DateTimeOffset Timestamp, int Id, string? Value);
public record SecondTestDto(Guid DiscriminatorId, DateTimeOffset Timestamp, int Id, double Capacity);
public class MappingClientsTest
{
private readonly ITimestampedValuesClient timestampedValuesClient = Substitute.For<ITimestampedValuesClient>();
private readonly ILogger<TimestampedSetMapper> logger = Substitute.For<ILogger<TimestampedSetMapper>>();
private readonly TimestampedMappingClient timestampedMappingClient;
private readonly MappingConfig mappingConfigs;
public MappingClientsTest()
{
mappingConfigs = GetConfig();
var storage = new MapperStorage(mappingConfigs, logger);
timestampedMappingClient = new TimestampedMappingClient(timestampedValuesClient, storage);
}
[Fact]
public async Task GetMultiMapped()
{
// Arrange
var discriminatorIds = mappingConfigs.Keys;
var firstDiscriminatorId = discriminatorIds.First();
var secondDiscriminatorId = discriminatorIds.Last();
var getResult = new[]
{
new TimestampedValuesDto()
{
DiscriminatorId = firstDiscriminatorId,
Timestamp = DateTime.UtcNow,
Values = new Dictionary<string, object>
{
{ nameof(FirstTestDto.Id), JsonDocument.Parse(JsonSerializer.Serialize(1)).RootElement },
{ nameof(FirstTestDto.Value), JsonDocument.Parse(JsonSerializer.Serialize("string1")).RootElement}
}
},
new TimestampedValuesDto()
{
DiscriminatorId = secondDiscriminatorId,
Timestamp = DateTime.UtcNow,
Values = new Dictionary<string, object>
{
{ nameof(SecondTestDto.Id), JsonDocument.Parse(JsonSerializer.Serialize(1)).RootElement },
{ nameof(SecondTestDto.Capacity), JsonDocument.Parse(JsonSerializer.Serialize(0.1)).RootElement}
}
}
};
timestampedValuesClient
.Get(discriminatorIds, null, null, null, 0, 1, CancellationToken.None)
.ReturnsForAnyArgs(getResult);
// Act
var result = await timestampedMappingClient.GetMultiMapped(discriminatorIds, null, null, null, 0, 1, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.Equal(getResult.Count(), result.Count());
var firstActualDto = (FirstTestDto) result[firstDiscriminatorId].First();
Assert.NotNull(firstActualDto);
var actualId = firstActualDto.Id.ToString();
var expectedId = getResult[0].Values[nameof(FirstTestDto.Id)].ToString();
Assert.Equal(expectedId, actualId);
var secondActualDto = (SecondTestDto) result[secondDiscriminatorId].First();
Assert.NotNull(secondActualDto);
actualId = secondActualDto.Id.ToString();
expectedId = getResult[1].Values[nameof(SecondTestDto.Id)].ToString();
Assert.Equal(expectedId, actualId);
}
private MappingConfig GetConfig()
{
var config = new MappingConfig();
config[Guid.NewGuid()] = typeof(FirstTestDto);
config[Guid.NewGuid()] = typeof(SecondTestDto);
return config;
}
}

View File

@ -1,14 +1,15 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Services; using DD.Persistence.Services;
using NSubstitute; using NSubstitute;
using System.Text.Json;
namespace DD.Persistence.Repository.Test; namespace DD.Persistence.Test;
public class TimestampedValuesServiceShould public class TimestampedValuesServiceShould
{ {
private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>(); private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>();
private readonly IDataSchemeRepository dataSchemeRepository = Substitute.For<IDataSchemeRepository>(); private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For<ISchemePropertyRepository>();
private TimestampedValuesService timestampedValuesService; private readonly TimestampedValuesService timestampedValuesService;
public TimestampedValuesServiceShould() public TimestampedValuesServiceShould()
{ {
@ -33,22 +34,21 @@ public class TimestampedValuesServiceShould
.AddHours(-1) .AddHours(-1)
.ToUniversalTime(); .ToUniversalTime();
var getResult = await timestampedValuesService var getResult = await timestampedValuesService
.Get(discriminatorIds, geTimestamp, columnNames, 0, count, CancellationToken.None); .Get(discriminatorIds, geTimestamp, null, columnNames, 0, count, CancellationToken.None);
Assert.NotNull(getResult); Assert.NotNull(getResult);
Assert.Empty(getResult); Assert.Empty(getResult);
} }
private static IEnumerable<TimestampedValuesDto> Generate(int countToCreate, DateTimeOffset from) private static IEnumerable<TimestampedValuesDto> Generate(int countToCreate, DateTimeOffset from)
{ {
var result = new List<TimestampedValuesDto>();
for (int i = 0; i < countToCreate; i++) for (int i = 0; i < countToCreate; i++)
{ {
var values = new Dictionary<string, object>() var values = new Dictionary<string, object>()
{ {
{ "A", i }, { "A", GetJsonFromObject(i) },
{ "B", i * 1.1 }, { "B", GetJsonFromObject(i * 1.1) },
{ "C", $"Any{i}" }, { "C", GetJsonFromObject($"Any{i}") },
{ "D", DateTimeOffset.Now }, { "D", GetJsonFromObject(DateTimeOffset.Now) }
}; };
yield return new TimestampedValuesDto() yield return new TimestampedValuesDto()
@ -58,4 +58,11 @@ public class TimestampedValuesServiceShould
}; };
} }
} }
private static JsonElement GetJsonFromObject(object value)
{
var jsonString = JsonSerializer.Serialize(value);
var doc = JsonDocument.Parse(jsonString);
return doc.RootElement;
}
} }

View File

@ -0,0 +1,107 @@
using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Enumerations;
using DD.Persistence.Filter.TreeBuilder;
using Newtonsoft.Json;
namespace DD.Persistence.Test;
public class TreeBuilderTest
{
[Fact]
public void TreeBuildingShouldBuilt()
{
//arrange
var treeString = "(\"A\"==1)||(\"B\"==2)&&(\"C\"==3)||((\"D\"==4)||(\"E\"==5))&&(\"F\"==6)";
//act
var root = treeString.BuildTree();
//assert
Assert.NotNull(root);
var expectedRoot = JsonConvert.SerializeObject(new TVertex(
OperationEnum.And,
new TVertex(
OperationEnum.And,
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1.0),
new TLeaf(OperationEnum.Equal, "B", 2.0)
),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "C", 3.0),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "D", 4.0),
new TLeaf(OperationEnum.Equal, "E", 5.0)
)
)
),
new TLeaf(OperationEnum.Equal, "F", 6.0)
));
var actualRoot = JsonConvert.SerializeObject(root);
Assert.Equal(expectedRoot, actualRoot);
}
[Fact]
public void TreeOperationsShouldBuilt()
{
//arrange
var treeString = "(\"A\"==1)||(\"B\"!=1)||(\"C\">1)||(\"D\">=1)||(\"E\"<1)||(\"F\"<=1)";
//act
var root = treeString.BuildTree();
//assert
Assert.NotNull(root);
var expectedRoot = JsonConvert.SerializeObject(new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1.0),
new TLeaf(OperationEnum.NotEqual, "B", 1.0)
),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Greate, "C", 1.0),
new TLeaf(OperationEnum.GreateOrEqual, "D", 1.0)
)
),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Less, "E", 1.0),
new TLeaf(OperationEnum.LessOrEqual, "F", 1.0)
)
));
var actualRoot = JsonConvert.SerializeObject(root);
Assert.Equal(expectedRoot, actualRoot);
}
[Fact]
public void LeafValuesShouldBuilt()
{
//arrange
var treeString = "(\"A\"==1.2345)||(\"B\"==12345)||(\"C\"==\"12345\")";
//act
var root = treeString.BuildTree();
//assert
Assert.NotNull(root);
var expectedRoot = JsonConvert.SerializeObject(new TVertex(
OperationEnum.Or,
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1.2345),
new TLeaf(OperationEnum.Equal, "B", 12345.0)
),
new TLeaf(OperationEnum.Equal, "C", "12345")
));
var actualRoot = JsonConvert.SerializeObject(root);
Assert.Equal(expectedRoot, actualRoot);
}
}

View File

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence", "DD.Persis
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.API", "DD.Persistence.API\DD.Persistence.API.csproj", "{8650A227-929E-45F0-AEF7-2C91F45FE884}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.API", "DD.Persistence.API\DD.Persistence.API.csproj", "{8650A227-929E-45F0-AEF7-2C91F45FE884}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Repository", "DD.Persistence.Repository\DD.Persistence.Repository.csproj", "{493D6D92-231B-4CB6-831B-BE13884B0DE4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Database", "DD.Persistence.Database\DD.Persistence.Database.csproj", "{F77475D1-D074-407A-9D69-2FADDDAE2056}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.Database", "DD.Persistence.Database\DD.Persistence.Database.csproj", "{F77475D1-D074-407A-9D69-2FADDDAE2056}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.IntegrationTests", "DD.Persistence.IntegrationTests\DD.Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DD.Persistence.IntegrationTests", "DD.Persistence.IntegrationTests\DD.Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}"
@ -51,10 +49,6 @@ Global
{8650A227-929E-45F0-AEF7-2C91F45FE884}.Debug|Any CPU.Build.0 = Debug|Any CPU {8650A227-929E-45F0-AEF7-2C91F45FE884}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.ActiveCfg = Release|Any CPU {8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.Build.0 = Release|Any CPU {8650A227-929E-45F0-AEF7-2C91F45FE884}.Release|Any CPU.Build.0 = Release|Any CPU
{493D6D92-231B-4CB6-831B-BE13884B0DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{493D6D92-231B-4CB6-831B-BE13884B0DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{493D6D92-231B-4CB6-831B-BE13884B0DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{493D6D92-231B-4CB6-831B-BE13884B0DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.Build.0 = Debug|Any CPU {F77475D1-D074-407A-9D69-2FADDDAE2056}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F77475D1-D074-407A-9D69-2FADDDAE2056}.Release|Any CPU.ActiveCfg = Release|Any CPU {F77475D1-D074-407A-9D69-2FADDDAE2056}.Release|Any CPU.ActiveCfg = Release|Any CPU

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>
/// Получение данных на текущую дату (с пагинацией) /// Получение данных на текущую дату (с пагинацией)
@ -47,55 +48,33 @@ public interface IChangeLogApi : ISyncWithDiscriminatorApi<ChangeLogValuesDto>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> GetChangeLogForDate(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token); Task<IActionResult> GetChangeLogForDate(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token);
/// <summary>
/// Добавить одну запись
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IActionResult> Add(Guid idDiscriminator, ChangeLogValuesDto dto, 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>
/// <param name="dto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IActionResult> Update(ChangeLogValuesDto dto, 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>
/// <param name="id"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IActionResult> Delete(Guid id, CancellationToken token);
/// <summary> /// <summary>
/// Удалить несколько записей /// Удалить несколько записей
/// </summary> /// </summary>
/// <param name="ids"></param> /// <param name="ids"></param>
/// <param name="comment">комментарий к удалению</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IActionResult> DeleteRange(IEnumerable<Guid> ids, CancellationToken token); Task<IActionResult> DeleteRange(IEnumerable<Guid> ids, string comment, CancellationToken token);
/// <summary> /// <summary>
/// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени) /// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени)

View File

@ -0,0 +1,22 @@
namespace DD.Persistence.Filter.Models.Abstractions;
/// <summary>
/// Посетитель бинарного дерева
/// </summary>
/// <typeparam name="TVisitResult"></typeparam>
public interface INodeVisitor<TVisitResult>
{
/// <summary>
/// Посетить узел
/// </summary>
/// <param name="vertex"></param>
/// <returns></returns>
TVisitResult Visit(TVertex vertex);
/// <summary>
/// Посетить лист
/// </summary>
/// <param name="leaf"></param>
/// <returns></returns>
TVisitResult Visit(TLeaf leaf);
}

Some files were not shown because too many files have changed in this diff Show More