Merge branch 'master' into Partitioning
All checks were successful
Unit tests / test (push) Successful in 2m23s

This commit is contained in:
Roman Efremov 2025-01-22 17:12:18 +05:00
commit b09d2dd704
144 changed files with 3046 additions and 2284 deletions

View File

@ -4,6 +4,7 @@ using DD.Persistence.Models;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using System.Net; using System.Net;
using DD.Persistence.Models.Common;
namespace DD.Persistence.API.Controllers; namespace DD.Persistence.API.Controllers;

View File

@ -1,20 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
namespace DD.Persistence.API.Controllers;
/// <summary>
/// Работа с временными данными
/// </summary>
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class DataSaubController : TimeSeriesController<DataSaubDto>
{
public DataSaubController(ITimeSeriesDataRepository<DataSaubDto> timeSeriesDataRepository) : base(timeSeriesDataRepository)
{
}
}

View File

@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using System.Net; using System.Net;
using System.Text.Json;
using DD.Persistence.Models.Common;
namespace DD.Persistence.API.Controllers; namespace DD.Persistence.API.Controllers;
@ -28,9 +30,9 @@ public class SetpointController : ControllerBase, ISetpointApi
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("current")] [HttpGet("current")]
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token) public async Task<ActionResult<Dictionary<Guid, object>>> GetCurrent([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
{ {
var result = await setpointRepository.GetCurrent(setpointKeys, token); var result = await setpointRepository.GetCurrentDictionary(setpointKeys, token);
return Ok(result); return Ok(result);
} }
@ -104,7 +106,7 @@ public class SetpointController : ControllerBase, ISetpointApi
public async Task<IActionResult> Add(Guid setpointKey, object newValue, CancellationToken token) public async Task<IActionResult> Add(Guid setpointKey, object newValue, CancellationToken token)
{ {
var userId = User.GetUserId<Guid>(); var userId = User.GetUserId<Guid>();
await setpointRepository.Add(setpointKey, newValue, userId, token); await setpointRepository.Add(setpointKey, (JsonElement)newValue, userId, token);
return CreatedAtAction(nameof(Add), true); return CreatedAtAction(nameof(Add), true);
} }

View File

@ -4,6 +4,7 @@ using DD.Persistence.Models;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using System.Net; using System.Net;
using DD.Persistence.Models.Common;
namespace DD.Persistence.API.Controllers; namespace DD.Persistence.API.Controllers;

View File

@ -1,76 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
namespace DD.Persistence.API.Controllers;
[ApiController]
[Authorize]
[Route("api/[controller]")]
public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDto>
where TDto : class, ITimeSeriesAbstractDto, new()
{
private readonly ITimeSeriesDataRepository<TDto> timeSeriesDataRepository;
public TimeSeriesController(ITimeSeriesDataRepository<TDto> timeSeriesDataRepository)
{
this.timeSeriesDataRepository = timeSeriesDataRepository;
}
/// <summary>
/// Получить список объектов, удовлетворяющий диапазону дат
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token)
{
var result = await timeSeriesDataRepository.GetGtDate(dateBegin, token);
return Ok(result);
}
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("datesRange")]
public async Task<IActionResult> GetDatesRange(CancellationToken token)
{
var result = await timeSeriesDataRepository.GetDatesRange(token);
return Ok(result);
}
/// <summary>
/// Получить список объектов с прореживанием, удовлетворяющий диапазону дат
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("resampled")]
public async Task<IActionResult> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default)
{
var result = await timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token);
return Ok(result);
}
/// <summary>
/// Добавить записи
/// </summary>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> AddRange(IEnumerable<TDto> dtos, CancellationToken token)
{
var result = await timeSeriesDataRepository.AddRange(dtos, token);
return Ok(result);
}
}

View File

@ -1,104 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using System.Net;
namespace DD.Persistence.API.Controllers;
/// <summary>
/// Хранение наборов данных с отметкой времени.
/// Не оптимизировано под большие данные.
/// </summary>
[ApiController]
[Authorize]
[Route("api/[controller]/{idDiscriminator}")]
public class TimestampedSetController : ControllerBase
{
private readonly ITimestampedSetRepository repository;
public TimestampedSetController(ITimestampedSetRepository repository)
{
this.repository = repository;
}
/// <summary>
/// Записать новые данные
/// Предполагается что данные с одним дискриминатором имеют одинаковую структуру
/// </summary>
/// <param name="idDiscriminator">Дискриминатор (идентификатор) набора</param>
/// <param name="sets"></param>
/// <param name="token"></param>
/// <returns>кол-во затронутых записей</returns>
[HttpPost]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> AddRange([FromRoute] Guid idDiscriminator, [FromBody] IEnumerable<TimestampedSetDto> sets, CancellationToken token)
{
var result = await repository.AddRange(idDiscriminator, sets, token);
return Ok(result);
}
/// <summary>
/// Получение данных с фильтрацией. Значение фильтра null - отключен
/// </summary>
/// <param name="idDiscriminator">Дискриминатор (идентификатор) набора</param>
/// <param name="geTimestamp">Фильтр позднее даты</param>
/// <param name="columnNames">Фильтр свойств набора. Можно запросить только некоторые свойства из набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns>Фильтрованный набор данных с сортировкой по отметке времени</returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<TimestampedSetDto>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, [FromQuery] IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var result = await repository.Get(idDiscriminator, geTimestamp, columnNames, skip, take, token);
return Ok(result);
}
/// <summary>
/// Получить последние данные
/// </summary>
/// <param name="idDiscriminator">Дискриминатор (идентификатор) набора</param>
/// <param name="columnNames">Фильтр свойств набора. Можно запросить только некоторые свойства из набора</param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns>Фильтрованный набор данных с сортировкой по отметке времени</returns>
[HttpGet("last")]
[ProducesResponseType(typeof(IEnumerable<TimestampedSetDto>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetLast(Guid idDiscriminator, [FromQuery] IEnumerable<string>? columnNames, int take, CancellationToken token)
{
var result = await repository.GetLast(idDiscriminator, columnNames, take, token);
return Ok(result);
}
/// <summary>
/// Диапазон дат за которые есть данные
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns>Дата первой и последней записи</returns>
[HttpGet("datesRange")]
[ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<IActionResult> GetDatesRange(Guid idDiscriminator, CancellationToken token)
{
var result = await repository.GetDatesRange(idDiscriminator, token);
return Ok(result);
}
/// <summary>
/// Количество записей по указанному набору в БД. Для пагинации.
/// </summary>
/// <param name="idDiscriminator">Дискриминатор (идентификатор) набора</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("count")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<IActionResult> Count(Guid idDiscriminator, CancellationToken token)
{
var result = await repository.Count(idDiscriminator, token);
return Ok(result);
}
}

View File

@ -0,0 +1,151 @@
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Net;
namespace DD.Persistence.API.Controllers;
/// <summary>
/// Хранение наборов данных с отметкой времени.
/// </summary>
[ApiController]
[Authorize]
[Route("api/[controller]/{discriminatorId}")]
public class TimestampedValuesController : ControllerBase
{
private readonly ITimestampedValuesService timestampedValuesRepository;
public TimestampedValuesController(ITimestampedValuesService repository)
{
this.timestampedValuesRepository = repository;
}
/// <summary>
/// Записать новые данные.
/// Предполагается что данные с одним дискриминатором имеют одинаковую структуру
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="dtos"></param>
/// <param name="token"></param>
[HttpPost]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
public async Task<IActionResult> AddRange([FromRoute] Guid discriminatorId, [FromBody] IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
{
var result = await timestampedValuesRepository.AddRange(discriminatorId, dtos, token);
return CreatedAtAction(nameof(AddRange), result);
}
/// <summary>
/// Получение данных с фильтрацией. Значение фильтра null - отключен
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromRoute] Guid discriminatorId, DateTimeOffset? timestampBegin, [FromQuery] string[]? columnNames, int skip, int take, CancellationToken token)
{
var result = await timestampedValuesRepository.Get(discriminatorId, timestampBegin, columnNames, skip, take, token);
return result.Any() ? Ok(result) : NoContent();
}
/// <summary>
/// Получить данные, начиная с заданной отметки времени
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="token"></param>
[HttpGet("gtdate")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetGtDate([FromRoute] Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
{
var result = await timestampedValuesRepository.GetGtDate(discriminatorId, timestampBegin, token);
return result.Any() ? Ok(result) : NoContent();
}
/// <summary>
/// Получить данные c начала
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="take"></param>
/// <param name="token"></param>
[HttpGet("first")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetFirst([FromRoute] Guid discriminatorId, int take, CancellationToken token)
{
var result = await timestampedValuesRepository.GetFirst(discriminatorId, take, token);
return result.Any() ? Ok(result) : NoContent();
}
/// <summary>
/// Получить данные c конца
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="take"></param>
/// <param name="token"></param>
[HttpGet("last")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetLast([FromRoute] Guid discriminatorId, int take, CancellationToken token)
{
var result = await timestampedValuesRepository.GetLast(discriminatorId, take, token);
return result.Any() ? Ok(result) : NoContent();
}
/// <summary>
/// Получить список объектов с прореживанием, удовлетворяющий диапазону дат
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
[HttpGet("resampled")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetResampledData([FromRoute] Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default)
{
var result = await timestampedValuesRepository.GetResampledData(discriminatorId, timestampBegin, intervalSec, approxPointsCount, token);
return result.Any() ? Ok(result) : NoContent();
}
/// <summary>
/// Получить количество записей по указанному набору в БД. Для пагинации
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="token"></param>
[HttpGet("count")]
public async Task<ActionResult<int>> Count([FromRoute] Guid discriminatorId, CancellationToken token)
{
var result = await timestampedValuesRepository.Count(discriminatorId, token);
return Ok(result);
}
/// <summary>
/// Получить диапазон дат, в пределах которых хранятся даные
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="token"></param>
[HttpGet("datesRange")]
public async Task<ActionResult<DatesRangeDto>> GetDatesRange([FromRoute] Guid discriminatorId, CancellationToken token)
{
var result = await timestampedValuesRepository.GetDatesRange(discriminatorId, token);
return Ok(result);
}
}

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Services.Interfaces; using DD.Persistence.Services.Interfaces;
using System.Net; using System.Net;
using DD.Persistence.Models.Common;
namespace DD.Persistence.API.Controllers; namespace DD.Persistence.API.Controllers;

View File

@ -16,12 +16,6 @@ namespace DD.Persistence.API;
public static class DependencyInjection public static class DependencyInjection
{ {
//public static void MapsterSetup()
//{
// TypeAdapterConfig.GlobalSettings.Default.Config
// .ForType<TechMessageDto, TechMessage>()
// .Ignore(dest => dest.System, dest => dest.SystemId);
//}
public static void AddSwagger(this IServiceCollection services, IConfiguration configuration) public static void AddSwagger(this IServiceCollection services, IConfiguration configuration)
{ {
services.AddSwaggerGen(c => services.AddSwaggerGen(c =>
@ -59,6 +53,7 @@ public static class DependencyInjection
public static void AddServices(this IServiceCollection services) public static void AddServices(this IServiceCollection services)
{ {
services.AddTransient<IWitsDataService, WitsDataService>(); services.AddTransient<IWitsDataService, WitsDataService>();
services.AddTransient<ITimestampedValuesService, TimestampedValuesService>();
} }
#region Authentication #region Authentication

View File

@ -0,0 +1,51 @@
# Persistence Service Readme
## Краткое описание
Persistence сервис отвечает за работу с хранимыми данными
в рамках совокупности различных систем.
## Локальное развертывание
1. Скачать репозиторий по SSH
```
ssh://git@git.ddrilling.ru:2221/on.nemtina/persistence.git
```
Для доступа к репозиториям редварительно необходимо сгенерировать SSH ключ и добавить его в Gitea
2. Выбрать ветку dev
## Использование Swagger-а
1. Сконфигурировать appsettings.Development.json
(при отсутствии) занести флаг:
```json
"NeedUseKeyCloak": true
```
2. Запустить решение в режиме Debug
3. Выполнить авторизацию через KeyCloak - качестве client_id указать:
```
webapi
```
После этого должен произойти редирект на страницу авторизации в KeyCloak
4. Заполнить поля и авторизоваться
```
Username or email: myuser
```
```
Password: 12345
```
## Тестирование
Запуск тестов рекомендуется осуществлять без использования KeyCloak<br> Для этого
настройка appsettings.Tests.json должна содержать:
```
"NeedUseKeyCloak": false,
"AuthUser": {
"username": "myuser",
"password": 12345,
"clientId": "webapi",
"grantType": "password"
}
```

View File

@ -26,8 +26,6 @@ public class Startup
services.AddJWTAuthentication(Configuration); services.AddJWTAuthentication(Configuration);
services.AddMemoryCache(); services.AddMemoryCache();
services.AddServices(); services.AddServices();
//DependencyInjection.MapsterSetup();
} }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

View File

@ -20,5 +20,6 @@
"clientId": "webapi", "clientId": "webapi",
"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"
} },
"ClientUrl": "http://localhost:5000/"
} }

View File

@ -3,15 +3,17 @@ using DD.Persistence.Client.Clients.Base;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients; namespace DD.Persistence.Client.Clients;
public class ChangeLogClient : BaseClient, IChangeLogClient public class ChangeLogClient : BaseClient, IChangeLogClient
{ {
private readonly Interfaces.Refit.IRefitChangeLogClient refitChangeLogClient; private readonly IRefitChangeLogClient refitChangeLogClient;
public ChangeLogClient(Interfaces.Refit.IRefitChangeLogClient refitChangeLogClient, ILogger<ChangeLogClient> logger) : base(logger) public ChangeLogClient(IRefitClientFactory<IRefitChangeLogClient> refitClientFactory, ILogger<ChangeLogClient> logger) : base(logger)
{ {
this.refitChangeLogClient = refitChangeLogClient; this.refitChangeLogClient = refitClientFactory.Create();
} }
public async Task<int> ClearAndAddRange(Guid idDiscriminator, IEnumerable<DataWithWellDepthAndSectionDto> dtos, CancellationToken token) public async Task<int> ClearAndAddRange(Guid idDiscriminator, IEnumerable<DataWithWellDepthAndSectionDto> dtos, CancellationToken token)

View File

@ -9,9 +9,9 @@ public class DataSourceSystemClient : BaseClient, IDataSourceSystemClient
{ {
private readonly IRefitDataSourceSystemClient dataSourceSystemClient; private readonly IRefitDataSourceSystemClient dataSourceSystemClient;
public DataSourceSystemClient(IRefitDataSourceSystemClient dataSourceSystemClient, ILogger<DataSourceSystemClient> logger) : base(logger) public DataSourceSystemClient(IRefitClientFactory<IRefitDataSourceSystemClient> dataSourceSystemClientFactory, ILogger<DataSourceSystemClient> logger) : base(logger)
{ {
this.dataSourceSystemClient = dataSourceSystemClient; this.dataSourceSystemClient = dataSourceSystemClientFactory.Create();
} }
public async Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token) public async Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token)

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
namespace DD.Persistence.Client.Clients.Interfaces; namespace DD.Persistence.Client.Clients.Interfaces;

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients.Interfaces; namespace DD.Persistence.Client.Clients.Interfaces;
@ -24,6 +25,15 @@ public interface ISetpointClient : IDisposable
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token); Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token);
/// <summary>
/// Получить актуальные значения уставок
/// </summary>
/// <param name="setpointConfigs"></param>
/// <param name="token"></param>
/// <returns></returns>s
Task<Dictionary<Guid, object>> GetCurrentDictionary(IEnumerable<Guid> setpointConfigs, CancellationToken token);
/// <summary> /// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории /// Получить диапазон дат, для которых есть данные в репозитории
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
namespace DD.Persistence.Client.Clients.Interfaces; namespace DD.Persistence.Client.Clients.Interfaces;

View File

@ -1,44 +0,0 @@
using DD.Persistence.Models;
namespace DD.Persistence.Client.Clients.Interfaces;
/// <summary>
/// Клиент для работы с временными данными
/// </summary>
/// <typeparam name="TDto"></typeparam>
public interface ITimeSeriesClient<TDto> : IDisposable where TDto : class, new()
{
/// <summary>
/// Добавление записей
/// </summary>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> AddRange(IEnumerable<TDto> dtos, CancellationToken token);
/// <summary>
/// Получить список объектов, удовлетворяющий диапазону дат
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="dateEnd"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TDto>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token);
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto?> GetDatesRange(CancellationToken token);
/// <summary>
/// Получить список объектов с прореживанием, удовлетворяющий диапазону дат
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TDto>> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default);
}

View File

@ -1,59 +0,0 @@
using DD.Persistence.Models;
namespace DD.Persistence.Client.Clients.Interfaces;
/// <summary>
/// Клиент для работы с репозиторием для хранения разных наборов данных рядов.
/// idDiscriminator - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest.
/// idDiscriminator формируют клиенты и только им известно что они обозначают.
/// Так как данные приходят редко, то их прореживания для построения графиков не предусмотрено.
/// </summary>
public interface ITimestampedSetClient : IDisposable
{
/// <summary>
/// Записать новые данные
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="sets"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token);
/// <summary>
/// Количество записей по указанному набору в БД. Для пагинации
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Count(Guid idDiscriminator, CancellationToken token);
/// <summary>
/// Получение данных с фильтрацией. Значение фильтра null - отключен
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="geTimestamp"></param>
/// <param name="columnNames"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedSetDto>> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Диапазон дат за которые есть данные
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token);
/// <summary>
///
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="columnNames"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedSetDto>> GetLast(Guid idDiscriminator, IEnumerable<string>? columnNames, int take, CancellationToken token);
}

View File

@ -0,0 +1,103 @@
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients.Interfaces;
/// <summary>
/// Клиент для работы с наборами данных, имеющими отметку времени.
/// discriminatorId - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest.
/// discriminatorId формируют клиенты и только им известно что они обозначают.
/// </summary>
public interface ITimestampedValuesClient : IDisposable
{
/// <summary>
/// Записать новые данные
/// Предполагается что данные с одним дискриминатором имеют одинаковую структуру
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="dtos"></param>
/// <param name="token"></param>
Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token);
/// <summary>
/// Получить данные с фильтрацией. Значение фильтра null - отключен
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> Get(Guid discriminatorId, DateTimeOffset? timestampBegin, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получить данные, начиная с заданной отметки времени
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token);
/// <summary>
/// Получить данные с начала
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int take, CancellationToken token);
/// <summary>
/// Получить данные с конца
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int take, CancellationToken token);
/// <summary>
/// Получить данные с прореживанием, удовлетворяющем диапазону дат
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="timestampBegin"></param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> GetResampledData(Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default);
/// <summary>
/// Количество записей по указанному набору в БД. Для пагинации
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="token"></param>
Task<int> Count(Guid discriminatorId, CancellationToken token);
/// <summary>
/// Диапазон дат, в пределах которых осуществляется хранение данных
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="token"></param>
Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token);
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="idDiscriminator"></param>
/// <param name="geTimestamp"></param>
/// <param name="columnNames"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<T>> Get<T>(Guid idDiscriminator, DateTimeOffset? geTimestamp, 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>> GetLast<T>(Guid idDiscriminator, int take, CancellationToken token);
}

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using Refit; using Refit;
namespace DD.Persistence.Client.Clients.Interfaces; namespace DD.Persistence.Client.Clients.Interfaces;

View File

@ -1,10 +1,11 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using Refit; using Refit;
namespace DD.Persistence.Client.Clients.Interfaces.Refit; namespace DD.Persistence.Client.Clients.Interfaces.Refit;
public interface IRefitChangeLogClient : IDisposable public interface IRefitChangeLogClient : IRefitClient, IDisposable
{ {
private const string BaseRoute = "/api/ChangeLog"; private const string BaseRoute = "/api/ChangeLog";

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DD.Persistence.Client.Clients.Interfaces.Refit;
/// <summary>
/// Базовый Refit интерфейс
/// </summary>
public interface IRefitClient
{
}

View File

@ -2,7 +2,7 @@
using Refit; using Refit;
namespace DD.Persistence.Client.Clients.Interfaces.Refit; namespace DD.Persistence.Client.Clients.Interfaces.Refit;
public interface IRefitDataSourceSystemClient : IDisposable public interface IRefitDataSourceSystemClient : IRefitClient, IDisposable
{ {
private const string BaseRoute = "/api/dataSourceSystem"; private const string BaseRoute = "/api/dataSourceSystem";

View File

@ -1,14 +1,19 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using Refit; using Refit;
using System.Text.Json;
namespace DD.Persistence.Client.Clients.Interfaces.Refit; namespace DD.Persistence.Client.Clients.Interfaces.Refit;
public interface IRefitSetpointClient : IDisposable public interface IRefitSetpointClient : IRefitClient, IDisposable
{ {
private const string BaseRoute = "/api/setpoint"; private const string BaseRoute = "/api/setpoint";
//[Get($"{BaseRoute}/current")]
//Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, CancellationToken token);
[Get($"{BaseRoute}/current")] [Get($"{BaseRoute}/current")]
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, CancellationToken token); Task<IApiResponse<Dictionary<Guid, JsonElement>>> GetCurrent([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, CancellationToken token);
[Get($"{BaseRoute}/history")] [Get($"{BaseRoute}/history")]
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetHistory([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, [Query] DateTimeOffset historyMoment, CancellationToken token); Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetHistory([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, [Query] DateTimeOffset historyMoment, CancellationToken token);

View File

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Mvc; using DD.Persistence.Models;
using DD.Persistence.Models;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using Refit; using Refit;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients.Interfaces.Refit namespace DD.Persistence.Client.Clients.Interfaces.Refit
{ {
public interface IRefitTechMessagesClient : IDisposable public interface IRefitTechMessagesClient : IRefitClient, IDisposable
{ {
private const string BaseRoute = "/api/techMessages"; private const string BaseRoute = "/api/techMessages";

View File

@ -1,21 +0,0 @@
using DD.Persistence.Models;
using Refit;
namespace DD.Persistence.Client.Clients.Interfaces.Refit;
public interface IRefitTimeSeriesClient<TDto> : IDisposable
where TDto : class, new()
{
private const string BaseRoute = "/api/dataSaub";
[Post($"{BaseRoute}")]
Task<IApiResponse<int>> AddRange(IEnumerable<TDto> dtos, CancellationToken token);
[Get($"{BaseRoute}")]
Task<IApiResponse<IEnumerable<TDto>>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token);
[Get($"{BaseRoute}/resampled")]
Task<IApiResponse<IEnumerable<TDto>>> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default);
[Get($"{BaseRoute}/datesRange")]
Task<IApiResponse<DatesRangeDto?>> GetDatesRange(CancellationToken token);
}

View File

@ -1,24 +0,0 @@
using DD.Persistence.Models;
using Refit;
namespace DD.Persistence.Client.Clients.Interfaces.Refit;
public interface IRefitTimestampedSetClient : IDisposable
{
private const string baseUrl = "/api/TimestampedSet/{idDiscriminator}";
[Post(baseUrl)]
Task<IApiResponse<int>> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token);
[Get(baseUrl)]
Task<IApiResponse<IEnumerable<TimestampedSetDto>>> Get(Guid idDiscriminator, [Query] DateTimeOffset? geTimestamp, [Query] IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
[Get($"{baseUrl}/last")]
Task<IApiResponse<IEnumerable<TimestampedSetDto>>> GetLast(Guid idDiscriminator, [Query] IEnumerable<string>? columnNames, int take, CancellationToken token);
[Get($"{baseUrl}/count")]
Task<IApiResponse<int>> Count(Guid idDiscriminator, CancellationToken token);
[Get($"{baseUrl}/datesRange")]
Task<IApiResponse<DatesRangeDto?>> GetDatesRange(Guid idDiscriminator, CancellationToken token);
}

View File

@ -0,0 +1,61 @@
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using Refit;
namespace DD.Persistence.Client.Clients.Interfaces.Refit;
/// <summary>
/// Refit интерфейс для TimestampedValuesController
/// </summary>
public interface IRefitTimestampedValuesClient : IRefitClient, IDisposable
{
private const string baseUrl = "/api/TimestampedValues/{discriminatorId}";
/// <summary>
/// Записать новые данные
/// </summary>
[Post(baseUrl)]
Task<IApiResponse<int>> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token);
/// <summary>
/// Получение данных с фильтрацией
/// </summary>
[Get(baseUrl)]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> Get(Guid discriminatorId, DateTimeOffset? timestampBegin, [Query(CollectionFormat.Multi)] IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получить данные, начиная с заданной отметки времени
/// </summary>
[Get($"{baseUrl}/gtdate")]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token);
/// <summary>
/// Получить данные c начала
/// </summary>
[Get($"{baseUrl}/first")]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> GetFirst(Guid discriminatorId, int take, CancellationToken token);
/// <summary>
/// Получить данные c конца
/// </summary>
[Get($"{baseUrl}/last")]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> GetLast(Guid discriminatorId, int take, CancellationToken token);
/// <summary>
/// Получить список объектов с прореживанием, удовлетворяющий диапазону временных отметок
/// </summary>
[Get($"{baseUrl}/resampled")]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> GetResampledData(Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default);
/// <summary>
/// Получить количество записей по указанному набору в БД. Для пагинации
/// </summary>
[Get($"{baseUrl}/count")]
Task<IApiResponse<int>> Count(Guid discriminatorId, CancellationToken token);
/// <summary>
/// Получить диапазон дат, в пределах которых хранятся даные
/// </summary>
[Get($"{baseUrl}/datesRange")]
Task<IApiResponse<DatesRangeDto?>> GetDatesRange(Guid discriminatorId, CancellationToken token);
}

View File

@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Mvc; using DD.Persistence.Models;
using DD.Persistence.Models;
using Refit; using Refit;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients.Interfaces.Refit; namespace DD.Persistence.Client.Clients.Interfaces.Refit;
public interface IRefitWitsDataClient : IDisposable public interface IRefitWitsDataClient : IRefitClient, IDisposable
{ {
private const string BaseRoute = "/api/witsData"; private const string BaseRoute = "/api/witsData";

View File

@ -1,18 +1,27 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
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;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Globalization;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients; 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(IRefitSetpointClient refitSetpointClient, ILogger<SetpointClient> logger) : base(logger) public SetpointClient(
IRefitClientFactory<IRefitSetpointClient> refitSetpointClientFactory,
ISetpointConfigStorage setpointConfigStorage,
ILogger<SetpointClient> logger) : base(logger)
{ {
this.refitSetpointClient = refitSetpointClient; 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)
@ -20,7 +29,21 @@ public class SetpointClient : BaseClient, ISetpointClient
var result = await ExecuteGetResponse( var result = await ExecuteGetResponse(
async () => await refitSetpointClient.GetCurrent(setpointKeys, token), token); async () => await refitSetpointClient.GetCurrent(setpointKeys, token), token);
return result!; return result!.Select(x => new SetpointValueDto {
Key = x.Key,
Value = DeserializeValue(x.Key, x.Value)
});
}
public async Task<Dictionary<Guid, object>> GetCurrentDictionary(IEnumerable<Guid> setpointConfigs, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitSetpointClient.GetCurrent(setpointConfigs, token), token);
return result!.ToDictionary(x => x.Key,x => DeserializeValue(x.Key,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)
@ -28,6 +51,10 @@ 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!;
} }
@ -36,6 +63,9 @@ 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!;
} }
@ -52,9 +82,13 @@ 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!;
} }
public async Task Add(Guid setpointKey, object newValue, CancellationToken token) public async Task Add(Guid setpointKey, object newValue, CancellationToken token)
{ {
await ExecutePostResponse( await ExecutePostResponse(
@ -67,4 +101,21 @@ 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

@ -4,6 +4,7 @@ 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 DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients; namespace DD.Persistence.Client.Clients;
@ -11,9 +12,9 @@ public class TechMessagesClient : BaseClient, ITechMessagesClient
{ {
private readonly IRefitTechMessagesClient refitTechMessagesClient; private readonly IRefitTechMessagesClient refitTechMessagesClient;
public TechMessagesClient(IRefitTechMessagesClient refitTechMessagesClient, ILogger<TechMessagesClient> logger) : base(logger) public TechMessagesClient(IRefitClientFactory<IRefitTechMessagesClient> refitTechMessagesClientFactory, ILogger<TechMessagesClient> logger) : base(logger)
{ {
this.refitTechMessagesClient = refitTechMessagesClient; this.refitTechMessagesClient = refitTechMessagesClientFactory.Create();
} }
public async Task<PaginationContainer<TechMessageDto>> GetPage(PaginationRequest request, CancellationToken token) public async Task<PaginationContainer<TechMessageDto>> GetPage(PaginationRequest request, CancellationToken token)

View File

@ -1,55 +0,0 @@
using Microsoft.Extensions.Logging;
using DD.Persistence.Client.Clients.Base;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Models;
namespace DD.Persistence.Client.Clients;
public class TimeSeriesClient<TDto> : BaseClient, ITimeSeriesClient<TDto> where TDto : class, new()
{
private readonly IRefitTimeSeriesClient<TDto> timeSeriesClient;
public TimeSeriesClient(IRefitTimeSeriesClient<TDto> refitTechMessagesClient, ILogger<TimeSeriesClient<TDto>> logger) : base(logger)
{
this.timeSeriesClient = refitTechMessagesClient;
}
public async Task<int> AddRange(IEnumerable<TDto> dtos, CancellationToken token)
{
var result = await ExecutePostResponse(
async () => await timeSeriesClient.AddRange(dtos, token), token);
return result;
}
public async Task<IEnumerable<TDto>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await timeSeriesClient.Get(dateBegin, dateEnd, token), token);
return result!;
}
public async Task<IEnumerable<TDto>> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default)
{
var result = await ExecuteGetResponse(
async () => await timeSeriesClient.GetResampledData(dateBegin, intervalSec, approxPointsCount, token), token);
return result!;
}
public async Task<DatesRangeDto?> GetDatesRange(CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await timeSeriesClient.GetDatesRange(token), token);
return result;
}
public void Dispose()
{
timeSeriesClient.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@ -1,63 +0,0 @@
using Microsoft.Extensions.Logging;
using DD.Persistence.Client.Clients.Base;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Models;
namespace DD.Persistence.Client.Clients;
public class TimestampedSetClient : BaseClient, ITimestampedSetClient
{
private readonly IRefitTimestampedSetClient refitTimestampedSetClient;
public TimestampedSetClient(IRefitTimestampedSetClient refitTimestampedSetClient, ILogger<TimestampedSetClient> logger) : base(logger)
{
this.refitTimestampedSetClient = refitTimestampedSetClient;
}
public async Task<int> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token)
{
var result = await ExecutePostResponse(
async () => await refitTimestampedSetClient.AddRange(idDiscriminator, sets, token), token);
return result;
}
public async Task<IEnumerable<TimestampedSetDto>> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.Get(idDiscriminator, geTimestamp, columnNames, skip, take, token), token);
return result!;
}
public async Task<IEnumerable<TimestampedSetDto>> GetLast(Guid idDiscriminator, IEnumerable<string>? columnNames, int take, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetLast(idDiscriminator, columnNames, take, token), token);
return result!;
}
public async Task<int> Count(Guid idDiscriminator, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.Count(idDiscriminator, token), token);
return result;
}
public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetDatesRange(idDiscriminator, token), token);
return result;
}
public void Dispose()
{
refitTimestampedSetClient.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,127 @@
using DD.Persistence.Client.Clients.Base;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace DD.Persistence.Client.Clients;
/// <inheritdoc/>
public class TimestampedValuesClient : BaseClient, ITimestampedValuesClient
{
private readonly IRefitTimestampedValuesClient refitTimestampedSetClient;
/// <inheritdoc/>
public TimestampedValuesClient(IRefitClientFactory<IRefitTimestampedValuesClient> refitTimestampedSetClientFactory, ILogger<TimestampedValuesClient> logger) : base(logger)
{
this.refitTimestampedSetClient = refitTimestampedSetClientFactory.Create();
}
/// <inheritdoc/>
private readonly ConcurrentDictionary<Guid, TimestampedSetMapperBase> mapperCache = new();
/// <inheritdoc/>
public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> sets, CancellationToken token)
{
var result = await ExecutePostResponse(
async () => await refitTimestampedSetClient.AddRange(discriminatorId, sets, token), token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> Get(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.Get(discriminatorId, geTimestamp, columnNames, skip, take, token), token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetGtDate(discriminatorId, timestampBegin, token), token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int take, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetFirst(discriminatorId, take, token), token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int take, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetLast(discriminatorId, take, token), token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetResampledData(Guid discriminatorId, DateTimeOffset dateBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetResampledData(discriminatorId, dateBegin, intervalSec, approxPointsCount, token), token);
return result;
}
/// <inheritdoc/>
public async Task<int> Count(Guid discriminatorId, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.Count(discriminatorId, token), token);
return result;
}
/// <inheritdoc/>
public async Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.GetDatesRange(discriminatorId, token), token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<T>> Get<T>(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var data = await Get(idDiscriminator, geTimestamp, columnNames, skip, take, token);
var mapper = GetMapper<T>(idDiscriminator);
return data.Select(mapper.DeserializeTimeStampedData);
}
/// <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/>
public void Dispose()
{
refitTimestampedSetClient.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@ -3,15 +3,16 @@ 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 DD.Persistence.Models.Common;
namespace DD.Persistence.Client.Clients; namespace DD.Persistence.Client.Clients;
public class WitsDataClient : BaseClient, IWitsDataClient public class WitsDataClient : BaseClient, IWitsDataClient
{ {
private readonly IRefitWitsDataClient refitWitsDataClient; private readonly IRefitWitsDataClient refitWitsDataClient;
public WitsDataClient(IRefitWitsDataClient refitWitsDataClient, ILogger<WitsDataClient> logger) : base(logger) public WitsDataClient(IRefitClientFactory<IRefitWitsDataClient> refitWitsDataClientFactory, ILogger<WitsDataClient> logger) : base(logger)
{ {
this.refitWitsDataClient = refitWitsDataClient; this.refitWitsDataClient = refitWitsDataClientFactory.Create();
} }
public async Task<int> AddRange(IEnumerable<WitsDataDto> dtos, CancellationToken token) public async Task<int> AddRange(IEnumerable<WitsDataDto> dtos, CancellationToken token)

View File

@ -9,13 +9,13 @@
<!--Генерация NuGet пакета при сборке--> <!--Генерация NuGet пакета при сборке-->
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<!--Наименование--> <!--Наименование-->
<Title>Persistence.Client</Title> <Title>DD.Persistence.Client</Title>
<!--Версия пакета--> <!--Версия пакета-->
<VersionPrefix>1.0.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</VersionPrefix> <VersionPrefix>1.5.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH)).1</VersionPrefix>
<!--Версия сборки--> <!--Версия сборки-->
<AssemblyVersion>1.0.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</AssemblyVersion> <AssemblyVersion>1.5.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH)).1</AssemblyVersion>
<!--Id пакета--> <!--Id пакета-->
<PackageId>Persistence.Client</PackageId> <PackageId>DD.Persistence.Client</PackageId>
<!--Автор--> <!--Автор-->
<Authors>Digital Drilling</Authors> <Authors>Digital Drilling</Authors>
@ -33,15 +33,15 @@
<!--Формат пакета с символами--> <!--Формат пакета с символами-->
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!--Путь к пакету--> <!--Путь к пакету-->
<PackageOutputPath>C:\Projects\Nuget</PackageOutputPath> <PackageOutputPath>C:\Projects\Nuget\Persistence</PackageOutputPath>
<!--Readme--> <!--Readme-->
<PackageReadmeFile>Readme.md</PackageReadmeFile> <PackageReadmeFile>Readme.md</PackageReadmeFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.0.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</VersionPrefix> <VersionPrefix>1.5.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</VersionPrefix>
<AssemblyVersion>1.0.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</AssemblyVersion> <AssemblyVersion>1.5.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -53,11 +53,12 @@
<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="RestSharp" Version="112.1.0" /> <PackageReference Include="RestSharp" Version="112.1.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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" /> <ProjectReference Include="..\DD.Persistence.Models\DD.Persistence.Models.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,34 @@
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Models;
using Microsoft.Extensions.DependencyInjection;
namespace DD.Persistence.Client;
/// <summary>
///
/// </summary>
public static class DependencyInjection
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddPersistenceClients(this IServiceCollection services, Dictionary<Guid, Type>? setpointTypeConfigs = null)
{
services.AddTransient(typeof(IRefitClientFactory<>), typeof(RefitClientFactory<>));
services.AddTransient<IChangeLogClient, ChangeLogClient>();
services.AddTransient<IDataSourceSystemClient, DataSourceSystemClient>();
services.AddTransient<ISetpointClient, SetpointClient>();
services.AddTransient<ITechMessagesClient, TechMessagesClient>();
services.AddTransient<ITimestampedValuesClient, TimestampedValuesClient>();
services.AddTransient<IWitsDataClient, WitsDataClient>();
services.AddSingleton<ISetpointConfigStorage, SetpointConfigStorage>(provider =>
{
return new SetpointConfigStorage(setpointTypeConfigs);
});
return services;
}
}

View File

@ -0,0 +1,16 @@
using DD.Persistence.Client.Clients.Interfaces.Refit;
namespace DD.Persistence.Client;
/// <summary>
/// Интерфейс для фабрики, которая создает refit-клиентов
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IRefitClientFactory<T> where T : IRefitClient
{
/// <summary>
/// Создание refit-клиента
/// </summary>
/// <returns></returns>
public T Create();
}

View File

@ -0,0 +1,11 @@
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,146 +0,0 @@
using Microsoft.Extensions.Configuration;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Helpers;
using Refit;
using DD.Persistence.Factories;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json;
namespace DD.Persistence.Client
{
/// <summary>
/// Фабрика клиентов для доступа к Persistence - сервису
/// </summary>
public class PersistenceClientFactory
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
private static readonly RefitSettings RefitSettings = new(new SystemTextJsonContentSerializer(JsonSerializerOptions));
private readonly IServiceProvider provider;
private HttpClient httpClient;
public PersistenceClientFactory(IHttpClientFactory httpClientFactory, IServiceProvider provider, IConfiguration configuration)
{
this.provider = provider;
httpClient = httpClientFactory.CreateClient();
httpClient.Authorize(configuration);
}
public PersistenceClientFactory(IHttpClientFactory httpClientFactory, IAuthTokenFactory authTokenFactory, IServiceProvider provider, IConfiguration configuration)
{
this.provider = provider;
httpClient = httpClientFactory.CreateClient();
var token = authTokenFactory.GetToken();
httpClient.Authorize(token);
}
/// <summary>
/// Получить клиент для работы с уставками
/// </summary>
/// <returns></returns>
public ISetpointClient GetSetpointClient()
{
var logger = provider.GetRequiredService<ILogger<SetpointClient>>();
var restClient = RestService.For<IRefitSetpointClient>(httpClient, RefitSettings);
var client = new SetpointClient(restClient, logger);
return client;
}
/// <summary>
/// Получить клиент для работы с технологическими сообщениями
/// </summary>
/// <returns></returns>
public ITechMessagesClient GetTechMessagesClient()
{
var logger = provider.GetRequiredService<ILogger<TechMessagesClient>>();
var restClient = RestService.For<IRefitTechMessagesClient>(httpClient, RefitSettings);
var client = new TechMessagesClient(restClient, logger);
return client;
}
/// <summary>
/// Получить клиент для работы с временными данными
/// </summary>
/// <typeparam name="TDto"></typeparam>
/// <returns></returns>
public ITimeSeriesClient<TDto> GetTimeSeriesClient<TDto>()
where TDto : class, new()
{
var logger = provider.GetRequiredService<ILogger<TimeSeriesClient<TDto>>>();
var restClient = RestService.For<IRefitTimeSeriesClient<TDto>>(httpClient, RefitSettings);
var client = new TimeSeriesClient<TDto>(restClient, logger);
return client;
}
/// <summary>
/// Получить клиент для работы с данными с отметкой времени
/// </summary>
/// <returns></returns>
public ITimestampedSetClient GetTimestampedSetClient()
{
var logger = provider.GetRequiredService<ILogger<TimestampedSetClient>>();
var restClient = RestService.For<IRefitTimestampedSetClient>(httpClient, RefitSettings);
var client = new TimestampedSetClient(restClient, logger);
return client;
}
/// <summary>
/// Получить клиент для работы с записями ChangeLog
/// </summary>
/// <returns></returns>
public IChangeLogClient GetChangeLogClient()
{
var logger = provider.GetRequiredService<ILogger<ChangeLogClient>>();
var restClient = RestService.For<IRefitChangeLogClient>(httpClient, RefitSettings);
var client = new ChangeLogClient(restClient, logger);
return client;
}
/// <summary>
/// Получить клиент для работы c параметрами Wits
/// </summary>
/// <returns></returns>
public IWitsDataClient GetWitsDataClient()
{
var logger = provider.GetRequiredService<ILogger<WitsDataClient>>();
var restClient = RestService.For<IRefitWitsDataClient>(httpClient, RefitSettings);
var client = new WitsDataClient(restClient, logger);
return client;
}
/// <summary>
/// Получить клиент для работы c системами
/// </summary>
/// <returns></returns>
public IDataSourceSystemClient GetDataSourceSystemClient()
{
var logger = provider.GetRequiredService<ILogger<DataSourceSystemClient>>();
var restClient = RestService.For<IRefitDataSourceSystemClient>(httpClient, RefitSettings);
var client = new DataSourceSystemClient(restClient, logger);
return client;
}
}
}

View File

@ -11,8 +11,7 @@ Persistence сервисом посредством обращения к кон
## Список предоставляемых клиентов ## Список предоставляемых клиентов
- `ISetpointClient` - Клиент для работы с уставками - `ISetpointClient` - Клиент для работы с уставками
- `ITechMessagesClient` - Клиент для работы с технологическими сообщениями - `ITechMessagesClient` - Клиент для работы с технологическими сообщениями
- `ITimeSeriesClient` - Клиент для работы с временными данными - `ITimestampedValuesClient` - Клиент для работы с наборами данных, имеющими отметку времени
- `ITimestampedSetClient` - Клиент для работы с данными с отметкой времени
- `IChangeLogClient` - Клиент для работы с записями ChangeLog - `IChangeLogClient` - Клиент для работы с записями ChangeLog
- `IWitsDataClient` - Клиент для работы с параметрами Wits - `IWitsDataClient` - Клиент для работы с параметрами Wits
- `IDataSourceSystemClient` - Клиент для работы с системами - `IDataSourceSystemClient` - Клиент для работы с системами

View File

@ -0,0 +1,52 @@
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Helpers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Refit;
using System.Configuration;
using System.Text.Json;
namespace DD.Persistence.Client;
/// <summary>
/// Фабрика, которая создает refit-клиентов
/// </summary>
/// <typeparam name="T"></typeparam>
public class RefitClientFactory<T> : IRefitClientFactory<T> where T : IRefitClient
{
private HttpClient client;
private RefitSettings refitSettings;
/// <inheritdoc/>
public RefitClientFactory(IConfiguration configuration, ILogger<IRefitClientFactory<T>> logger, HttpClient client)
{
//this.client = factory.CreateClient();
this.client = client;
var baseUrl = configuration.GetSection("ClientUrl").Get<string>();
if (String.IsNullOrEmpty(baseUrl))
{
var exception = new SettingsPropertyNotFoundException("В настройках конфигурации не указан адрес Persistence сервиса.");
logger.LogError(exception.Message);
throw exception;
}
client.BaseAddress = new Uri(baseUrl);
JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
refitSettings = new(new SystemTextJsonContentSerializer(JsonSerializerOptions));
}
/// <summary>
/// создание клиента
/// </summary>
/// <returns></returns>
public T Create()
{
return RestService.For<T>(client, refitSettings);
}
}

View File

@ -0,0 +1,20 @@
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

@ -0,0 +1,135 @@
using DD.Persistence.Models;
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
namespace DD.Persistence.Client;
internal abstract class TimestampedSetMapperBase
{
public abstract object Map(TimestampedValuesDto data);
}
internal class TimestampedSetMapper<T> : TimestampedSetMapperBase
{
private readonly Type entityType = typeof(T);
public Guid IdDiscriminator { get; }
private readonly ConcurrentDictionary<string, PropertyInfo?> PropertyCache = new();
public TimestampedSetMapper(Guid idDiscriminator)
{
IdDiscriminator = idDiscriminator;
}
public override object Map(TimestampedValuesDto data)
{
return DeserializeTimeStampedData(data)!;
}
public T DeserializeTimeStampedData(TimestampedValuesDto data)
{
if (entityType.IsValueType)
return MapStruct(data);
else
return MapClass(data);
}
private T MapClass(TimestampedValuesDto data)
{
var entity = (T)RuntimeHelpers.GetUninitializedObject(typeof(T));
foreach (var (propertyName, value) in data.Values)
{
if (value is JsonElement jsonElement)
SetPropertyValueFromJson(ref entity, propertyName, jsonElement);
}
SetPropertyValue(ref entity, "Timestamp", data.Timestamp);
return entity;
}
private T MapStruct(TimestampedValuesDto data)
{
var entity = Activator.CreateInstance<T>();
object boxedEntity = entity!;
foreach (var (propertyName, value) in data.Values)
{
if (value is JsonElement jsonElement)
SetPropertyValueForStructFromJson(ref boxedEntity, propertyName, jsonElement);
}
SetPropertyValueForStruct(ref boxedEntity, "Timestamp", data.Timestamp);
return (T)boxedEntity;
}
private void SetPropertyValueForStructFromJson(ref object entity, string propertyName, JsonElement element)
{
var property = GetPropertyInfo(propertyName);
if (property is null)
return;
try
{
var value = element.Deserialize(property.PropertyType);
property.SetValue(entity, value);
}
catch (Exception ex)
{
}
}
private void SetPropertyValueForStruct(ref object entity, string propertyName, object value)
{
var property = GetPropertyInfo(propertyName);
if (property is null)
return;
try
{
var convertedValue = Convert.ChangeType(value, property.PropertyType);
property.SetValue(entity, convertedValue);
}
catch (Exception ex)
{
}
}
private void SetPropertyValueFromJson(ref T entity, string propertyName, JsonElement jsonElement)
{
var property = GetPropertyInfo(propertyName);
if (property is null)
return;
try
{
var value = jsonElement.Deserialize(property.PropertyType);
property.SetValue(entity, value);
}
catch (Exception ex)
{
}
}
private void SetPropertyValue(ref T entity, string propertyName, object value)
{
var property = GetPropertyInfo(propertyName);
if (property is null)
return;
try
{
var convertedValue = Convert.ChangeType(value, property.PropertyType);
property.SetValue(entity, convertedValue);
}
catch (Exception ex)
{
}
}
private PropertyInfo? GetPropertyInfo(string propertyName)
{
return PropertyCache.GetOrAdd(propertyName, name => entityType.GetProperty(name));
}
}

View File

@ -1,5 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using System.Text.Json;
using DD.Persistence.Database.Model; using DD.Persistence.Database.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
@ -12,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("20241226122220_Init")] [Migration("20250122120353_Init")]
partial class Init partial class Init
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -25,6 +26,23 @@ namespace DD.Persistence.Database.Postgres.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b =>
{
b.Property<Guid>("DiscriminatorId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<string>("PropNames")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Наименования полей в порядке индексации");
b.HasKey("DiscriminatorId");
b.ToTable("data_scheme");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
@ -105,27 +123,24 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("tech_message"); b.ToTable("tech_message");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedSet", b => modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b =>
{ {
b.Property<Guid>("IdDiscriminator") b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Дискриминатор ссылка на тип сохраняемых данных"); .HasComment("Дискриминатор системы");
b.Property<DateTimeOffset>("Timestamp") b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Отметка времени, строго в UTC"); .HasComment("Временная отметка");
b.Property<string>("Set") b.Property<string>("Values")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasComment("Набор сохраняемых данных"); .HasComment("Данные");
b.HasKey("IdDiscriminator", "Timestamp"); b.HasKey("DiscriminatorId", "Timestamp");
b.ToTable("timestamped_set", t => b.ToTable("timestamped_values");
{
t.HasComment("Общая таблица данных временных рядов");
});
}); });
modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b => modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b =>
@ -181,96 +196,13 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Model.DataSaub", b =>
{
b.Property<DateTimeOffset>("Date")
.HasColumnType("timestamp with time zone")
.HasColumnName("date");
b.Property<double?>("AxialLoad")
.HasColumnType("double precision")
.HasColumnName("axialLoad");
b.Property<double?>("BitDepth")
.HasColumnType("double precision")
.HasColumnName("bitDepth");
b.Property<double?>("BlockPosition")
.HasColumnType("double precision")
.HasColumnName("blockPosition");
b.Property<double?>("BlockSpeed")
.HasColumnType("double precision")
.HasColumnName("blockSpeed");
b.Property<double?>("Flow")
.HasColumnType("double precision")
.HasColumnName("flow");
b.Property<double?>("HookWeight")
.HasColumnType("double precision")
.HasColumnName("hookWeight");
b.Property<int>("IdFeedRegulator")
.HasColumnType("integer")
.HasColumnName("idFeedRegulator");
b.Property<int?>("Mode")
.HasColumnType("integer")
.HasColumnName("mode");
b.Property<double?>("Mse")
.HasColumnType("double precision")
.HasColumnName("mse");
b.Property<short>("MseState")
.HasColumnType("smallint")
.HasColumnName("mseState");
b.Property<double?>("Pressure")
.HasColumnType("double precision")
.HasColumnName("pressure");
b.Property<double?>("Pump0Flow")
.HasColumnType("double precision")
.HasColumnName("pump0Flow");
b.Property<double?>("Pump1Flow")
.HasColumnType("double precision")
.HasColumnName("pump1Flow");
b.Property<double?>("Pump2Flow")
.HasColumnType("double precision")
.HasColumnName("pump2Flow");
b.Property<double?>("RotorSpeed")
.HasColumnType("double precision")
.HasColumnName("rotorSpeed");
b.Property<double?>("RotorTorque")
.HasColumnType("double precision")
.HasColumnName("rotorTorque");
b.Property<string>("User")
.HasColumnType("text")
.HasColumnName("user");
b.Property<double?>("WellDepth")
.HasColumnType("double precision")
.HasColumnName("wellDepth");
b.HasKey("Date");
b.ToTable("data_saub");
});
modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b => modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("Key")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Ключ"); .HasComment("Ключ");
b.Property<DateTimeOffset>("Created") b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания уставки"); .HasComment("Дата создания уставки");
@ -278,12 +210,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Id автора последнего изменения"); .HasComment("Id автора последнего изменения");
b.Property<object>("Value") b.Property<JsonElement>("Value")
.IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasComment("Значение уставки"); .HasComment("Значение уставки");
b.HasKey("Key", "Created"); b.HasKey("Key", "Timestamp");
b.ToTable("setpoint"); b.ToTable("setpoint");
}); });
@ -298,6 +229,17 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); b.Navigation("System");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b =>
{
b.HasOne("DD.Persistence.Database.Entity.DataScheme", "DataScheme")
.WithMany()
.HasForeignKey("DiscriminatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("DataScheme");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable
@ -33,32 +34,15 @@ namespace DD.Persistence.Database.Postgres.Migrations
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "data_saub", name: "data_scheme",
columns: table => new columns: table => new
{ {
date = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false), DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Идентификатор схемы данных"),
mode = table.Column<int>(type: "integer", nullable: true), PropNames = table.Column<string>(type: "jsonb", nullable: false, comment: "Наименования полей в порядке индексации")
user = table.Column<string>(type: "text", nullable: true),
wellDepth = table.Column<double>(type: "double precision", nullable: true),
bitDepth = table.Column<double>(type: "double precision", nullable: true),
blockPosition = table.Column<double>(type: "double precision", nullable: true),
blockSpeed = table.Column<double>(type: "double precision", nullable: true),
pressure = table.Column<double>(type: "double precision", nullable: true),
axialLoad = table.Column<double>(type: "double precision", nullable: true),
hookWeight = table.Column<double>(type: "double precision", nullable: true),
rotorTorque = table.Column<double>(type: "double precision", nullable: true),
rotorSpeed = table.Column<double>(type: "double precision", nullable: true),
flow = table.Column<double>(type: "double precision", nullable: true),
mseState = table.Column<short>(type: "smallint", nullable: false),
idFeedRegulator = table.Column<int>(type: "integer", nullable: false),
mse = table.Column<double>(type: "double precision", nullable: true),
pump0Flow = table.Column<double>(type: "double precision", nullable: true),
pump1Flow = table.Column<double>(type: "double precision", nullable: true),
pump2Flow = table.Column<double>(type: "double precision", nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_data_saub", x => x.date); table.PrimaryKey("PK_data_scheme", x => x.DiscriminatorId);
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
@ -93,28 +77,33 @@ namespace DD.Persistence.Database.Postgres.Migrations
columns: table => new columns: table => new
{ {
Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"), Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"),
Created = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата создания уставки"), Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата создания уставки"),
Value = table.Column<object>(type: "jsonb", nullable: false, comment: "Значение уставки"), Value = table.Column<JsonElement>(type: "jsonb", nullable: false, comment: "Значение уставки"),
IdUser = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id автора последнего изменения") IdUser = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id автора последнего изменения")
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_setpoint", x => new { x.Key, x.Created }); table.PrimaryKey("PK_setpoint", x => new { x.Key, x.Timestamp });
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "timestamped_set", name: "timestamped_values",
columns: table => new columns: table => new
{ {
IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"), Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Временная отметка"),
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"), DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор системы"),
Set = table.Column<string>(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных") Values = table.Column<string>(type: "jsonb", nullable: false, comment: "Данные")
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_timestamped_set", x => new { x.IdDiscriminator, x.Timestamp }); table.PrimaryKey("PK_timestamped_values", x => new { x.DiscriminatorId, x.Timestamp });
}, table.ForeignKey(
comment: "Общая таблица данных временных рядов"); name: "FK_timestamped_values_data_scheme_DiscriminatorId",
column: x => x.DiscriminatorId,
principalTable: "data_scheme",
principalColumn: "DiscriminatorId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "tech_message", name: "tech_message",
@ -153,9 +142,6 @@ namespace DD.Persistence.Database.Postgres.Migrations
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "change_log"); name: "change_log");
migrationBuilder.DropTable(
name: "data_saub");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "parameter_data"); name: "parameter_data");
@ -166,10 +152,13 @@ namespace DD.Persistence.Database.Postgres.Migrations
name: "tech_message"); name: "tech_message");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "timestamped_set"); name: "timestamped_values");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "data_source_system"); name: "data_source_system");
migrationBuilder.DropTable(
name: "data_scheme");
} }
} }
} }

View File

@ -1,5 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using System.Text.Json;
using DD.Persistence.Database.Model; using DD.Persistence.Database.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
@ -22,6 +23,23 @@ namespace DD.Persistence.Database.Postgres.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("DD.Persistence.Database.Entity.DataScheme", b =>
{
b.Property<Guid>("DiscriminatorId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasComment("Идентификатор схемы данных");
b.Property<string>("PropNames")
.IsRequired()
.HasColumnType("jsonb")
.HasComment("Наименования полей в порядке индексации");
b.HasKey("DiscriminatorId");
b.ToTable("data_scheme");
});
modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b => modelBuilder.Entity("DD.Persistence.Database.Entity.DataSourceSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
@ -102,27 +120,24 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("tech_message"); b.ToTable("tech_message");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedSet", b => modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b =>
{ {
b.Property<Guid>("IdDiscriminator") b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Дискриминатор ссылка на тип сохраняемых данных"); .HasComment("Дискриминатор системы");
b.Property<DateTimeOffset>("Timestamp") b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Отметка времени, строго в UTC"); .HasComment("Временная отметка");
b.Property<string>("Set") b.Property<string>("Values")
.IsRequired() .IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasComment("Набор сохраняемых данных"); .HasComment("Данные");
b.HasKey("IdDiscriminator", "Timestamp"); b.HasKey("DiscriminatorId", "Timestamp");
b.ToTable("timestamped_set", t => b.ToTable("timestamped_values");
{
t.HasComment("Общая таблица данных временных рядов");
});
}); });
modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b => modelBuilder.Entity("DD.Persistence.Database.Model.ChangeLog", b =>
@ -178,96 +193,13 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.ToTable("change_log"); b.ToTable("change_log");
}); });
modelBuilder.Entity("DD.Persistence.Database.Model.DataSaub", b =>
{
b.Property<DateTimeOffset>("Date")
.HasColumnType("timestamp with time zone")
.HasColumnName("date");
b.Property<double?>("AxialLoad")
.HasColumnType("double precision")
.HasColumnName("axialLoad");
b.Property<double?>("BitDepth")
.HasColumnType("double precision")
.HasColumnName("bitDepth");
b.Property<double?>("BlockPosition")
.HasColumnType("double precision")
.HasColumnName("blockPosition");
b.Property<double?>("BlockSpeed")
.HasColumnType("double precision")
.HasColumnName("blockSpeed");
b.Property<double?>("Flow")
.HasColumnType("double precision")
.HasColumnName("flow");
b.Property<double?>("HookWeight")
.HasColumnType("double precision")
.HasColumnName("hookWeight");
b.Property<int>("IdFeedRegulator")
.HasColumnType("integer")
.HasColumnName("idFeedRegulator");
b.Property<int?>("Mode")
.HasColumnType("integer")
.HasColumnName("mode");
b.Property<double?>("Mse")
.HasColumnType("double precision")
.HasColumnName("mse");
b.Property<short>("MseState")
.HasColumnType("smallint")
.HasColumnName("mseState");
b.Property<double?>("Pressure")
.HasColumnType("double precision")
.HasColumnName("pressure");
b.Property<double?>("Pump0Flow")
.HasColumnType("double precision")
.HasColumnName("pump0Flow");
b.Property<double?>("Pump1Flow")
.HasColumnType("double precision")
.HasColumnName("pump1Flow");
b.Property<double?>("Pump2Flow")
.HasColumnType("double precision")
.HasColumnName("pump2Flow");
b.Property<double?>("RotorSpeed")
.HasColumnType("double precision")
.HasColumnName("rotorSpeed");
b.Property<double?>("RotorTorque")
.HasColumnType("double precision")
.HasColumnName("rotorTorque");
b.Property<string>("User")
.HasColumnType("text")
.HasColumnName("user");
b.Property<double?>("WellDepth")
.HasColumnType("double precision")
.HasColumnName("wellDepth");
b.HasKey("Date");
b.ToTable("data_saub");
});
modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b => modelBuilder.Entity("DD.Persistence.Database.Model.Setpoint", b =>
{ {
b.Property<Guid>("Key") b.Property<Guid>("Key")
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Ключ"); .HasComment("Ключ");
b.Property<DateTimeOffset>("Created") b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания уставки"); .HasComment("Дата создания уставки");
@ -275,12 +207,11 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("uuid") .HasColumnType("uuid")
.HasComment("Id автора последнего изменения"); .HasComment("Id автора последнего изменения");
b.Property<object>("Value") b.Property<JsonElement>("Value")
.IsRequired()
.HasColumnType("jsonb") .HasColumnType("jsonb")
.HasComment("Значение уставки"); .HasComment("Значение уставки");
b.HasKey("Key", "Created"); b.HasKey("Key", "Timestamp");
b.ToTable("setpoint"); b.ToTable("setpoint");
}); });
@ -295,6 +226,17 @@ namespace DD.Persistence.Database.Postgres.Migrations
b.Navigation("System"); b.Navigation("System");
}); });
modelBuilder.Entity("DD.Persistence.Database.Entity.TimestampedValues", b =>
{
b.HasOne("DD.Persistence.Database.Entity.DataScheme", "DataScheme")
.WithMany()
.HasForeignKey("DiscriminatorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("DataScheme");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -1,8 +1,9 @@
 
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DD.Persistence.Models;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using DD.Persistence.ModelsAbstractions;
using DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Model; namespace DD.Persistence.Database.Model;

View File

@ -1,65 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Model;
[Table("data_saub")]
public class DataSaub : ITimestampedData
{
[Key, Column("date")]
public DateTimeOffset Date { get; set; }
[Column("mode")]
public int? Mode { get; set; }
[Column("user")]
public string? User { get; set; }
[Column("wellDepth")]
public double? WellDepth { get; set; }
[Column("bitDepth")]
public double? BitDepth { get; set; }
[Column("blockPosition")]
public double? BlockPosition { get; set; }
[Column("blockSpeed")]
public double? BlockSpeed { get; set; }
[Column("pressure")]
public double? Pressure { get; set; }
[Column("axialLoad")]
public double? AxialLoad { get; set; }
[Column("hookWeight")]
public double? HookWeight { get; set; }
[Column("rotorTorque")]
public double? RotorTorque { get; set; }
[Column("rotorSpeed")]
public double? RotorSpeed { get; set; }
[Column("flow")]
public double? Flow { get; set; }
[Column("mseState")]
public short MseState { get; set; }
[Column("idFeedRegulator")]
public int IdFeedRegulator { get; set; }
[Column("mse")]
public double? Mse { get; set; }
[Column("pump0Flow")]
public double? Pump0Flow { get; set; }
[Column("pump1Flow")]
public double? Pump1Flow { get; set; }
[Column("pump2Flow")]
public double? Pump2Flow { get; set; }
}

View File

@ -0,0 +1,15 @@
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

@ -11,7 +11,7 @@ public class DataSourceSystem
public Guid SystemId { get; set; } public Guid SystemId { get; set; }
[Required, Column(TypeName = "varchar(256)"), Comment("Наименование системы - источника данных")] [Required, Column(TypeName = "varchar(256)"), Comment("Наименование системы - источника данных")]
public required string Name { get; set; } public string Name { get; set; } = string.Empty;
[Comment("Описание системы - источника данных")] [Comment("Описание системы - источника данных")]
public string? Description { get; set; } public string? Description { get; set; }

View File

@ -1,8 +0,0 @@
namespace DD.Persistence.Database.Model;
public interface ITimestampedData
{
/// <summary>
/// Дата (должна быть обязательно в UTC)
/// </summary>
DateTimeOffset Date { get; set; }
}

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -6,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 public class ParameterData : ITimestampedItem
{ {
[Required, Comment("Дискриминатор системы")] [Required, Comment("Дискриминатор системы")]
public Guid DiscriminatorId { get; set; } public Guid DiscriminatorId { get; set; }

View File

@ -1,20 +1,22 @@
using Microsoft.EntityFrameworkCore; using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace DD.Persistence.Database.Model namespace DD.Persistence.Database.Model
{ {
[Table("setpoint")] [Table("setpoint")]
[PrimaryKey(nameof(Key), nameof(Created))] [PrimaryKey(nameof(Key), nameof(Timestamp))]
public class Setpoint public class Setpoint : ITimestampedItem
{ {
[Comment("Ключ")] [Comment("Ключ")]
public Guid Key { get; set; } public Guid Key { get; set; }
[Column(TypeName = "jsonb"), Comment("Значение уставки")] [Column(TypeName = "jsonb"), Comment("Значение уставки")]
public required object Value { get; set; } public required JsonElement Value { get; set; }
[Comment("Дата создания уставки")] [Comment("Дата создания уставки")]
public DateTimeOffset Created { get; set; } public DateTimeOffset Timestamp { get; set; }
[Comment("Id автора последнего изменения")] [Comment("Id автора последнего изменения")]
public Guid IdUser { get; set; } public Guid IdUser { get; set; }

View File

@ -1,3 +1,4 @@
using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -5,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity namespace DD.Persistence.Database.Entity
{ {
[Table("tech_message")] [Table("tech_message")]
public class TechMessage public class TechMessage : ITimestampedItem
{ {
[Key, Comment("Id события")] [Key, Comment("Id события")]
public Guid EventId { get; set; } public Guid EventId { get; set; }

View File

@ -1,12 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("timestamped_set")]
[Comment("Общая таблица данных временных рядов")]
[PrimaryKey(nameof(IdDiscriminator), nameof(Timestamp))]
public record TimestampedSet(
[property: Comment("Дискриминатор ссылка на тип сохраняемых данных")] Guid IdDiscriminator,
[property: Comment("Отметка времени, строго в UTC")] DateTimeOffset Timestamp,
[property: Column(TypeName = "jsonb"), Comment("Набор сохраняемых данных")] IDictionary<string, object> Set);

View File

@ -0,0 +1,23 @@
using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity;
[Table("timestamped_values")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
public class TimestampedValues : ITimestampedItem
{
[Comment("Временная отметка"), Key]
public DateTimeOffset Timestamp { get; set; }
[Comment("Дискриминатор системы"),]
public Guid DiscriminatorId { get; set; }
[Comment("Данные"), Column(TypeName = "jsonb")]
public required object[] Values { get; set; }
[Required, ForeignKey(nameof(DiscriminatorId)), Comment("Идентификаторы")]
public virtual DataScheme? DataScheme { get; set; }
}

View File

@ -1,5 +1,4 @@
 namespace DD.Persistence.Database.EntityAbstractions;
namespace DD.Persistence.Database.Model;
/// <summary> /// <summary>
/// Часть записи, описывающая изменение /// Часть записи, описывающая изменение

View File

@ -0,0 +1,8 @@
namespace DD.Persistence.Database.EntityAbstractions;
public interface ITimestampedItem
{
/// <summary>
/// Дата (должна быть обязательно в UTC)
/// </summary>
DateTimeOffset Timestamp { get; set; }
}

View File

@ -9,11 +9,11 @@ namespace DD.Persistence.Database;
/// </summary> /// </summary>
public class PersistenceDbContext : DbContext public class PersistenceDbContext : DbContext
{ {
public DbSet<DataSaub> DataSaub => Set<DataSaub>();
public DbSet<Setpoint> Setpoint => Set<Setpoint>(); public DbSet<Setpoint> Setpoint => Set<Setpoint>();
public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>(); public DbSet<DataScheme> DataSchemes => Set<DataScheme>();
public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>();
public DbSet<ChangeLog> ChangeLog => Set<ChangeLog>(); public DbSet<ChangeLog> ChangeLog => Set<ChangeLog>();
@ -31,8 +31,12 @@ public class PersistenceDbContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<TimestampedSet>() modelBuilder.Entity<DataScheme>()
.Property(e => e.Set) .Property(e => e.PropNames)
.HasJsonConversion();
modelBuilder.Entity<TimestampedValues>()
.Property(e => e.Values)
.HasJsonConversion(); .HasJsonConversion();
modelBuilder.Entity<ChangeLog>() modelBuilder.Entity<ChangeLog>()

View File

@ -7,6 +7,11 @@ using DD.Persistence.Models.Requests;
using Xunit; using Xunit;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client; using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Clients;
using Microsoft.Extensions.Logging;
using Refit;
using System.Net.Http;
namespace DD.Persistence.IntegrationTests.Controllers; namespace DD.Persistence.IntegrationTests.Controllers;
public class ChangeLogControllerTest : BaseIntegrationTest public class ChangeLogControllerTest : BaseIntegrationTest
@ -16,10 +21,12 @@ public class ChangeLogControllerTest : BaseIntegrationTest
public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory) public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
var persistenceClientFactory = scope.ServiceProvider var refitClientFactory = scope.ServiceProvider
.GetRequiredService<PersistenceClientFactory>(); .GetRequiredService<IRefitClientFactory<IRefitChangeLogClient>>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<ChangeLogClient>>();
client = persistenceClientFactory.GetChangeLogClient(); client = scope.ServiceProvider
.GetRequiredService<IChangeLogClient>();
} }
[Fact] [Fact]

View File

@ -1,85 +0,0 @@
using DD.Persistence.Database.Model;
using DD.Persistence.Models;
using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers;
public class DataSaubControllerTest : TimeSeriesBaseControllerTest<DataSaub, DataSaubDto>
{
private readonly DataSaubDto dto = new()
{
AxialLoad = 1,
BitDepth = 2,
BlockPosition = 3,
BlockSpeed = 4,
Date = DateTimeOffset.UtcNow,
Flow = 5,
HookWeight = 6,
IdFeedRegulator = 8,
Mode = 9,
Mse = 10,
MseState = 11,
Pressure = 12,
Pump0Flow = 13,
Pump1Flow = 14,
Pump2Flow = 15,
RotorSpeed = 16,
RotorTorque = 17,
User = string.Empty,
WellDepth = 18,
};
private readonly DataSaub entity = new()
{
AxialLoad = 1,
BitDepth = 2,
BlockPosition = 3,
BlockSpeed = 4,
Date = DateTimeOffset.UtcNow,
Flow = 5,
HookWeight = 6,
IdFeedRegulator = 8,
Mode = 9,
Mse = 10,
MseState = 11,
Pressure = 12,
Pump0Flow = 13,
Pump1Flow = 14,
Pump2Flow = 15,
RotorSpeed = 16,
RotorTorque = 17,
User = string.Empty,
WellDepth = 18,
};
public DataSaubControllerTest(WebAppFactoryFixture factory) : base(factory)
{
}
[Fact]
public async Task InsertRange_returns_success()
{
await InsertRangeSuccess(dto);
}
[Fact]
public async Task Get_returns_success()
{
var beginDate = DateTimeOffset.UtcNow.AddDays(-1);
var endDate = DateTimeOffset.UtcNow;
await GetSuccess(beginDate, endDate, entity);
}
[Fact]
public async Task GetDatesRange_returns_success()
{
await GetDatesRangeSuccess(entity);
}
[Fact]
public async Task GetResampledData_returns_success()
{
await GetResampledDataSuccess(entity);
}
}

View File

@ -6,6 +6,8 @@ using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models; using DD.Persistence.Models;
using Xunit; using Xunit;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using Microsoft.Extensions.Logging;
namespace DD.Persistence.IntegrationTests.Controllers namespace DD.Persistence.IntegrationTests.Controllers
{ {
@ -16,11 +18,12 @@ namespace DD.Persistence.IntegrationTests.Controllers
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
public DataSourceSystemControllerTest(WebAppFactoryFixture factory) : base(factory) public DataSourceSystemControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
var scope = factory.Services.CreateScope(); var refitClientFactory = scope.ServiceProvider
var persistenceClientFactory = scope.ServiceProvider .GetRequiredService<IRefitClientFactory<IRefitDataSourceSystemClient>>();
.GetRequiredService<PersistenceClientFactory>(); var logger = scope.ServiceProvider.GetRequiredService<ILogger<DataSourceSystemClient>>();
dataSourceSystemClient = persistenceClientFactory.GetDataSourceSystemClient(); dataSourceSystemClient = scope.ServiceProvider
.GetRequiredService<IDataSourceSystemClient>();
memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>(); memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
} }

View File

@ -1,8 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
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.Database.Model; using DD.Persistence.Database.Model;
using System.Net; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using Xunit; using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers namespace DD.Persistence.IntegrationTests.Controllers
@ -10,20 +13,45 @@ namespace DD.Persistence.IntegrationTests.Controllers
public class SetpointControllerTest : BaseIntegrationTest public class SetpointControllerTest : BaseIntegrationTest
{ {
private readonly ISetpointClient setpointClient; private readonly ISetpointClient setpointClient;
private class TestObject private readonly SetpointConfigStorage configStorage;
{
public string? Value1 { get; set; }
public int? Value2 { get; set; }
}
public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory) public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
var scope = factory.Services.CreateScope(); var refitClientFactory = scope.ServiceProvider
var persistenceClientFactory = scope.ServiceProvider .GetRequiredService<IRefitClientFactory<IRefitSetpointClient>>();
.GetRequiredService<PersistenceClientFactory>(); var logger = scope.ServiceProvider.GetRequiredService<ILogger<SetpointClient>>();
setpointClient = persistenceClientFactory.GetSetpointClient(); setpointClient = scope.ServiceProvider
.GetRequiredService<ISetpointClient>();
configStorage = (SetpointConfigStorage)scope.ServiceProvider.GetRequiredService<ISetpointConfigStorage>();
} }
[Fact]
public async Task GetCurrent_returns_correctType()
{
var id = Guid.Parse("e0fcad22-1761-476e-a729-a3c59d51ba41");
configStorage.AddOrReplace(id, typeof(float));
await setpointClient.Add(id, 48.3f, CancellationToken.None);
//act
var response = await setpointClient.GetCurrent([id], CancellationToken.None);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
Assert.Single(response);
var item = response.First();
Assert.Equal(item.Key, id);
Assert.IsNotType<JsonElement>(item.Value);
Assert.Equal(item.Value, 48.3f);
}
[Fact] [Fact]
public async Task GetCurrent_returns_success() public async Task GetCurrent_returns_success()
{ {
@ -35,7 +63,7 @@ namespace DD.Persistence.IntegrationTests.Controllers
}; };
//act //act
var response = await setpointClient.GetCurrent(setpointKeys, new CancellationToken()); var response = await setpointClient.GetCurrent(setpointKeys, CancellationToken.None);
//assert //assert
Assert.NotNull(response); Assert.NotNull(response);
@ -149,7 +177,7 @@ namespace DD.Persistence.IntegrationTests.Controllers
await Add(); await Add();
var dateBegin = DateTimeOffset.MinValue; var dateBegin = DateTimeOffset.UtcNow.AddDays(-1);
var take = 1; var take = 1;
var part = await setpointClient.GetPart(dateBegin, take, CancellationToken.None); var part = await setpointClient.GetPart(dateBegin, take, CancellationToken.None);
@ -160,14 +188,17 @@ namespace DD.Persistence.IntegrationTests.Controllers
Assert.NotNull(response); Assert.NotNull(response);
var expectedValue = part! var expectedValue = part!
.FirstOrDefault()!.Created .FirstOrDefault()!.Timestamp
.ToString("dd.MM.yyyy-HH:mm:ss"); .ToUniversalTime()
.ToString();
var actualValueFrom = response.From var actualValueFrom = response.From
.ToString("dd.MM.yyyy-HH:mm:ss"); .ToUniversalTime()
.ToString();
Assert.Equal(expectedValue, actualValueFrom); Assert.Equal(expectedValue, actualValueFrom);
var actualValueTo = response.To var actualValueTo = response.To
.ToString("dd.MM.yyyy-HH:mm:ss"); .ToUniversalTime()
.ToString();
Assert.Equal(expectedValue, actualValueTo); Assert.Equal(expectedValue, actualValueTo);
} }
@ -212,7 +243,7 @@ namespace DD.Persistence.IntegrationTests.Controllers
{ {
//arrange //arrange
var setpointKey = Guid.NewGuid(); var setpointKey = Guid.NewGuid();
var setpointValue = new TestObject() var setpointValue = new
{ {
Value1 = "1", Value1 = "1",
Value2 = 2 Value2 = 2

View File

@ -1,28 +1,31 @@
using Microsoft.Extensions.Caching.Memory; using DD.Persistence.Client;
using Microsoft.Extensions.DependencyInjection; using DD.Persistence.Client.Clients;
using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Enumerations; using DD.Persistence.Models.Enumerations;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
using System.Net; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers namespace DD.Persistence.IntegrationTests.Controllers
{ {
public class TechMessagesControllerTest : BaseIntegrationTest public class TechMessagesControllerTest : BaseIntegrationTest
{ {
private static readonly string SystemCacheKey = $"{typeof(Database.Entity.DataSourceSystem).FullName}CacheKey"; private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey";
private readonly ITechMessagesClient techMessagesClient; private readonly ITechMessagesClient techMessagesClient;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory) public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
var scope = factory.Services.CreateScope(); var refitClientFactory = scope.ServiceProvider
var persistenceClientFactory = scope.ServiceProvider .GetRequiredService<IRefitClientFactory<IRefitTechMessagesClient>>();
.GetRequiredService<PersistenceClientFactory>(); var logger = scope.ServiceProvider.GetRequiredService<ILogger<TechMessagesClient>>();
techMessagesClient = persistenceClientFactory.GetTechMessagesClient(); techMessagesClient = scope.ServiceProvider
.GetRequiredService<ITechMessagesClient>();
memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>(); memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
} }

View File

@ -1,118 +0,0 @@
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Database.Model;
using System.Net;
using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers;
public abstract class TimeSeriesBaseControllerTest<TEntity, TDto> : BaseIntegrationTest
where TEntity : class, ITimestampedData, new()
where TDto : class, new()
{
private readonly ITimeSeriesClient<TDto> timeSeriesClient;
public TimeSeriesBaseControllerTest(WebAppFactoryFixture factory) : base(factory)
{
dbContext.CleanupDbSet<TEntity>();
var scope = factory.Services.CreateScope();
var persistenceClientFactory = scope.ServiceProvider
.GetRequiredService<PersistenceClientFactory>();
timeSeriesClient = persistenceClientFactory.GetTimeSeriesClient<TDto>();
}
public async Task InsertRangeSuccess(TDto dto)
{
//arrange
var expected = dto.Adapt<TDto>();
//act
var response = await timeSeriesClient.AddRange(new TDto[] { expected }, new CancellationToken());
//assert
Assert.Equal(1, response);
}
public async Task GetSuccess(DateTimeOffset beginDate, DateTimeOffset endDate, TEntity entity)
{
//arrange
var dbset = dbContext.Set<TEntity>();
dbset.Add(entity);
dbContext.SaveChanges();
var response = await timeSeriesClient.Get(beginDate, endDate, new CancellationToken());
//assert
Assert.NotNull(response);
Assert.Single(response);
}
public async Task GetDatesRangeSuccess(TEntity entity)
{
//arrange
var datesRangeExpected = 30;
var entity2 = entity.Adapt<TEntity>();
entity2.Date = entity.Date.AddDays(datesRangeExpected);
var dbset = dbContext.Set<TEntity>();
dbset.Add(entity);
dbset.Add(entity2);
dbContext.SaveChanges();
var response = await timeSeriesClient.GetDatesRange(new CancellationToken());
//assert
Assert.NotNull(response);
var datesRangeActual = (response.To - response.From).Days;
Assert.Equal(datesRangeExpected, datesRangeActual);
}
public async Task GetResampledDataSuccess(TEntity entity)
{
//arrange
var approxPointsCount = 10;
var differenceBetweenStartAndEndDays = 50;
var entities = new List<TEntity>();
for (var i = 1; i <= differenceBetweenStartAndEndDays; i++)
{
var entity2 = entity.Adapt<TEntity>();
entity2.Date = entity.Date.AddDays(i - 1);
entities.Add(entity2);
}
var dbset = dbContext.Set<TEntity>();
dbset.AddRange(entities);
dbContext.SaveChanges();
var response = await timeSeriesClient.GetResampledData(entity.Date.AddMinutes(-1), differenceBetweenStartAndEndDays * 24 * 60 * 60 + 60, approxPointsCount, new CancellationToken());
//assert
Assert.NotNull(response);
var ratio = entities.Count / approxPointsCount;
if (ratio > 1)
{
var expectedResampledCount = entities
.Where((_, index) => index % ratio == 0)
.Count();
Assert.Equal(expectedResampledCount, response.Count());
}
else
{
Assert.Equal(entities.Count(), response.Count());
}
}
}

View File

@ -1,206 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Models;
using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers;
public class TimestampedSetControllerTest : BaseIntegrationTest
{
private readonly ITimestampedSetClient client;
public TimestampedSetControllerTest(WebAppFactoryFixture factory) : base(factory)
{
var persistenceClientFactory = scope.ServiceProvider
.GetRequiredService<PersistenceClientFactory>();
client = persistenceClientFactory.GetTimestampedSetClient();
}
[Fact]
public async Task InsertRange()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
IEnumerable<TimestampedSetDto> testSets = Generate(10, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
// act
var response = await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
// assert
Assert.Equal(testSets.Count(), response);
}
[Fact]
public async Task Get_without_filter()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 10;
IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
// act
var response = await client.Get(idDiscriminator, null, null, 0, int.MaxValue, CancellationToken.None);
// assert
Assert.NotNull(response);
Assert.Equal(count, response.Count());
}
[Fact]
public async Task Get_with_filter_props()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 10;
IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
string[] props = ["A"];
// act
var response = await client.Get(idDiscriminator, null, props, 0, int.MaxValue, new CancellationToken());
// assert
Assert.NotNull(response);
Assert.Equal(count, response.Count());
foreach (var item in response)
{
Assert.Single(item.Set);
var kv = item.Set.First();
Assert.Equal("A", kv.Key);
}
}
[Fact]
public async Task Get_geDate()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 10;
var dateMin = DateTimeOffset.Now;
var dateMax = DateTimeOffset.Now.AddSeconds(count);
IEnumerable<TimestampedSetDto> testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7)));
var insertResponse = await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
var tail = testSets.OrderBy(t => t.Timestamp).Skip(count / 2).Take(int.MaxValue);
var geDate = tail.First().Timestamp;
var tolerance = TimeSpan.FromSeconds(1);
var expectedCount = tail.Count();
// act
var response = await client.Get(idDiscriminator, geDate, null, 0, int.MaxValue, CancellationToken.None);
// assert
Assert.NotNull(response);
Assert.Equal(expectedCount, response.Count());
var minDate = response.Min(t => t.Timestamp);
Assert.Equal(geDate, geDate, tolerance);
}
[Fact]
public async Task Get_with_skip_take()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 10;
IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
var expectedCount = count / 2;
// act
var response = await client.Get(idDiscriminator, null, null, 2, expectedCount, new CancellationToken());
// assert
Assert.NotNull(response);
Assert.Equal(expectedCount, response.Count());
}
[Fact]
public async Task Get_with_big_skip_take()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
var expectedCount = 1;
int count = 10 + expectedCount;
IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
// act
var response = await client.Get(idDiscriminator, null, null, count - expectedCount, count, new CancellationToken());
// assert
Assert.NotNull(response);
Assert.Equal(expectedCount, response.Count());
}
[Fact]
public async Task GetLast()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 10;
IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
var expectedCount = 8;
// act
var response = await client.GetLast(idDiscriminator, null, expectedCount, new CancellationToken());
// assert
Assert.NotNull(response);
Assert.Equal(expectedCount, response.Count());
}
[Fact]
public async Task GetDatesRange()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 10;
var dateMin = DateTimeOffset.Now;
var dateMax = DateTimeOffset.Now.AddSeconds(count - 1);
IEnumerable<TimestampedSetDto> testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
var tolerance = TimeSpan.FromSeconds(1);
// act
var response = await client.GetDatesRange(idDiscriminator, new CancellationToken());
// assert
Assert.NotNull(response);
Assert.Equal(dateMin, response.From, tolerance);
Assert.Equal(dateMax, response.To, tolerance);
}
[Fact]
public async Task Count()
{
// arrange
Guid idDiscriminator = Guid.NewGuid();
int count = 144;
IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
// act
var response = await client.Count(idDiscriminator, new CancellationToken());
// assert
Assert.Equal(count, response);
}
private static IEnumerable<TimestampedSetDto> Generate(int n, DateTimeOffset from)
{
for (int i = 0; i < n; i++)
yield return new TimestampedSetDto
(
from.AddSeconds(i),
new Dictionary<string, object>{
{"A", i },
{"B", i * 1.1 },
{"C", $"Any{i}" },
{"D", DateTimeOffset.Now},
}
);
}
}

View File

@ -0,0 +1,405 @@
using DD.Persistence.Client;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers;
public class TimestampedValuesControllerTest : BaseIntegrationTest
{
private readonly ITimestampedValuesClient timestampedValuesClient;
private readonly IMemoryCache memoryCache;
private IEnumerable<Guid> discriminatorIds = [];
public TimestampedValuesControllerTest(WebAppFactoryFixture factory) : base(factory)
{
var refitClientFactory = scope.ServiceProvider
.GetRequiredService<IRefitClientFactory<IRefitTimestampedValuesClient>>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<TimestampedValuesClient>>();
timestampedValuesClient = scope.ServiceProvider
.GetRequiredService<ITimestampedValuesClient>();
memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
}
[Fact]
public async Task AddRange_returns_success()
{
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
await AddRange(discriminatorId);
}
[Fact]
public async Task Get_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
//act
var response = await timestampedValuesClient.Get(discriminatorId, null, null, 0, 1, CancellationToken.None);
//assert
Assert.Null(response);
}
[Fact]
public async Task Get_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
var columnNames = new List<string>() { "A", "C" };
var skip = 5;
var take = 5;
var dtos = await AddRange(discriminatorId);
//act
var response = await timestampedValuesClient.Get(discriminatorId, timestampBegin, columnNames, skip, take, CancellationToken.None);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
var actualCount = response.Count();
Assert.Equal(take, actualCount);
var actualColumnNames = response.SelectMany(e => e.Values.Keys).Distinct().ToList();
Assert.Equal(columnNames, actualColumnNames);
var expectedValueKind = JsonValueKind.Number;
var actualValueKind = ((JsonElement) response.First().Values["A"]).ValueKind;
Assert.Equal(expectedValueKind, actualValueKind);
expectedValueKind = JsonValueKind.String;
actualValueKind = ((JsonElement)response.First().Values["C"]).ValueKind;
Assert.Equal(expectedValueKind, actualValueKind);
}
[Fact]
public async Task GetGtDate_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
//act
var response = await timestampedValuesClient.GetGtDate(discriminatorId, timestampBegin, CancellationToken.None);
//assert
Assert.Null(response);
}
[Fact]
public async Task GetGtDate_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow.AddSeconds(-5);
//act
var response = await timestampedValuesClient.GetGtDate(discriminatorId, timestampBegin, CancellationToken.None);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
var expectedCount = dtos.Count(dto => dto.Timestamp.ToUniversalTime() > timestampBegin);
var actualCount = response.Count();
Assert.Equal(expectedCount, actualCount);
}
[Fact]
public async Task GetFirst_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var take = 1;
//act
var response = await timestampedValuesClient.GetFirst(discriminatorId, take, CancellationToken.None);
//assert
Assert.Null(response);
}
[Fact]
public async Task GetFirst_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId);
var take = 1;
//act
var response = await timestampedValuesClient.GetFirst(discriminatorId, take, CancellationToken.None);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
var expectedTimestampString = dtos
.OrderBy(dto => dto.Timestamp)
.First().Timestamp
.ToUniversalTime()
.ToString();
var actualTimestampString = response
.First().Timestamp
.ToUniversalTime()
.ToString();
Assert.Equal(expectedTimestampString, actualTimestampString);
}
[Fact]
public async Task GetLast_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var take = 1;
//act
var response = await timestampedValuesClient.GetLast(discriminatorId, take, CancellationToken.None);
//assert
Assert.Null(response);
}
[Fact]
public async Task GetLast_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId);
var take = 1;
//act
var response = await timestampedValuesClient.GetLast(discriminatorId, take, CancellationToken.None);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
var expectedTimestampString = dtos
.OrderByDescending(dto => dto.Timestamp)
.First().Timestamp
.ToUniversalTime()
.ToString();
var actualTimestampString = response
.First().Timestamp
.ToUniversalTime()
.ToString();
Assert.Equal(expectedTimestampString, actualTimestampString);
}
[Fact]
public async Task GetResampledData_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow;
//act
var response = await timestampedValuesClient.GetResampledData(discriminatorId, timestampBegin);
//assert
Assert.Null(response);
}
[Fact]
public async Task GetResampledData_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var count = 2048;
var timestampBegin = DateTimeOffset.UtcNow;
var dtos = await AddRange(discriminatorId, count);
//act
var response = await timestampedValuesClient.GetResampledData(discriminatorId, timestampBegin, count);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
var expectedCount = count / 2;
var actualCount = response.Count();
Assert.Equal(expectedCount, actualCount);
}
[Fact]
public async Task Count_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
//act
var response = await timestampedValuesClient.Count(discriminatorId, CancellationToken.None);
//assert
Assert.Equal(0, response);
}
[Fact]
public async Task Count_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId);
//act
var response = await timestampedValuesClient.Count(discriminatorId, CancellationToken.None);
//assert
var expectedCount = dtos.Count();
Assert.Equal(expectedCount, response);
}
[Fact]
public async Task GetDatesRange_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
//act
var response = await timestampedValuesClient.GetDatesRange(discriminatorId, CancellationToken.None);
//assert
Assert.Null(response);
}
[Fact]
public async Task GetDatesRange_AfterSave_returns_success()
{
//arrange
Cleanup();
var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId);
//act
var response = await timestampedValuesClient.GetDatesRange(discriminatorId, CancellationToken.None);
//assert
Assert.NotNull(response);
var expectedDateFromString = dtos
.OrderBy(dto => dto.Timestamp)
.First().Timestamp
.ToUniversalTime()
.ToString();
var actualDateFromString = response.From
.ToUniversalTime()
.ToString();
Assert.Equal(expectedDateFromString, actualDateFromString);
var expectedDateToString = dtos
.OrderByDescending(dto => dto.Timestamp)
.First().Timestamp
.ToUniversalTime()
.ToString();
var actualDateToString = response.To
.ToUniversalTime()
.ToString();
Assert.Equal(expectedDateToString, actualDateToString);
}
private async Task<IEnumerable<TimestampedValuesDto>> AddRange(Guid discriminatorId, int countToCreate = 10)
{
// arrange
IEnumerable<TimestampedValuesDto> generatedDtos = Generate(countToCreate, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
// act
var response = await timestampedValuesClient.AddRange(discriminatorId, generatedDtos, CancellationToken.None);
// assert
Assert.Equal(generatedDtos.Count(), response);
return generatedDtos;
}
private static IEnumerable<TimestampedValuesDto> Generate(int countToCreate, DateTimeOffset from)
{
var result = new List<TimestampedValuesDto>();
for (int i = 0; i < countToCreate; i++)
{
var values = new Dictionary<string, object>()
{
{ "A", i },
{ "B", i * 1.1 },
{ "C", $"Any{i}" },
{ "D", DateTimeOffset.Now },
};
yield return new TimestampedValuesDto()
{
Timestamp = from.AddSeconds(i),
Values = values
};
}
}
private void Cleanup()
{
discriminatorIds = [];
dbContext.CleanupDbSet<TimestampedValues>();
dbContext.CleanupDbSet<DataScheme>();
}
}

View File

@ -1,10 +1,12 @@
using Microsoft.Extensions.DependencyInjection; using DD.Persistence.Client;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Models; using DD.Persistence.Models;
using System.Net; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client;
namespace DD.Persistence.IntegrationTests.Controllers; namespace DD.Persistence.IntegrationTests.Controllers;
public class WitsDataControllerTest : BaseIntegrationTest public class WitsDataControllerTest : BaseIntegrationTest
@ -13,11 +15,12 @@ public class WitsDataControllerTest : BaseIntegrationTest
public WitsDataControllerTest(WebAppFactoryFixture factory) : base(factory) public WitsDataControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
var scope = factory.Services.CreateScope(); var refitClientFactory = scope.ServiceProvider
var persistenceClientFactory = scope.ServiceProvider .GetRequiredService<IRefitClientFactory<IRefitWitsDataClient>>();
.GetRequiredService<PersistenceClientFactory>(); var logger = scope.ServiceProvider.GetRequiredService<ILogger<WitsDataClient>>();
witsDataClient = persistenceClientFactory.GetWitsDataClient(); witsDataClient = scope.ServiceProvider
.GetRequiredService<IWitsDataClient>();
} }
[Fact] [Fact]

View File

@ -1,4 +1,7 @@
namespace DD.Persistence.IntegrationTests using DD.Persistence.Client.Helpers;
using Microsoft.Extensions.Configuration;
namespace DD.Persistence.IntegrationTests
{ {
/// <summary> /// <summary>
/// Фабрика HTTP клиентов для интеграционных тестов /// Фабрика HTTP клиентов для интеграционных тестов
@ -6,14 +9,19 @@
public class TestHttpClientFactory : IHttpClientFactory public class TestHttpClientFactory : IHttpClientFactory
{ {
private readonly WebAppFactoryFixture factory; private readonly WebAppFactoryFixture factory;
private readonly IConfiguration configuration;
public TestHttpClientFactory(WebAppFactoryFixture factory) public TestHttpClientFactory(WebAppFactoryFixture factory, IConfiguration configuration)
{ {
this.factory = factory; this.factory = factory;
this.configuration = configuration;
} }
public HttpClient CreateClient(string name) public HttpClient CreateClient(string name)
{ {
return factory.CreateClient(); var client = factory.CreateClient();
client.Authorize(configuration);
return client;
} }
} }
} }

View File

@ -11,6 +11,9 @@ using DD.Persistence.Database.Model;
using DD.Persistence.Database.Postgres; using DD.Persistence.Database.Postgres;
using RestSharp; using RestSharp;
using DD.Persistence.App; using DD.Persistence.App;
using DD.Persistence.Client.Helpers;
using DD.Persistence.Factories;
using System.Net;
namespace DD.Persistence.IntegrationTests; namespace DD.Persistence.IntegrationTests;
public class WebAppFactoryFixture : WebApplicationFactory<Program> public class WebAppFactoryFixture : WebApplicationFactory<Program>
@ -40,12 +43,12 @@ public class WebAppFactoryFixture : WebApplicationFactory<Program>
services.AddLogging(builder => builder.AddConsole()); services.AddLogging(builder => builder.AddConsole());
services.RemoveAll<IHttpClientFactory>(); services.RemoveAll<IHttpClientFactory>();
services.AddSingleton<IHttpClientFactory>(provider => services.AddSingleton<IHttpClientFactory>((provider) =>
{ {
return new TestHttpClientFactory(this); return new TestHttpClientFactory(this, provider.GetRequiredService<IConfiguration>());
}); });
services.AddHttpClient();
services.AddSingleton<PersistenceClientFactory>(); services.AddPersistenceClients();
var serviceProvider = services.BuildServiceProvider(); var serviceProvider = services.BuildServiceProvider();

View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!--Генерация NuGet пакета при сборке-->
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<!--Наименование-->
<Title>DD.Persistence.Models</Title>
<!--Версия пакета-->
<VersionPrefix>1.3.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</VersionPrefix>
<!--Версия сборки-->
<AssemblyVersion>1.3.$([System.DateTime]::UtcNow.ToString(yyMM.ddHH))</AssemblyVersion>
<!--Id пакета-->
<PackageId>DD.Persistence.Models</PackageId>
<!--Автор-->
<Authors>Digital Drilling</Authors>
<!--Компания-->
<Company>Digital Drilling</Company>
<!--Описание-->
<Description>Пакет для получения dtos для работы с Persistence сервисом</Description>
<!--Url репозитория-->
<RepositoryUrl>https://git.ddrilling.ru/on.nemtina/persistence.git</RepositoryUrl>
<!--тип репозитория-->
<RepositoryType>git</RepositoryType>
<!--Символы отладки-->
<IncludeSymbols>true</IncludeSymbols>
<!--Формат пакета с символами-->
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!--Путь к пакету-->
<PackageOutputPath>C:\Projects\Nuget\Persistence</PackageOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
namespace DD.Persistence.Models;
/// <summary>
/// Схема для набора данных
/// </summary>
public class DataSchemeDto
{
/// <summary>
/// Дискриминатор
/// </summary>
public Guid DiscriminatorId { get; set; }
/// <summary>
/// Наименования полей
/// </summary>
public string[] PropNames { get; set; } = [];
}

View File

@ -13,7 +13,7 @@ public class DataSourceSystemDto
/// <summary> /// <summary>
/// Наименование /// Наименование
/// </summary> /// </summary>
public required string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Описание /// Описание

View File

@ -1,4 +1,4 @@
namespace DD.Persistence.Models; namespace DD.Persistence.Models.Common;
/// <summary> /// <summary>
/// Диапазон дат /// Диапазон дат

View File

@ -1,12 +1,12 @@
namespace DD.Persistence.Models; namespace DD.Persistence.ModelsAbstractions;
/// <summary> /// <summary>
/// Интерфейс, описывающий временные данные /// Интерфейс, описывающий временные данные
/// </summary> /// </summary>
public interface ITimeSeriesAbstractDto public interface ITimestampAbstractDto
{ {
/// <summary> /// <summary>
/// временная отметка /// временная отметка
/// </summary> /// </summary>
DateTimeOffset Date { get; set; } DateTimeOffset Timestamp { get; set; }
} }

View File

@ -1,4 +1,4 @@
namespace DD.Persistence.Models; namespace DD.Persistence.ModelsAbstractions;
public interface IWithSectionPart public interface IWithSectionPart
{ {
public double DepthStart { get; set; } public double DepthStart { get; set; }

View File

@ -1,4 +1,4 @@
namespace DD.Persistence.Models; namespace DD.Persistence.Models.Common;
/// <summary> /// <summary>
/// Контейнер для поддержки постраничного просмотра таблиц /// Контейнер для поддержки постраничного просмотра таблиц

View File

@ -8,7 +8,7 @@ public class SetpointLogDto : SetpointValueDto
/// <summary> /// <summary>
/// Дата сохранения уставки /// Дата сохранения уставки
/// </summary> /// </summary>
public DateTimeOffset Created { get; set; } public DateTimeOffset Timestamp { get; set; }
/// <summary> /// <summary>
/// Ключ пользователя /// Ключ пользователя

View File

@ -0,0 +1,19 @@
using DD.Persistence.ModelsAbstractions;
namespace DD.Persistence.Models;
/// <summary>
/// Набор данных с отметкой времени
/// </summary>
public class TimestampedValuesDto : ITimestampAbstractDto
{
/// <summary>
/// Временная отметка
/// </summary>
public DateTimeOffset Timestamp { get; set; }
/// <summary>
/// Набор данных
/// </summary>
public Dictionary<string, object> Values { get; set; } = [];
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="Testcontainers" Version="4.1.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.1.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.extensibility.core" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
<ProjectReference Include="..\DD.Persistence.Repository\DD.Persistence.Repository.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,32 @@

using DD.Persistence.Database;
using DD.Persistence.Database.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Testcontainers.PostgreSql;
using Xunit;
namespace DD.Persistence.Repository.Test;
public class RepositoryTestFixture : IAsyncLifetime
{
public readonly PostgreSqlContainer dbContainer = new PostgreSqlBuilder().Build();
public PersistencePostgresContext GetDbContext() => new(new DbContextOptionsBuilder<PersistencePostgresContext>()
.UseNpgsql(dbContainer.GetConnectionString()).Options);
public IMemoryCache GetMemoryCache() => new MemoryCache(new MemoryCacheOptions());
public virtual async Task InitializeAsync()
{
await dbContainer.StartAsync();
var forumDbContext = new PersistencePostgresContext(new DbContextOptionsBuilder<PersistencePostgresContext>()
.UseNpgsql(dbContainer.GetConnectionString()).Options);
await forumDbContext.Database.MigrateAsync();
}
public async Task DisposeAsync() => await dbContainer.DisposeAsync();
}

View File

@ -0,0 +1,56 @@
using DD.Persistence.Database.Model;
using DD.Persistence.Repository.Repositories;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace DD.Persistence.Repository.Test;
public class SetpointRepositoryShould : IClassFixture<RepositoryTestFixture>
{
private readonly RepositoryTestFixture fixture;
private readonly PersistencePostgresContext context;
private readonly SetpointRepository sut;
public SetpointRepositoryShould(RepositoryTestFixture fixture)
{
this.fixture = fixture;
context = fixture.GetDbContext();
sut = new SetpointRepository(context);
}
[Fact]
public async Task ReturnValueKindNumber()
{
var id = Guid.NewGuid();
var value = GetJsonFromObject(22);
await sut.Add(id, value, Guid.NewGuid(), CancellationToken.None);
var t = fixture.dbContainer.GetConnectionString();
//act
var result = await sut.GetCurrent([id], CancellationToken.None);
//assert
result.ShouldNotBeNull();
result.ShouldNotBeEmpty();
var setpoint = result.First();
setpoint.Value.ShouldNotBeNull();
setpoint
.Value.ShouldBeOfType<JsonElement>()
.ValueKind.ShouldBe(JsonValueKind.Number);
}
private JsonElement GetJsonFromObject(object value)
{
var jsonString = JsonSerializer.Serialize(value);
var doc = JsonDocument.Parse(jsonString);
return doc.RootElement;
}
}

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