Merge branch 'dev' into feature/detected_operations

# Conflicts:
#	AsbCloudInfrastructure/DependencyInjection.cs
This commit is contained in:
Степанов Дмитрий 2023-11-15 09:37:31 +05:00
commit 23e8615e8a
142 changed files with 57309 additions and 1354 deletions

View File

@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudApp", "AsbCloudApp\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudInfrastructure", "AsbCloudInfrastructure\AsbCloudInfrastructure.csproj", "{67DBFC52-BAE4-4903-827A-AD0288C292B6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{D04A84E7-5F08-4042-8FB5-476EE49E9D22}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudDb", "AsbCloudDb\AsbCloudDb.csproj", "{40FBD29B-724B-4496-B5D9-1A5D14102456}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsbCloudWebApi.Tests", "AsbCloudWebApi.Tests\AsbCloudWebApi.Tests.csproj", "{9CF6FBB1-9AF5-45AB-A521-24F11A79B540}"
@ -35,10 +33,6 @@ Global
{67DBFC52-BAE4-4903-827A-AD0288C292B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67DBFC52-BAE4-4903-827A-AD0288C292B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67DBFC52-BAE4-4903-827A-AD0288C292B6}.Release|Any CPU.Build.0 = Release|Any CPU
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D04A84E7-5F08-4042-8FB5-476EE49E9D22}.Release|Any CPU.Build.0 = Release|Any CPU
{40FBD29B-724B-4496-B5D9-1A5D14102456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40FBD29B-724B-4496-B5D9-1A5D14102456}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40FBD29B-724B-4496-B5D9-1A5D14102456}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@ -5,21 +5,10 @@ namespace AsbCloudApp.Data.AutogeneratedDailyReport;
/// <summary>
/// Базовая информация о суточном отчёте
/// </summary>
public class AutoGeneratedDailyReportInfoDto
public class AutoGeneratedDailyReportInfoDto : ReportInfoDto
{
/// <summary>
/// Дата формирования отчёта
/// </summary>
public DateOnly ReportDate { get; set; }
/// <summary>
/// Название файла
/// </summary>
public string FileName { get; set; } = null!;
/// <summary>
/// Размер файла
/// </summary>
public int FileSize { get; set; }
/// <summary>
/// Дата формирования отчёта
/// </summary>
public DateOnly ReportDate { get; set; }
}

View File

@ -172,7 +172,7 @@ namespace AsbCloudApp.Data
if (progress.HasValue)
CurrentState.Progress = progress.Value;
Trace.TraceInformation($"{WorkNameForTrace} state: {newState}");
Trace.TraceInformation($"{WorkNameForTrace} state[{100*progress:#}%]: {newState}");
}
/// <summary>

View File

@ -0,0 +1,26 @@
using AsbCloudApp.Data.SAUB;
using System;
namespace AsbCloudApp.Data.DrillTestReport
{
/// <summary>
/// Информация о drill test, выгружаемая в отчете
/// </summary>
public class DrillTestReportDataDto
{
/// <summary>
/// Данные для отчета
/// </summary>
public DrillTestDto Data { get; set; } = null!;
/// <summary>
/// Заголовок отчета
/// </summary>
public string Caption { get; set; } = null!;
/// <summary>
/// Дата отчета
/// </summary>
public DateTime Date { get; set; } = DateTime.Now;
}
}

View File

@ -0,0 +1,25 @@
using System;
namespace AsbCloudApp.Data.DrillTestReport
{
/// <summary>
/// Базовая информация о drill_test отчёте
/// </summary>
public class DrillTestReportInfoDto : ReportInfoDto
{
/// <summary>
/// Идентификатор отчета
/// </summary>
public int Id { get; set; }
/// <summary>
/// Проходка
/// </summary>
public float DrillDepth { get; set; }
/// <summary>
/// Дата и время
/// </summary>
public DateTime DateTime { get; set; }
}
}

View File

@ -0,0 +1,18 @@
namespace AsbCloudApp.Data
{
/// <summary>
/// Справочная информация об отчете
/// </summary>
public class ReportInfoDto
{
/// <summary>
/// Название файла
/// </summary>
public string FileName { get; set; } = null!;
/// <summary>
/// Размер файла
/// </summary>
public int FileSize { get; set; } = 0;
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudApp.Data.SAUB
{
/// <summary>
/// DTO для описания записи drill_test
/// </summary>
public class DrillTestDto
{
/// <summary>
/// Идентификатор drill test
/// </summary>
public int Id { get; set; }
/// <summary>
/// Время начала drill test
/// </summary>
public DateTimeOffset TimeStampStart { get; set; }
/// <summary>
/// Глубина начала drill test
/// </summary>
public float DepthStart { get; set; }
/// <summary>
/// Связанная с drill_test телеметрия
/// </summary>
public TelemetryDto? Telemetry { get; set; }
/// <summary>
/// Параметры теста
/// </summary>
public IEnumerable<DrillTestParamsDto> Params { get; set; } = Enumerable.Empty<DrillTestParamsDto>();
}
}

View File

@ -0,0 +1,38 @@
namespace AsbCloudApp.Data.SAUB
{
/// <summary>
/// Параметры Drill Test
/// </summary>
public class DrillTestParamsDto
{
/// <summary>
/// Шаг
/// </summary>
public int Step { get; set; }
/// <summary>
/// Нагрузка
/// </summary>
public float? Workload { get; set; }
/// <summary>
/// Заданная скорость
/// </summary>
public float? Speed { get; set; }
/// <summary>
/// Скорость проходки
/// </summary>
public float? DepthSpeed { get; set; }
/// <summary>
/// Время бурения шага, сек
/// </summary>
public float? TimeDrillStep { get; set; }
/// <summary>
/// Глубина бурения шага
/// </summary>
public float? DepthDrillStep { get; set; }
}
}

View File

@ -21,7 +21,7 @@ namespace AsbCloudApp.Data.SAUB
/// <summary>
/// id категории события
/// </summary>
[Range(1, int.MaxValue, ErrorMessage = "Id категории события не может быть отрицательным")]
[Range(0, int.MaxValue, ErrorMessage = "Id категории события не может быть отрицательным")]
public int IdCategory { get; set; }
/// <summary>
@ -32,7 +32,7 @@ namespace AsbCloudApp.Data.SAUB
/// <summary>
/// тип определения наступления события
/// </summary>
[Range(1, int.MaxValue, ErrorMessage = "Id типа события не может быть отрицательным")]
[Range(0, int.MaxValue, ErrorMessage = "Id типа события не может быть отрицательным")]
public int EventType { get; set; }
/// <summary>

View File

@ -44,4 +44,5 @@ public class SectionByOperationsDto
/// Дата после завершения последней операции операции в секции
/// </summary>
public DateTimeOffset DateEnd { get; set; }
public string Caption { get; set; }
}

View File

@ -10,7 +10,7 @@ public class WellboreDto
/// <summary>
/// Скважина
/// </summary>
public WellWithTimezoneDto Well { get; set; } = null!;
public WellDto Well { get; set; } = null!;
/// <summary>
/// Идентификатор

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudApp.Exceptions
{
@ -8,9 +10,9 @@ namespace AsbCloudApp.Exceptions
public class ArgumentInvalidException : Exception
{
/// <summary>
/// название аргумента
/// словарь с ошибками, где ключ - имя аргумента, а значение - массив из одного сообщения
/// </summary>
public string ParamName { get; } = string.Empty;
public IDictionary<string, string[]> ErrorState { get; } = null!;
/// <summary>
/// конструктор
@ -20,7 +22,20 @@ namespace AsbCloudApp.Exceptions
public ArgumentInvalidException(string paramName, string message)
: base(message)
{
ParamName = paramName;
ErrorState = new Dictionary<string, string[]>() {
{ paramName, new[]{ message } }
};
}
/// <summary>
/// конструктор
/// </summary>
/// <param name="paramsNames"></param>
/// <param name="message"></param>
public ArgumentInvalidException(string[] paramsNames, string message)
: base(message)
{
ErrorState = paramsNames.ToDictionary(paramName => paramName, item => new[] { message });
}
}
}

View File

@ -0,0 +1,41 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Repositories
{
/// <summary>
/// репозиторий по работе с данными drill_test
/// </summary>
public interface IDrillTestRepository
{
/// <summary>
/// Получить данные drill_test в соответствии с параметрами запроса
/// </summary>
/// <param name="idTelemetry">ключ телеметрии</param>
/// <param name="request">запрос</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<DrillTestDto>> GetAllAsync(int idTelemetry, FileReportRequest request, CancellationToken cancellationToken);
/// <summary>
/// Получить запись drill_test
/// </summary>
/// <param name="idTelemetry">ключ телеметрии</param>
/// <param name="id">ключ записи drill_test</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<DrillTestDto> GetAsync(int idTelemetry, int id, CancellationToken cancellationToken);
/// <summary>
/// Сохранить данные drill_test
/// </summary>
/// <param name="idTelemetry">ключ телеметрии</param>
/// <param name="dto">запись drill test</param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> SaveDataAsync(int idTelemetry, DrillTestDto dto, CancellationToken token);
}
}

View File

@ -0,0 +1,69 @@
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using System;
using System.Collections.Generic;
namespace AsbCloudApp.Repositories
{
/// <summary>
/// Хранилище кеша
/// </summary>
/// <typeparam name="TDto"></typeparam>
public interface ITelemetryDataCache<TDto> where TDto : ITelemetryData
{
/// <summary>
/// добавить в кеш чанк записей по телеметрии
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="range"></param>
void AddRange(int idTelemetry, IEnumerable<TDto> range);
/// <summary>
/// вернуть последнюю записть
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
TDto? GetLastOrDefault(int idTelemetry);
/// <summary>
/// Получить кешированые записи
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="dateBegin"></param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount">приблизительное кол-во возвращаемых записей после их прореживания</param>
/// <returns></returns>
IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600, int approxPointsCount = 1024);
/// <summary>
/// Получить кешированые записи
/// </summary>
/// <param name="idTelemetry"></param>
/// <param name="request"></param>
/// <returns></returns>
IEnumerable<TDto>? GetOrDefault(int idTelemetry, TelemetryDataRequest request);
/// <summary>
/// Диапазон дат находящийся в кеше
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
DatesRangeDto? GetOrDefaultCachedaDateRange(int idTelemetry);
/// <summary>
/// Получить диапазон дат телеметрии.
/// Дата первой записи телеметрии храниться отдельно и запоняется при инициализации
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
DatesRangeDto? GetOrDefaultDataDateRange(int idTelemetry);
/// <summary>
/// Получение первой и последней записи телеметрии.
/// Первая запись телеметрии храниться отдельно и запоняется при инициализации
/// </summary>
/// <param name="idTelemetry"></param>
/// <returns></returns>
(TDto First, TDto Last)? GetOrDefaultFirstLast(int idTelemetry);
}
}

View File

@ -1,19 +0,0 @@
using System;
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса для получения авто-генерируемых суточных отчётов
/// </summary>
public class AutoGeneratedDailyReportRequest : RequestBase
{
/// <summary>
/// Дата начала периода
/// </summary>
public DateOnly? StartDate { get; set; }
/// <summary>
/// Дата конца периода
/// </summary>
public DateOnly? FinishDate { get; set; }
}

View File

@ -0,0 +1,19 @@
using System;
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса для получения отчетов (файлов)
/// </summary>
public class FileReportRequest : RequestBase
{
/// <summary>
/// Дата начала периода
/// </summary>
public DateOnly? GeDate { get; set; }
/// <summary>
/// Дата конца периода
/// </summary>
public DateOnly? LeDate { get; set; }
}

View File

@ -84,7 +84,7 @@ namespace AsbCloudApp.Requests
{
if (LtDepth < GtDepth)
yield return new ValidationResult(
$"{nameof(LtDepth)} должно быть больше {nameof(GtDepth)}. ({LtDepth:O} < {GtDepth:O})",
$"{nameof(LtDepth)} должно быть больше {nameof(GtDepth)}. ({LtDepth} < {GtDepth})",
new[] { nameof(LtDepth), nameof(GtDepth) });
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса для ствола скважины
/// </summary>
public class WellboreRequest : RequestBase
{
/// <summary>
/// Пары идентификаторов скважины и секции
/// </summary>
public IEnumerable<(int idWell, int? idSection)> Ids { get; set; } = null!;
}

View File

@ -1,20 +0,0 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.AutogeneratedDailyReport;
namespace AsbCloudApp.Services.AutoGeneratedDailyReports;
/// <summary>
/// Сервис для генерации файлов авто-генерируемых суточный отчётов
/// </summary>
public interface IAutoGeneratedDailyReportMakerService
{
/// <summary>
/// Генерация файла
/// </summary>
/// <param name="report"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<Stream> MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken);
}

View File

@ -21,7 +21,7 @@ public interface IAutoGeneratedDailyReportService
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PaginationContainer<AutoGeneratedDailyReportInfoDto>> GetListAsync(int idWell,
AutoGeneratedDailyReportRequest request,
FileReportRequest request,
CancellationToken cancellationToken);
/// <summary>

View File

@ -0,0 +1,35 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DrillTestReport;
using AsbCloudApp.Requests;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services
{
/// <summary>
/// сервис по работе с отчетами drill test
/// </summary>
public interface IDrillTestReportService
{
/// <summary>
/// Список файлов drill test
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="request">параметры запроса</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PaginationContainer<DrillTestReportInfoDto>> GetListAsync(int idWell,
FileReportRequest request,
CancellationToken cancellationToken);
/// <summary>
/// Генерация файла с отчётом
/// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="id">ключ drill test записи</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<(string fileName, Stream stream)> GenerateAsync(int idWell, int id, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,19 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services;
/// <summary>
/// Сервис для генерации файлов отчётов
/// </summary>
public interface IReportMakerService<T>
{
/// <summary>
/// Генерация файла
/// </summary>
/// <param name="report">модель с данными для построения отчета</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<Stream> MakeReportAsync(T report, CancellationToken cancellationToken);
}

View File

@ -57,5 +57,13 @@ namespace AsbCloudApp.Services
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<ReportPropertiesDto>> GetAllReportsByWellAsync(int idWell, CancellationToken token);
/// <summary>
/// Удаление отчетов, если превышен их период хранения
/// </summary>
/// <param name="lifetime">период хранения отчетов</param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> DeleteAllOldReportsAsync(TimeSpan lifetime, CancellationToken token);
}
}

View File

@ -26,17 +26,32 @@ namespace AsbCloudApp.Services
Task<IEnumerable<TDto>> GetAsync(int idWell,
DateTime dateBegin = default, double intervalSec = 600d,
int approxPointsCount = 1024, CancellationToken token = default);
/// <summary>
/// Получить данные тех. процесса
/// </summary>
/// <param name="idWell"></param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TDto>> GetAsync(int idWell, TelemetryDataRequest request, CancellationToken token);
/// <summary>
/// Получение статистики за период
/// Период за который есть данные по скважине в рамках временного интервала
/// </summary>
/// <param name="idWell"></param>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="geDate"></param>
/// <param name="leDate"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token);
Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token);
/// <summary>
/// Период за который есть данные по скважине
/// </summary>
/// <param name="idWell"></param>
/// <returns></returns>
DatesRangeDto? GetRange(int idWell);
/// <summary>
/// добавить/изменить данные тех. процесса (используется панелью)

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
namespace AsbCloudApp.Services;
@ -11,20 +10,11 @@ namespace AsbCloudApp.Services;
/// </summary>
public interface IWellboreService
{
/// <summary>
/// Получение ствола скважины
/// </summary>
/// <param name="idWell"></param>
/// <param name="idSection"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<WellboreDto?> GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken);
/// <summary>
/// Получение стволов скважин
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<WellboreDto>> GetWellboresAsync(WellboreRequest request, CancellationToken cancellationToken);
/// <summary>
/// Получение стволов скважин
/// </summary>
/// <param name="idsWells"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<WellboreDto>> GetWellboresAsync(IEnumerable<int> idsWells, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace AsbCloudDb
{
public static class EFExtentionsInnitialization
{
public static void EnshureCreatedAndMigrated(this DatabaseFacade db)
{
db.SetCommandTimeout(TimeSpan.FromMinutes(5));
if (db.EnsureCreated())
{
db.CreateMigrationTable();
db.WriteMigrationsInfo();
}
else
{
db.SetCommandTimeout(TimeSpan.FromMinutes(20));
db.Migrate();
}
}
private static void CreateMigrationTable(this DatabaseFacade db)
{
var sqlCreateMigrationTable =
$"CREATE TABLE public.\"__EFMigrationsHistory\" " +
$"(\"MigrationId\" varchar(150) NOT NULL, " +
$" \"ProductVersion\" varchar(32) NOT NULL, " +
$" CONSTRAINT \"PK___EFMigrationsHistory\" PRIMARY KEY (\"MigrationId\"));";
db.ExecuteSqlRaw(sqlCreateMigrationTable);
}
private static void WriteMigrationsInfo(this DatabaseFacade db)
{
var efVersion = db.GetType().Assembly.GetName().Version!;
var efVersionString = $"{efVersion.Major}.{efVersion.Minor}.{efVersion.Build}";
var migrations = db.GetPendingMigrations()
.Select(migration => $" ('{migration}', '{efVersionString}')");
var sqlAddLastMigration =
$"INSERT INTO public.\"__EFMigrationsHistory\" " +
$"(\"MigrationId\", \"ProductVersion\") " +
$"VALUES {string.Join(',', migrations)};";
db.ExecuteSqlRaw(sqlAddLastMigration);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Drill_Test : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "t_drill_test",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор"),
id_telemetry = table.Column<int>(type: "integer", nullable: false, comment: "Идентификатор телеметрии"),
timestamp_start = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Время начала"),
depthStart = table.Column<float>(type: "real", nullable: false, comment: "Глубина начала"),
t_drill_test_params = table.Column<string>(type: "jsonb", nullable: false, comment: "Параметры записи drill test")
},
constraints: table =>
{
table.PrimaryKey("PK_t_drill_test", x => new { x.id, x.id_telemetry });
table.ForeignKey(
name: "FK_t_drill_test_t_telemetry_id_telemetry",
column: x => x.id_telemetry,
principalTable: "t_telemetry",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
},
comment: "Drill_test");
migrationBuilder.CreateIndex(
name: "IX_t_drill_test_id_telemetry",
table: "t_drill_test",
column: "id_telemetry");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "t_drill_test");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Update_EntityFillerSubsystem : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_subsystem",
keyColumn: "id",
keyValue: 65536,
columns: new[] { "description", "name" },
values: new object[] { "Осцилляция", "Осцилляция" });
migrationBuilder.UpdateData(
table: "t_subsystem",
keyColumn: "id",
keyValue: 65537,
columns: new[] { "description", "name" },
values: new object[] { "Демпфер", "Демпфер" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_subsystem",
keyColumn: "id",
keyValue: 65536,
columns: new[] { "description", "name" },
values: new object[] { "Spin master", "Spin master" });
migrationBuilder.UpdateData(
table: "t_subsystem",
keyColumn: "id",
keyValue: 65537,
columns: new[] { "description", "name" },
values: new object[] { "Torque master", "Torque master" });
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Rename_Field_IsContact_In_CompanyType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "IsContact",
table: "t_company_type",
newName: "is_contact");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "is_contact",
table: "t_company_type",
newName: "IsContact");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Or_Update_Data_In_CompanyType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 1,
columns: new[] { "caption", "order" },
values: new object[] { "Недропользователь", 3 });
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 2,
columns: new[] { "is_contact", "order" },
values: new object[] { true, 2 });
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 3,
columns: new[] { "is_contact", "order" },
values: new object[] { true, 0 });
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (4, 'Сервис по ГТИ', true, 6) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по ГТИ', is_contact=true, ""order""=6;");
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (5, 'Растворный сервис', true, 4) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Растворный сервис', is_contact=true, ""order""=4;");
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (6, 'Сервис по ННБ', true, 5) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по ННБ', is_contact=true, ""order""=5;");
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (7, 'Служба супервайзинга', true, 1) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Служба супервайзинга', is_contact=true, ""order""=1;");
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (9, 'Сервис по цементированию', true, 7) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по цементированию', is_contact=true, ""order""=7;");
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (11, 'Дизельный сервис', false, 9) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Дизельный сервис', is_contact=false, ""order""=9;");
migrationBuilder.Sql(@"INSERT INTO public.t_company_type (id, caption, is_contact, ""order"") " +
@"VALUES (12, 'Сервис по обслуживанию верхних силовых приводов', true, 8) " +
@"ON CONFLICT (id) DO UPDATE SET caption='Сервис по обслуживанию верхних силовых приводов', is_contact=false, ""order""=8;");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "t_company_type",
keyColumn: "id",
keyValue: 4);
migrationBuilder.DeleteData(
table: "t_company_type",
keyColumn: "id",
keyValue: 5);
migrationBuilder.DeleteData(
table: "t_company_type",
keyColumn: "id",
keyValue: 6);
migrationBuilder.DeleteData(
table: "t_company_type",
keyColumn: "id",
keyValue: 7);
migrationBuilder.DeleteData(
table: "t_company_type",
keyColumn: "id",
keyValue: 9);
migrationBuilder.DeleteData(
table: "t_company_type",
keyColumn: "id",
keyValue: 12);
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 1,
columns: new[] { "caption", "order" },
values: new object[] { "Недрапользователь", 1 });
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Data_To_WellSectionType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "t_well_section_type",
columns: new[] { "id", "caption", "order" },
values: new object[,]
{
{ 34, "Хвостовик 6", 6.5f },
{ 35, "Хвостовик 7", 6.6f },
{ 36, "Хвостовик 8", 6.7f },
{ 37, "Хвостовик 9", 6.8f },
{ 38, "Хвостовик 10", 6.9f }
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 34);
migrationBuilder.DeleteData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 35);
migrationBuilder.DeleteData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 36);
migrationBuilder.DeleteData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 37);
migrationBuilder.DeleteData(
table: "t_well_section_type",
keyColumn: "id",
keyValue: 38);
}
}
}

View File

@ -0,0 +1,224 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class UpdateTable_t_telemetry_data_Set_ImportantColumns_NotNull : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM t_telemetry_data_saub WHERE " +
"well_depth IS NULL OR " +
"rotor_torque IS NULL OR " +
"rotor_speed IS NULL OR " +
"pressure IS NULL OR " +
"mode IS NULL OR " +
"hook_weight IS NULL OR " +
"block_position IS NULL OR " +
"bit_depth IS NULL OR " +
"axial_load IS NULL;");
migrationBuilder.AlterColumn<float>(
name: "well_depth",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Глубина забоя",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Глубина забоя");
migrationBuilder.AlterColumn<float>(
name: "rotor_torque",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Момент на роторе",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Момент на роторе");
migrationBuilder.AlterColumn<float>(
name: "rotor_speed",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Обороты ротора",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Обороты ротора");
migrationBuilder.AlterColumn<float>(
name: "pressure",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Давление",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Давление");
migrationBuilder.AlterColumn<short>(
name: "mode",
table: "t_telemetry_data_saub",
type: "smallint",
nullable: false,
defaultValue: (short)0,
comment: "Режим САУБ",
oldClrType: typeof(short),
oldType: "smallint",
oldNullable: true,
oldComment: "Режим САУБ");
migrationBuilder.AlterColumn<float>(
name: "hook_weight",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Вес на крюке",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Вес на крюке");
migrationBuilder.AlterColumn<float>(
name: "block_position",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Высота талевого блока",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Высота талевого блока");
migrationBuilder.AlterColumn<float>(
name: "bit_depth",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Положение инструмента",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Положение инструмента");
migrationBuilder.AlterColumn<float>(
name: "axial_load",
table: "t_telemetry_data_saub",
type: "real",
nullable: false,
defaultValue: 0f,
comment: "Осевая нагрузка",
oldClrType: typeof(float),
oldType: "real",
oldNullable: true,
oldComment: "Осевая нагрузка");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<float>(
name: "well_depth",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Глубина забоя",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Глубина забоя");
migrationBuilder.AlterColumn<float>(
name: "rotor_torque",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Момент на роторе",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Момент на роторе");
migrationBuilder.AlterColumn<float>(
name: "rotor_speed",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Обороты ротора",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Обороты ротора");
migrationBuilder.AlterColumn<float>(
name: "pressure",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Давление",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Давление");
migrationBuilder.AlterColumn<short>(
name: "mode",
table: "t_telemetry_data_saub",
type: "smallint",
nullable: true,
comment: "Режим САУБ",
oldClrType: typeof(short),
oldType: "smallint",
oldComment: "Режим САУБ");
migrationBuilder.AlterColumn<float>(
name: "hook_weight",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Вес на крюке",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Вес на крюке");
migrationBuilder.AlterColumn<float>(
name: "block_position",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Высота талевого блока",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Высота талевого блока");
migrationBuilder.AlterColumn<float>(
name: "bit_depth",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Положение инструмента",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Положение инструмента");
migrationBuilder.AlterColumn<float>(
name: "axial_load",
table: "t_telemetry_data_saub",
type: "real",
nullable: true,
comment: "Осевая нагрузка",
oldClrType: typeof(float),
oldType: "real",
oldComment: "Осевая нагрузка");
}
}
}

View File

@ -120,7 +120,8 @@ namespace AsbCloudDb.Migrations
.HasColumnName("caption");
b.Property<bool>("IsContact")
.HasColumnType("boolean");
.HasColumnType("boolean")
.HasColumnName("is_contact");
b.Property<int>("Order")
.HasColumnType("integer")
@ -134,9 +135,9 @@ namespace AsbCloudDb.Migrations
new
{
Id = 1,
Caption = "Недрапользователь",
Caption = "Недропользователь",
IsContact = false,
Order = 1
Order = 3
},
new
{
@ -151,6 +152,48 @@ namespace AsbCloudDb.Migrations
Caption = "Сервис автоматизации бурения",
IsContact = false,
Order = 0
},
new
{
Id = 4,
Caption = "Сервис по ГТИ",
IsContact = true,
Order = 6
},
new
{
Id = 5,
Caption = "Растворный сервис",
IsContact = true,
Order = 4
},
new
{
Id = 6,
Caption = "Сервис по ННБ",
IsContact = true,
Order = 5
},
new
{
Id = 7,
Caption = "Служба супервайзинга",
IsContact = true,
Order = 1
},
new
{
Id = 9,
Caption = "Сервис по цементированию",
IsContact = true,
Order = 7
},
new
{
Id = 12,
Caption = "Сервис по обслуживанию верхних силовых приводов",
IsContact = true,
Order = 7
});
});
@ -413,6 +456,43 @@ namespace AsbCloudDb.Migrations
b.HasComment("части программ бурения");
});
modelBuilder.Entity("AsbCloudDb.Model.DrillTest", b =>
{
b.Property<int>("Id")
.HasColumnType("integer")
.HasColumnName("id")
.HasComment("Идентификатор");
b.Property<int>("IdTelemetry")
.HasColumnType("integer")
.HasColumnName("id_telemetry")
.HasComment("Идентификатор телеметрии");
b.Property<float>("DepthStart")
.HasColumnType("real")
.HasColumnName("depthStart")
.HasComment("Глубина начала");
b.Property<string>("Params")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("t_drill_test_params")
.HasComment("Параметры записи drill test");
b.Property<DateTimeOffset>("TimeStampStart")
.HasColumnType("timestamp with time zone")
.HasColumnName("timestamp_start")
.HasComment("Время начала");
b.HasKey("Id", "IdTelemetry");
b.HasIndex("IdTelemetry");
b.ToTable("t_drill_test");
b.HasComment("Drill_test");
});
modelBuilder.Entity("AsbCloudDb.Model.Faq", b =>
{
b.Property<int>("Id")
@ -4531,14 +4611,14 @@ namespace AsbCloudDb.Migrations
new
{
Id = 65536,
Description = "Spin master",
Name = "Spin master"
Description = "Осцилляция",
Name = "Осцилляция"
},
new
{
Id = 65537,
Description = "Torque master",
Name = "Torque master"
Description = "Демпфер",
Name = "Демпфер"
});
});
@ -4638,7 +4718,7 @@ namespace AsbCloudDb.Migrations
.HasColumnName("date")
.HasComment("'2021-10-19 18:23:54+05'");
b.Property<float?>("AxialLoad")
b.Property<float>("AxialLoad")
.HasColumnType("real")
.HasColumnName("axial_load")
.HasComment("Осевая нагрузка");
@ -4653,12 +4733,12 @@ namespace AsbCloudDb.Migrations
.HasColumnName("axial_load_sp")
.HasComment("Осевая нагрузка. Задание");
b.Property<float?>("BitDepth")
b.Property<float>("BitDepth")
.HasColumnType("real")
.HasColumnName("bit_depth")
.HasComment("Положение инструмента");
b.Property<float?>("BlockPosition")
b.Property<float>("BlockPosition")
.HasColumnType("real")
.HasColumnName("block_position")
.HasComment("Высота талевого блока");
@ -4713,7 +4793,7 @@ namespace AsbCloudDb.Migrations
.HasColumnName("flow_idle")
.HasComment("Расход. Холостой ход");
b.Property<float?>("HookWeight")
b.Property<float>("HookWeight")
.HasColumnType("real")
.HasColumnName("hook_weight")
.HasComment("Вес на крюке");
@ -4743,7 +4823,7 @@ namespace AsbCloudDb.Migrations
.HasColumnName("id_user")
.HasComment("Пользователь САУБ");
b.Property<short?>("Mode")
b.Property<short>("Mode")
.HasColumnType("smallint")
.HasColumnName("mode")
.HasComment("Режим САУБ");
@ -4758,7 +4838,7 @@ namespace AsbCloudDb.Migrations
.HasColumnName("mse_state")
.HasComment("Текущее состояние работы MSE");
b.Property<float?>("Pressure")
b.Property<float>("Pressure")
.HasColumnType("real")
.HasColumnName("pressure")
.HasComment("Давление");
@ -4808,12 +4888,12 @@ namespace AsbCloudDb.Migrations
.HasColumnName("pump2_flow")
.HasComment("Расход. Буровой насос 3");
b.Property<float?>("RotorSpeed")
b.Property<float>("RotorSpeed")
.HasColumnType("real")
.HasColumnName("rotor_speed")
.HasComment("Обороты ротора");
b.Property<float?>("RotorTorque")
b.Property<float>("RotorTorque")
.HasColumnType("real")
.HasColumnName("rotor_torque")
.HasComment("Момент на роторе");
@ -4833,7 +4913,7 @@ namespace AsbCloudDb.Migrations
.HasColumnName("rotor_torque_sp")
.HasComment("Момент на роторе. Задание");
b.Property<float?>("WellDepth")
b.Property<float>("WellDepth")
.HasColumnType("real")
.HasColumnName("well_depth")
.HasComment("Глубина забоя");
@ -7022,6 +7102,36 @@ namespace AsbCloudDb.Migrations
Id = 33,
Caption = "Техническая колонна 3",
Order = 2.2f
},
new
{
Id = 34,
Caption = "Хвостовик 6",
Order = 6.5f
},
new
{
Id = 35,
Caption = "Хвостовик 7",
Order = 6.6f
},
new
{
Id = 36,
Caption = "Хвостовик 8",
Order = 6.7f
},
new
{
Id = 37,
Caption = "Хвостовик 9",
Order = 6.8f
},
new
{
Id = 38,
Caption = "Хвостовик 10",
Order = 6.9f
});
});
@ -7846,6 +7956,17 @@ namespace AsbCloudDb.Migrations
b.Navigation("Well");
});
modelBuilder.Entity("AsbCloudDb.Model.DrillTest", b =>
{
b.HasOne("AsbCloudDb.Model.Telemetry", "Telemetry")
.WithMany()
.HasForeignKey("IdTelemetry")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Telemetry");
});
modelBuilder.Entity("AsbCloudDb.Model.Faq", b =>
{
b.HasOne("AsbCloudDb.Model.User", "AuthorAnswer")

View File

@ -84,6 +84,7 @@ namespace AsbCloudDb.Model
public DbSet<ManualDirectory> ManualDirectories => Set<ManualDirectory>();
public DbSet<Contact> Contacts => Set<Contact>();
public DbSet<DrillTest> DrillTests => Set<DrillTest>();
public AsbCloudDbContext() : base()
{
Interlocked.Increment(ref referenceCount);
@ -103,7 +104,7 @@ namespace AsbCloudDb.Model
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True"
optionsBuilder.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True;Include Error Detail=True;"
//, builder=>builder.EnableRetryOnFailure(2, System.TimeSpan.FromMinutes(1))
);
}
@ -411,6 +412,15 @@ namespace AsbCloudDb.Model
.HasForeignKey(m => m.IdDirectory)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DrillTest>()
.HasKey(m => new { m.Id, m.IdTelemetry });
modelBuilder.Entity<DrillTest>(entity =>
{
entity.Property(e => e.Params)
.HasJsonConversion();
});
DefaultData.DefaultContextData.Fill(modelBuilder);
}

View File

@ -14,6 +14,8 @@ namespace AsbCloudDb.Model
[Column("caption")]
[StringLength(255)]
public string Caption { get; set; } = null!;
[Column("is_contact")]
public bool IsContact { get; set; }
[Column("order")]

View File

@ -3,9 +3,16 @@
internal class EntityFillerCompanyType : EntityFiller<CompanyType>
{
public override CompanyType[] GetData() => new CompanyType[] {
new (){ Id = 1, Caption = "Недрапользователь", Order = 1 },
new (){ Id = 2, Caption = "Буровой подрядчик", Order = 2 },
new (){ Id = 3, Caption = "Сервис автоматизации бурения", Order = 0 }
new (){ Id = 1, Caption = "Недропользователь", IsContact = true, Order = 3 },
new (){ Id = 2, Caption = "Буровой подрядчик", IsContact = true, Order = 2 },
new (){ Id = 3, Caption = "Сервис автоматизации бурения", IsContact = true, Order = 0 },
new (){ Id = 4, Caption = "Сервис по ГТИ", IsContact = true, Order = 6 },
new (){ Id = 5, Caption = "Растворный сервис", IsContact = true, Order = 4 },
new (){ Id = 6, Caption = "Сервис по ННБ", IsContact = true, Order = 5 },
new (){ Id = 7, Caption = "Служба супервайзинга", Order = 1 },
new (){ Id = 9, Caption = "Сервис по цементированию", IsContact = true, Order = 7 },
new (){ Id = 11, Caption = "Дизельный сервис", IsContact = false, Order = 9 },
new (){ Id = 12, Caption = "Сервис по обслуживанию верхних силовых приводов", IsContact = true, Order = 8 },
};
}
}

View File

@ -161,6 +161,8 @@
new() { Id = 527, Name = "Manual.delete", Description = "Разрешение на удаление инструкций"},
new (){ Id = 528, Name="WellContact.delete", Description="Разрешение на удаление контакта"},
new (){ Id = 529, Name="DrillTestReport.get", Description="Разрешение на получение отчетов drill test"},
};
}
}

View File

@ -10,8 +10,8 @@ namespace AsbCloudDb.Model.DefaultData
new () {Id = 12, Name = "АПД слайд", Description = "Режим работы \"Бурение в слайде\""},
new () {Id = 2, Name = "MSE", Description = "Алгоритм поиска оптимальных параметров бурения САУБ"},
//Spin master - id подсистем с 65_536 до 131_071
new () {Id = 65536, Name = "Spin master", Description = "Spin master"},
new () {Id = 65537, Name = "Torque master", Description = "Torque master"}
new () {Id = 65536, Name = "Осцилляция", Description = "Осцилляция"},
new () {Id = 65537, Name = "Демпфер", Description = "Демпфер"}
};
}
}

View File

@ -42,6 +42,12 @@
new (){ Id = 31, Caption = "Техническая колонна", Order = 2},
new (){ Id = 32, Caption = "Техническая колонна 2", Order = 2.1f},
new (){ Id = 33, Caption = "Техническая колонна 3", Order = 2.2f},
new (){ Id = 34, Caption = "Хвостовик 6", Order = 6.5f},
new (){ Id = 35, Caption = "Хвостовик 7", Order = 6.6f},
new (){ Id = 36, Caption = "Хвостовик 8", Order = 6.7f},
new (){ Id = 37, Caption = "Хвостовик 9", Order = 6.8f},
new (){ Id = 38, Caption = "Хвостовик 10", Order = 6.9f},
};
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace AsbCloudDb.Model
{
[Table("t_drill_test"), Comment("Drill_test")]
public class DrillTest
{
/// <summary>
/// Идентификатор drill test
/// </summary>
[Key, Column("id"), Comment("Идентификатор")]
public int Id { get; set; }
/// <summary>
/// Идентификатор телеметрии drill test
/// </summary>
[Key, Column("id_telemetry"), Comment("Идентификатор телеметрии")]
public int IdTelemetry { get; set; }
/// <summary>
/// Время начала drill test
/// </summary>
[Column("timestamp_start", TypeName = "timestamp with time zone"), Comment("Время начала")]
public DateTimeOffset TimeStampStart { get; set; }
/// <summary>
/// Глубина начала drill test
/// </summary>
[Column("depthStart"), Comment("Глубина начала")]
public float DepthStart { get; set; }
[ForeignKey(nameof(IdTelemetry))]
public virtual Telemetry Telemetry { get; set; } = null!;
[Column("t_drill_test_params", TypeName = "jsonb"), Comment("Параметры записи drill test")]
public virtual ICollection<DrillTestParameter> Params { get; set; } = null!;
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace AsbCloudDb.Model
{
/// <summary>
/// Параметры записи drill test
/// </summary>
public class DrillTestParameter
{
/// <summary>
/// Шаг
/// </summary>
public int Step { get; set; }
/// <summary>
/// Нагрузка
/// </summary>
public float? Workload { get; set; }
/// <summary>
/// Заданная скорость
/// </summary>
public float? Speed { get; set; }
/// <summary>
/// Скорость проходки
/// </summary>
public float? DepthSpeed { get; set; }
/// <summary>
/// Время бурения шага
/// </summary>
public float? TimeDrillStep { get; set; }
/// <summary>
/// Глубина бурения шага
/// </summary>
public float? DepthDrillStep { get; set; }
}
}

View File

@ -76,6 +76,7 @@ namespace AsbCloudDb.Model
DbSet<Manual> Manuals { get; }
DbSet<ManualDirectory> ManualDirectories { get; }
DbSet<Contact> Contacts { get; }
DbSet<DrillTest> DrillTests { get; }
DatabaseFacade Database { get; }
Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token);

View File

@ -18,7 +18,7 @@ namespace AsbCloudDb.Model
public DateTimeOffset DateTime { get; set; }
[Column("mode"), Comment("Режим САУБ")]
public short? Mode { get; set; }
public short Mode { get; set; }
[Column("id_feed_regulator"), Comment("Текущий критерий бурения")]
public short? IdFeedRegulator { get; set; }
@ -27,13 +27,13 @@ namespace AsbCloudDb.Model
public short? MseState { get; set; }
[Column("well_depth"), Comment("Глубина забоя")]
public float? WellDepth { get; set; }
public float WellDepth { get; set; }
[Column("bit_depth"), Comment("Положение инструмента")]
public float? BitDepth { get; set; }
public float BitDepth { get; set; }
[Column("block_position"), Comment("Высота талевого блока")]
public float? BlockPosition { get; set; }
public float BlockPosition { get; set; }
[Column("block_position_min"), Comment("Талевый блок. Мин положение")]
public float? BlockPositionMin { get; set; }
@ -57,7 +57,7 @@ namespace AsbCloudDb.Model
public float? BlockSpeedSpDevelop { get; set; }
[Column("pressure"), Comment("Давление")]
public float? Pressure { get; set; }
public float Pressure { get; set; }
[Column("pressure_idle"), Comment("Давление. Холостой ход")]
public float? PressureIdle { get; set; }
@ -78,7 +78,7 @@ namespace AsbCloudDb.Model
public float? PressureDeltaLimitMax { get; set; }
[Column("axial_load"), Comment("Осевая нагрузка")]
public float? AxialLoad { get; set; }
public float AxialLoad { get; set; }
[Column("axial_load_sp"), Comment("Осевая нагрузка. Задание")]
public float? AxialLoadSp { get; set; }
@ -87,7 +87,7 @@ namespace AsbCloudDb.Model
public float? AxialLoadLimitMax { get; set; }
[Column("hook_weight"), Comment("Вес на крюке")]
public float? HookWeight { get; set; }
public float HookWeight { get; set; }
[Column("hook_weight_idle"), Comment("Вес на крюке. Холостой ход")]
public float? HookWeightIdle { get; set; }
@ -99,7 +99,7 @@ namespace AsbCloudDb.Model
public float? HookWeightLimitMax { get; set; }
[Column("rotor_torque"), Comment("Момент на роторе")]
public float? RotorTorque { get; set; }
public float RotorTorque { get; set; }
[Column("rotor_torque_idle"), Comment("Момент на роторе. Холостой ход")]
public float? RotorTorqueIdle { get; set; }
@ -111,7 +111,7 @@ namespace AsbCloudDb.Model
public float? RotorTorqueLimitMax { get; set; }
[Column("rotor_speed"), Comment("Обороты ротора")]
public float? RotorSpeed { get; set; }
public float RotorSpeed { get; set; }
[Column("flow"), Comment("Расход")]
public float? Flow { get; set; }

View File

@ -0,0 +1,265 @@
# Репликация данных PostgreSQL
## 1. Требования
1. Primary и Replica сервера должны принадлежать одной версии postgreSQL
2. Сервера должны иметь удаленный доступ
## 2. Настройка Primary-сервера
1. Открыть postgres.conf на редактирование
```
cd /etc/postgresql/15/main/
sudo nano postgresql.conf
```
2. В postgres.conf найти запись listen_addresses и добавить туда ip standby-сервера
> listen_addresses = '*, <ip standby-сервера>'
3. Открыть клиент для работы с postgres
```
sudo -u postgres psql
```
4. Создать пользователя с атрибутом REPLICATION. <br />
P.S: В данном примере создается пользователь с логином replicator и паролем q
```
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'q';
```
5. Открыть на редактирование файл pg_hba.conf
```
cd /etc/postgresql/15/main/
sudo nano pg_hba.conf
```
6. Вставить в pg_hba.conf запись.
Запись вставлять после комментария "Allow replication connections from localhost..." <br />
Данные для вставки записи:<br />
- replicator - имя пользователя, созданного на предыдущем шаге<br/>
- <ip подсети>, например, 192.168.0.0/24
```
host replication replicator 192.168.0.0/24 md5
```
7. Рестарт сервера
```
sudo systemctl restart postgresql
```
## 3. Настройка replica-сервера
1. Остановить сервер
```
sudo systemctl stop postgresql
```
2. Важно! Зайти под пользователем postgres
```
sudo su - postgres
```
3. Сделать резервную копию содержимого /var/lib/postgresql/15/main/ в папку main_old
```
cp -R /var/lib/postgresql/15/main/ /var/lib/postgresql/15/main_old/
```
4. Удалить папку main
```
rm -rf /var/lib/postgresql/15/main/
```
5. Используя утилиту basebackup создать базовую резервную копию с правами владения postgres (либо любого пользователя с соответствующими разрешениями).
```
pg_basebackup -h <ip primary-сервера> -D /var/lib/postgresql/14/main/ -U replicator -P -v -R -X stream -C -S slaveslot1
где: /var/lib/postgresql/15/main/ - каталог replica-сервера
```
6. Убедиться, что в папке main созданы файлы standby.signal и postgresql.auto.conf.
```
ls -ltrh /var/lib/postgresql/15/main/
```
7. Запустить сервер
```
systemctl start postgresql
```
## 4. Проверка настроек
1. Подсоединиться к primary-серверу
```
sudo -u postgres psql
```
2. На primary-сервере выполнить команду
```
SELECT * FROM pg_replication_slots;
```
3. Убедиться, что в представлении отображается слот репликации с именем slotslave1
4. На standby-сервере выпонить команду
```
SELECT * FROM pg_stat_wal_receiver;
```
5. Убедиться, что появилась запись с ip primary-сервера
6. На primary - сервере проверить режим репликации. Он может быть синхронным или асинхронным. Для проверки необходимо выполнить команду
```
SELECT * FROM pg_stat_replication;
```
7. Для включения синхронного режима необходимо выполнить следующую команду
```
ALTER SYSTEM SET synchronous_standby_names TO '*';
```
8. Сделать рестарт primary-сервера.
9. Внести запись в любую таблицу базы данных primary-сервера
10. Убедиться, что соответствующая запись появилась в таблице базы данных standby-сервера
11. Попытаться внести запись в таблицу базы данных standby-сервера.
12. Убедиться, что операция завершилась с ошибкой
> cannot execute OPERATION in a read-only transaction
## 5. Установка PgPool-II
1. Установить на primary-сервер pgpool2 и postgresql-14-pgpool2
```
apt-get -y install pgpool2 postgresql-15-pgpool2
```
2. Установить на standby-сервер только postgresql-14-pgpool2
```
apt-get -y install postgresql-15-pgpool2
```
### Далее все настройки выполнить на primary-сервере
3. Зайти на редактирование в конфигурационный файл pgpool2
```
sudo nano /etc/pgpool2/pgpool.conf
```
4. Задать параметры следующим образом:
```
backend_clustering_mode = 'streaming_replication'
listen_addresses = '*, <ip standby-сервера>'
port = 9999
___
backend_hostname0 = '<ip primary-сервера>'
backend_port0 = '<порт primary-сервера>'
backend_weight0 = 0
backend_data_directory0 = '/var/lib/postgresql/14/main'
___
backend_hostname1 = '<ip replica-сервера>'
backend_port1 = '<порт primary-сервера>'
backend_weight1 = 1
___
enable_pool_hba = on
log_statement = on
log_per_node_statement = on
pid_file_name = "pgpool.pid"
load_balance_mode = on
statement_level_load_balance = on
sr_check_period = 1
sr_check_user = '<имя пользователя>'
sr_check_password = '<пароль пользователя>'
health_check_period = 10
health_check_user = '<имя пользователя>'
health_check_password = '<пароль пользователя>'
```
5. Поскольку enable_pool_hba указан в режиме on, это значит, что Pgpool-II будет использовать pool_hba.conf для аутентификации клиента. Поэтому открываем на редактирование pool_hba.conf
```
sudo nano /etc/pgpool2/pool_hba.conf
```
6. Добавить строку
```
host all all <ip подсети> md5
```
7. Pgpool-II извлекает пароль пользователя из файла pool_passwd
```
sudo nano /etc/pgpool2/pool_passwd
```
Файл паролей представляет собой текстовый файл следующего формата:
```
пользователь1:пароль1
пользователь2:пароль2
```
Файл может содержать 3 типа паролей. Pgpool-II идентифицирует тип формата пароля по его префиксу, поэтому каждая запись пароля в pool_passwd должна иметь префикс формата пароля.
- Обычный текст : пароль в текстовом формате с использованием префикса TEXT (например, TEXTmypassword ) .
- Зашифрованный пароль AES256 : зашифрованный пароль AES256, используя префикс AES (например, AESmzVzywsN1Z5GABhSAhwLSA== ) .
- Хешированный пароль MD5 : хешированный пароль MD5, используя префикс md5 (например, md5270e98c3db83dbc0e40f98d9bfe20972 ) .
8. В примере в качестве пароля используется обычный текст (пароль q)
```
postgres:TEXTq
```
9. Запустить pgpool
```
sudo pgpool -n
```
10. Убедиться, что процесс успешно запущен и подключены 2 ноды с разными индексами. В данном примере для primary node установлен индекс 0, а для standBy ноды установден индекс 1
```
2023-09-14 06:08:08.339: main pid 3941: LOG: find_primary_node: primary node is 0
2023-09-14 06:08:08.339: main pid 3941: LOG: find_primary_node: standby node is 1
2023-09-14 06:08:08.343: pcp_main pid 3977: LOG: PCP process: 3977 started
2023-09-14 06:08:08.343: sr_check_worker pid 3978: LOG: process started
2023-09-14 06:08:08.345: health_check pid 3979: LOG: process started
2023-09-14 06:08:08.349: health_check pid 3980: LOG: process started
2023-09-14 06:08:08.559: main pid 3941: LOG: pgpool-II successfully started. version 4.3.5 (tamahomeboshi)
2023-09-14 06:08:08.662: main pid 3941: LOG: node status[0]: 1
2023-09-14 06:08:08.662: main pid 3941: LOG: node status[1]: 2
```
11. При старте pgpool возможны следующие ошибки:
- файл pgpool_status не найден / нет прав
- pgpool стартует, но ноды имеют одинаковый индекс и балансировка идет только на первую ноду (как проверить балансировку указано ниже) <br/>
Проблема решилась удалением файла pgpool_status, откуда pgpool пытался считывать статусы для нод.
```
cd /var/log/postgresql
rm -rf pgpool_status
sudo systemctl restart postgresql
sudo pgpool -n
```
## 6. Тестирование балансировки PgPool-II
1. При запущенном pgpool (он должен выводить логи), открыть еще один терминал. Зайти в базу, используя Pgpool-II на 9999-порте, выполнив команду
```
psql -h <ip сервера, где установлен pgpool> -p 9999 -d postgres -U postgres
```
2. Выполнить команду
```
show pool_nodes;
```
3. Убедиться, что обе ноды находятся в статусе up, а балансировка установлена на standBy-сервере (load_balance_node = true)
```
node_id | hostname | port | status | pg_status | lb_weight | role | pg_role | select_cnt | load_balance_node | replication_delay | replication_state | replication_sync_state | last_status_change
---------+--------------+------+--------+-----------+-----------+---------+---------+------------+-------------------+-------------------+-------------------+------------------------+---------------------
0 | 192.168.0.71 | 5432 | up | up | 0.000000 | primary | primary | 0 | false | 0 | | | 2023-09-14 06:36:16
1 | 192.168.0.72 | 5432 | up | up | 1.000000 | standby | standby | 0 | true | 0 | | | 2023-09-14 06:36:16
(2 rows)
```
4. Выполнить команды Insert / Update / Delete (в качесве примера была внесена запись в таблицу public.t_company). Убедиться, что запрос приходит на primary-сервер (нода с индексом 0).
```
2023-09-14 07:04:31.800: DBeaver 23.1.2 - Main <postgres> pid 4805: LOG: DB node id: 0 backend pid: 4814 statement: Execute: INSERT INTO public.t_company (id,caption,id_company_type)
VALUES ($1,$2,$3)
```
5. Выполинть команду Select. Убедиться, что запрос приходит на standBy-сервер (нода с индексом 1).
```
2023-09-14 07:53:19.275: DBeaver 23.1.2 - Main <postgres> pid 5069: LOG: DB node id: 1 backend pid: 2745 statement: Execute: SELECT x.* FROM public.t_company x
```

View File

@ -13,6 +13,7 @@
<None Remove="CommonLibs\logo_720x404.png" />
<None Remove="CommonLibs\Readme.md" />
<None Remove="Services\DailyReport\DailyReportTemplate.xlsx" />
<None Remove="Services\DrillTestReport\DrillTestReportTemplate.xlsx" />
<None Remove="Services\Trajectory\PlannedTrajectoryTemplate.xlsx" />
<None Remove="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
<None Remove="Services\WellOperationService\WellOperationImportTemplate.xlsx" />
@ -31,6 +32,7 @@
<ItemGroup>
<EmbeddedResource Include="Services\DetectOperations\DetectOperations.xlsx" />
<EmbeddedResource Include="Services\DailyReport\DailyReportTemplate.xlsx" />
<EmbeddedResource Include="Services\DrillTestReport\DrillTestReportTemplate.xlsx" />
<EmbeddedResource Include="Services\Trajectory\PlannedTrajectoryTemplate.xlsx" />
<EmbeddedResource Include="Services\WellOperationService\ScheduleReportTemplate.xlsx" />
<EmbeddedResource Include="Services\AutoGeneratedDailyReports\AutogeneratedDailyReportTemplate.xlsx" />

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure
{
public static class AssemblyExtensions
{
public static async Task<Stream> GetTemplateCopyStreamAsync(this Assembly assembly, string templateName, CancellationToken cancellationToken)
{
var resourceName = assembly
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith(templateName))!;
using var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
return memoryStream;
}
}
}

View File

@ -1,6 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -11,13 +14,39 @@ namespace AsbCloudInfrastructure.Background;
/// </summary>
public class BackgroundWorker : BackgroundService
{
private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
private readonly IServiceProvider serviceProvider;
public WorkStore WorkStore { get; } = new WorkStore();
/// <summary>
/// Очередь работ
/// </summary>
private Queue<Work> works = new(8);
/// <summary>
/// Список периодических работ
/// </summary>
public IEnumerable<Work> Works => works;
/// <summary>
/// Работа выполняемая в данный момент
/// </summary>
public Work? CurrentWork;
/// <summary>
/// последние 16 завершившиеся с ошибкой
/// </summary>
public CyclycArray<Work> Felled { get; } = new(16);
/// <summary>
/// последние 16 успешно завершенных
/// </summary>
public CyclycArray<Work> Done { get; } = new(16);
/// <summary>
/// Ошибка в главном цикле, никогда не должна появляться
/// </summary>
public string MainLoopLastException { get; private set; } = string.Empty;
public BackgroundWorker(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
@ -25,25 +54,65 @@ public class BackgroundWorker : BackgroundService
protected override async Task ExecuteAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
Trace.TraceInformation($"{GetType().Name} started");
while (!token.IsCancellationRequested && works.TryDequeue(out CurrentWork))
{
var work = WorkStore.GetNext();
if (work is null)
try
{
await Task.Delay(executePeriod, token);
continue;
using var scope = serviceProvider.CreateScope();
var result = await CurrentWork.Start(scope.ServiceProvider, token);
if (!result)
Felled.Add(CurrentWork);
else
Done.Add(CurrentWork);
CurrentWork = null;
await Task.Delay(minDelay, token);
}
catch (Exception ex)
{
MainLoopLastException = $"BackgroundWorker " +
$"MainLoopLastException: \r\n" +
$"date: {DateTime.Now:O}\r\n" +
$"message: {ex.Message}\r\n" +
$"inner: {ex.InnerException?.Message}\r\n" +
$"stackTrace: {ex.StackTrace}";
Trace.TraceError(MainLoopLastException);
}
CurrentWork = work;
using var scope = serviceProvider.CreateScope();
var result = await work.Start(scope.ServiceProvider, token);
if (!result)
WorkStore.Felled.Add(work);
CurrentWork = null;
await Task.Delay(minDelay, token);
}
}
/// <summary>
/// Добавить в очередь
/// <para>
/// work.Id может быть не уникальным,
/// при этом метод TryRemoveFromQueue удалит все работы с совпадающими id
/// </para>
/// </summary>
/// <param name="work"></param>
public void Enqueue(Work work)
{
works.Enqueue(work);
if (ExecuteTask is null || ExecuteTask.IsCompleted)
StartAsync(CancellationToken.None).Wait();
}
/// <summary>
/// Удаление работы по ID из одноразовой очереди
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool TryRemoveFromQueue(string id)
{
var work = Works.FirstOrDefault(w => w.Id == id);
if (work is not null)
{
works = new Queue<Work>(Works.Where(w => w.Id != id));
return true;
}
return false;
}
}

View File

@ -1,41 +0,0 @@
using System.Linq;
namespace System.Collections.Generic
{
public class OrderedList<T>: IEnumerable<T>, ICollection<T>
where T : notnull
{
private readonly List<T> list = new List<T>();
private readonly Func<T, object> keySelector;
private readonly bool isDescending = false;
private IOrderedEnumerable<T> OrdredList => isDescending
? list.OrderByDescending(keySelector)
: list.OrderBy(keySelector);
public int Count => list.Count;
public bool IsReadOnly => false;
public OrderedList(Func<T, object> keySelector, bool isDescending = false)
{
this.keySelector = keySelector;
this.isDescending = isDescending;
}
public void Add(T item) => list.Add(item);
public void Clear()=> list.Clear();
public bool Contains(T item)=> list.Contains(item);
public void CopyTo(T[] array, int arrayIndex)=> list.CopyTo(array, arrayIndex);
public bool Remove(T item)=> list.Remove(item);
public IEnumerator<T> GetEnumerator() => OrdredList.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,116 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background;
/// <summary>
/// Сервис для фонового выполнения периодической работы
/// </summary>
public class PeriodicBackgroundWorker : BackgroundService
{
private readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
private readonly IServiceProvider serviceProvider;
private readonly List<WorkPeriodic> works = new(8);
/// <summary>
/// Список периодических работ
/// </summary>
public IEnumerable<WorkPeriodic> Works => works;
/// <summary>
/// Работа выполняемая в данный момент
/// </summary>
public Work? CurrentWork;
/// <summary>
/// Ошибка в главном цикле, никогда не должна появляться
/// </summary>
public string MainLoopLastException { get; private set; } = string.Empty;
public PeriodicBackgroundWorker(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken token)
{
Trace.TraceInformation($"{GetType().Name} started");
while (!token.IsCancellationRequested)
{
try
{
var periodicWork = GetNext();
if (periodicWork is null)
{
await Task.Delay(executePeriod, token);
continue;
}
CurrentWork = periodicWork.Work;
using var scope = serviceProvider.CreateScope();
var result = await periodicWork.Work.Start(scope.ServiceProvider, token);
CurrentWork = null;
await Task.Delay(minDelay, token);
}
catch (Exception ex)
{
MainLoopLastException = $"BackgroundWorker " +
$"MainLoopLastException: \r\n" +
$"date: {DateTime.Now:O}\r\n" +
$"message: {ex.Message}\r\n" +
$"inner: {ex.InnerException?.Message}\r\n" +
$"stackTrace: {ex.StackTrace}";
Trace.TraceError(MainLoopLastException);
}
}
}
/// <summary>
/// Добавить фоновую работу выполняющуюся с заданным периодом
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="period"></param>
public void Add<T>(TimeSpan period)
where T : Work, new()
{
var work = new T();
var periodic = new WorkPeriodic(work, period);
works.Add(periodic);
}
/// <summary>
/// Добавить фоновую работу выполняющуюся с заданным периодом
/// </summary>
/// <param name="work"></param>
/// <param name="period"></param>
public void Add(Work work, TimeSpan period)
{
var periodic = new WorkPeriodic(work, period);
works.Add(periodic);
if (ExecuteTask is null || ExecuteTask.IsCompleted)
StartAsync(CancellationToken.None).Wait();
}
private WorkPeriodic? GetNext()
{
var work = works
.OrderBy(w => w.NextStart)
.FirstOrDefault();
if (work is null || work.NextStart > DateTime.Now)
return null;
return work;
}
}

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -12,6 +13,8 @@ namespace AsbCloudInfrastructure.Background;
/// </summary>
public abstract class Work : BackgroundWorkDto
{
private CancellationTokenSource? stoppingCts;
private sealed class WorkBase : Work
{
private Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> ActionAsync { get; }
@ -68,8 +71,9 @@ public abstract class Work : BackgroundWorkDto
SetStatusStart();
try
{
var task = Action(Id, services, UpdateStatus, token);
await task.WaitAsync(Timeout, token);
stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(token);
var task = Action(Id, services, UpdateStatus, stoppingCts.Token);
await task.WaitAsync(Timeout, stoppingCts.Token);
SetStatusComplete();
return true;
}
@ -79,15 +83,28 @@ public abstract class Work : BackgroundWorkDto
SetLastError(message);
if (OnErrorAsync is not null)
{
var task = Task.Run(
async () => await OnErrorAsync(Id, exception, token),
token);
await task.WaitAsync(OnErrorHandlerTimeout, token);
try
{
var task = Task.Run(
async () => await OnErrorAsync(Id, exception, token),
token);
await task.WaitAsync(OnErrorHandlerTimeout, token);
}
catch (Exception onErrorAsyncException)
{
var message2 = FormatExceptionMessage(onErrorAsyncException);
Trace.TraceError($"Backgroud work:\"{Id}\" OnError handler throws exception: {message2}");
}
}
}
return false;
}
public void Stop()
{
stoppingCts?.Cancel();
}
private static string FormatExceptionMessage(Exception exception)
{
var firstException = FirstException(exception);

View File

@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudInfrastructure.Background;
/// <summary>
/// <para>
/// Очередь работ
/// </para>
/// Не периодические задачи будут возвращаться первыми, как самые приоритетные.
/// </summary>
public class WorkStore
{
private readonly List<WorkPeriodic> periodics = new(8);
/// <summary>
/// Список периодических задач
/// </summary>
public IEnumerable<WorkPeriodic> Periodics => periodics;
/// <summary>
/// Работы выполняемые один раз
/// </summary>
public Queue<Work> RunOnceQueue { get; private set; } = new(8);
/// <summary>
/// Завершившиеся с ошибкой
/// </summary>
public CyclycArray<Work> Felled { get; } = new(16);
/// <summary>
/// Добавить фоновую работу выполняющуюся с заданным периодом
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="period"></param>
public void AddPeriodic<T>(TimeSpan period)
where T : Work, new()
{
var work = new T();
var periodic = new WorkPeriodic(work, period);
periodics.Add(periodic);
}
/// <summary>
/// Добавить фоновую работу выполняющуюся с заданным периодом
/// </summary>
/// <param name="work"></param>
/// <param name="period"></param>
public void AddPeriodic(Work work, TimeSpan period)
{
var periodic = new WorkPeriodic(work, period);
periodics.Add(periodic);
}
/// <summary>
/// Удаление работы по ID из одноразовой очереди
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool TryRemoveFromRunOnceQueue(string id)
{
var work = RunOnceQueue.FirstOrDefault(w => w.Id == id);
if (work is not null)
{
RunOnceQueue = new Queue<Work>(RunOnceQueue.Where(w => w.Id != id));
return true;
}
return false;
}
/// <summary>
/// <para>
/// Возвращает приоритетную задачу.
/// </para>
/// <para>
/// Если приоритетные закончились, то ищет ближайшую периодическую.
/// Если до старта ближайшей периодической работы меньше 20 сек,
/// то этой задаче устанавливается время последнего запуска в now и она возвращается.
/// Если больше 20 сек, то возвращается null.
/// </para>
/// </summary>
/// <param name="maxTimeToNextWork"></param>
/// <returns></returns>
public Work? GetNext()
{
if (RunOnceQueue.Any())
return RunOnceQueue.Dequeue();
var work = GetNextPeriodic();
if (work is null || work.NextStart > DateTime.Now)
return null;
return work.Work;
}
private WorkPeriodic? GetNextPeriodic()
{
var work = Periodics
.OrderBy(w => w.NextStart)
.FirstOrDefault();
return work;
}
}

View File

@ -0,0 +1,34 @@
using AsbCloudApp.Services;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Background
{
/// <summary>
/// Задача по удалению загруженных отчетов
/// </summary>
public class WorkToDeleteOldReports : Work
{
public WorkToDeleteOldReports()
: base("work to delete reports older than 30 days")
{
Timeout = TimeSpan.FromMinutes(10);
}
/// <summary>
/// Удаление отчетов, загруженных ранее 30 дней от текущей даты
/// </summary>
/// <param name="id"></param>
/// <param name="services"></param>
/// <param name="onProgressCallback"></param>
/// <param name="token"></param>
/// <returns></returns>
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
{
var reportService = services.GetRequiredService<IReportService>();
await reportService.DeleteAllOldReportsAsync(TimeSpan.FromDays(30), token);
}
}
}

View File

@ -0,0 +1,11 @@
# BackgroundWorker
Класс выполнения разовой фоновой работы.
Для каждой работы создается свой scope.
# NotificationBackgroundWorker
Предназначен для различных оповещений пользователей разными способами.
Фактически это дополнительный экземпляр BackgroundWorker, чтобы оповещения не ждали завершения долгих операций из стандартного BackgroundWorker.
Не должен давать большой нагрузки БД.
# PeriodicBackgroundWorker
Класс выполнения периодической фоновой работы.

View File

@ -1,12 +0,0 @@
# Проблемы фонового сервиса
- Нужно состояние по загрузки сервиса и очереди работ.
- Все ли задачи укладываются в таймаут,
- Сколько свободного времени остается,
- Что делает текущая задача,
- нет управления сервисом. Для исключения его влияния на другие процессы сервера.
- отключать/включать целиком
- отключать/включать отдельную периодическую задачу
# Сделать
- Разработать dto статуса задачи
- Отказаться от периодической задачи, при добавлении в хранилище задач период будет параметром метода добавления.

View File

@ -1,5 +1,7 @@
using System;
using AsbCloudApp.Data;
using AsbCloudApp.Data.AutogeneratedDailyReport;
using AsbCloudApp.Data.DrillTestReport;
using AsbCloudApp.Data.Manuals;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Data.SAUB;
@ -24,6 +26,7 @@ using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
using AsbCloudInfrastructure.Services.DailyReport;
using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services.DrillingProgram;
using AsbCloudInfrastructure.Services.DrillTestReport;
using AsbCloudInfrastructure.Services.ProcessMaps.Report;
using AsbCloudInfrastructure.Services.ProcessMaps.WellDrilling;
using AsbCloudInfrastructure.Services.SAUB;
@ -43,268 +46,273 @@ namespace AsbCloudInfrastructure
public static class DependencyInjection
{
public static IAsbCloudDbContext MakeContext(string connectionString)
{
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(connectionString)
.Options;
var context = new AsbCloudDbContext(options);
return context;
}
public static IAsbCloudDbContext MakeContext(string connectionString)
{
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
.UseNpgsql(connectionString)
.Options;
var context = new AsbCloudDbContext(options);
return context;
}
public static void MapsterSetup()
{
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<DateTimeOffset, DateTime>()
.MapWith((source) => source.DateTime);
public static void MapsterSetup()
{
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<DateTimeOffset, DateTime>()
.MapWith((source) => source.DateTime);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<DateTime, DateTimeOffset>()
.MapWith((source) => source == default ? new DateTime(0, DateTimeKind.Utc) : source);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<DateTime, DateTimeOffset>()
.MapWith((source) => source == default ? new DateTime(0, DateTimeKind.Utc) : source);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TimeDto, TimeOnly>()
.MapWith((source) => source.MakeTimeOnly());
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TimeDto, TimeOnly>()
.MapWith((source) => source.MakeTimeOnly());
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TimeOnly, TimeDto>()
.MapWith((source) => new(source));
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TimeOnly, TimeDto>()
.MapWith((source) => new(source));
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TimeOnly, TimeDto>()
.MapWith((source) => new(source));
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<TimeOnly, TimeDto>()
.MapWith((source) => new(source));
#pragma warning disable CS8603 // Possible null reference return.
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<WellDto, Well>()
.Ignore(dst => dst.Cluster,
dst => dst.RelationCompaniesWells,
dst => dst.Telemetry,
dst => dst.WellComposites,
dst => dst.WellCompositeSrcs,
dst => dst.WellOperations,
dst => dst.WellType);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<WellDto, Well>()
.Ignore(dst => dst.Cluster,
dst => dst.RelationCompaniesWells,
dst => dst.Telemetry,
dst => dst.WellComposites,
dst => dst.WellCompositeSrcs,
dst => dst.WellOperations,
dst => dst.WellType);
#pragma warning restore CS8603 // Possible null reference return.
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<ClusterDto, Cluster>()
.Ignore(dst => dst.Deposit,
dst => dst.Wells);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<ClusterDto, Cluster>()
.Ignore(dst => dst.Deposit,
dst => dst.Wells);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<FileCategoryDto, FileCategory>();
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<FileCategoryDto, FileCategory>();
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<WellFinalDocumentDto, WellFinalDocument>();
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<WellFinalDocumentDto, WellFinalDocument>();
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<NotificationDto, Notification>()
.Ignore(dst => dst.NotificationCategory,
dst => dst.User);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<NotificationDto, Notification>()
.Ignore(dst => dst.NotificationCategory,
dst => dst.User);
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<ProcessMapWellDrilling, ProcessMapPlanWellDrillingDto>()
.Map(dest => dest.AxialLoad, src => new PlanLimitDto
{
LimitMax = src.AxialLoadLimitMax,
Plan = src.AxialLoadPlan
})
.Map(dest => dest.Flow, src => new PlanLimitDto
{
LimitMax = src.FlowLimitMax,
Plan = src.FlowPlan
})
.Map(dest => dest.Pressure, src => new PlanLimitDto
{
LimitMax = src.PressureLimitMax,
Plan = src.PressurePlan
})
.Map(dest => dest.TopDriveSpeed, src => new PlanLimitDto
{
LimitMax = src.TopDriveSpeedLimitMax,
Plan = src.TopDriveSpeedPlan
})
.Map(dest => dest.TopDriveTorque, src => new PlanLimitDto
{
LimitMax = src.TopDriveTorqueLimitMax,
Plan = src.TopDriveTorquePlan
});
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<ProcessMapWellDrilling, ProcessMapPlanWellDrillingDto>()
.Map(dest => dest.AxialLoad, src => new PlanLimitDto
{
LimitMax = src.AxialLoadLimitMax,
Plan = src.AxialLoadPlan
})
.Map(dest => dest.Flow, src => new PlanLimitDto
{
LimitMax = src.FlowLimitMax,
Plan = src.FlowPlan
})
.Map(dest => dest.Pressure, src => new PlanLimitDto
{
LimitMax = src.PressureLimitMax,
Plan = src.PressurePlan
})
.Map(dest => dest.TopDriveSpeed, src => new PlanLimitDto
{
LimitMax = src.TopDriveSpeedLimitMax,
Plan = src.TopDriveSpeedPlan
})
.Map(dest => dest.TopDriveTorque, src => new PlanLimitDto
{
LimitMax = src.TopDriveTorqueLimitMax,
Plan = src.TopDriveTorquePlan
});
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<ProcessMapPlanWellDrillingDto, ProcessMapWellDrilling>()
.Map(dest => dest.AxialLoadPlan, src => src.AxialLoad.Plan)
.Map(dest => dest.AxialLoadLimitMax, src => src.AxialLoad.LimitMax)
.Map(dest => dest.FlowPlan, src => src.Flow.Plan)
.Map(dest => dest.FlowLimitMax, src => src.Flow.LimitMax)
.Map(dest => dest.PressurePlan, src => src.Pressure.Plan)
.Map(dest => dest.PressureLimitMax, src => src.Pressure.LimitMax)
.Map(dest => dest.TopDriveSpeedPlan, src => src.TopDriveSpeed.Plan)
.Map(dest => dest.TopDriveSpeedLimitMax, src => src.TopDriveSpeed.LimitMax)
.Map(dest => dest.TopDriveTorquePlan, src => src.TopDriveTorque.Plan)
.Map(dest => dest.TopDriveTorqueLimitMax, src => src.TopDriveTorque.LimitMax);
}
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<ProcessMapPlanWellDrillingDto, ProcessMapWellDrilling>()
.Map(dest => dest.AxialLoadPlan, src => src.AxialLoad.Plan)
.Map(dest => dest.AxialLoadLimitMax, src => src.AxialLoad.LimitMax)
.Map(dest => dest.FlowPlan, src => src.Flow.Plan)
.Map(dest => dest.FlowLimitMax, src => src.Flow.LimitMax)
.Map(dest => dest.PressurePlan, src => src.Pressure.Plan)
.Map(dest => dest.PressureLimitMax, src => src.Pressure.LimitMax)
.Map(dest => dest.TopDriveSpeedPlan, src => src.TopDriveSpeed.Plan)
.Map(dest => dest.TopDriveSpeedLimitMax, src => src.TopDriveSpeed.LimitMax)
.Map(dest => dest.TopDriveTorquePlan, src => src.TopDriveTorque.Plan)
.Map(dest => dest.TopDriveTorqueLimitMax, src => src.TopDriveTorque.LimitMax);
}
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
MapsterSetup();
string connectionStringName = "DefaultConnection";
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
MapsterSetup();
string connectionStringName = "DefaultConnection";
#if DEBUG
connectionStringName = "DebugConnection";
connectionStringName = "DebugConnection";
#endif
services.AddDbContext<AsbCloudDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString(connectionStringName)));
services.AddDbContext<AsbCloudDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString(connectionStringName)));
services.AddMemoryCache();
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>());
services.AddMemoryCache();
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>());
services.AddSingleton(new WitsInfoService());
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider));
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
services.AddSingleton<BackgroundWorker>();
services.AddSingleton<NotificationBackgroundWorker>();
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
services.AddSingleton(new WitsInfoService());
services.AddSingleton<ITelemetryDataCache<TelemetryDataSaubDto>>(provider =>
TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider));
services.AddSingleton<ITelemetryDataCache<TelemetryDataSpinDto>>(provider =>
TelemetryDataCache<TelemetryDataSpinDto>.GetInstance<TelemetryDataSpin>(provider));
services.AddSingleton<IRequerstTrackerService, RequestTrackerService>();
services.AddSingleton<PeriodicBackgroundWorker>();
services.AddSingleton<BackgroundWorker>();
services.AddSingleton<NotificationBackgroundWorker>();
services.AddSingleton<IReduceSamplingService>(provider => ReduceSamplingService.GetInstance(configuration));
services.AddTransient<IAuthService, AuthService>();
services.AddTransient<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>, ProcessMapPlanRepository<ProcessMapPlanWellDrillingDto, ProcessMapWellDrilling>>();
services.AddTransient<IProcessMapPlanRepository<ProcessMapPlanWellReamDto>, ProcessMapPlanRepository<ProcessMapPlanWellReamDto, ProcessMapWellReam>>();
services.AddTransient<IDepositRepository, DepositRepository>();
services.AddTransient<IDrillingProgramService, DrillingProgramService>();
services.AddTransient<IEventService, EventService>();
services.AddTransient<FileService>();
services.AddTransient<IMeasureService, MeasureService>();
services.AddTransient<IMessageService, MessageService>();
services.AddTransient<IOperationsStatService, OperationsStatService>();
services.AddTransient<IReportService, ReportService>();
services.AddTransient<ISetpointsService, SetpointsService>();
services.AddTransient<ITelemetryService, TelemetryService>();
services.AddTransient<ITelemetryUserService, TelemetryUserService>();
services.AddTransient<ITimezoneService, TimezoneService>();
services.AddTransient<IWellService, WellService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IProcessMapReportWellDrillingExportService, ProcessMapReportWellDrillingExportService>();
services.AddTransient<IPlannedTrajectoryImportService, PlannedTrajectoryImportService>();
services.AddTransient<IWellOperationRepository, WellOperationRepository>();
services.AddTransient<IScheduleReportService, ScheduleReportService>();
services.AddTransient<IDailyReportService, DailyReportService>();
services.AddTransient<IDetectedOperationService, DetectedOperationService>();
services.AddTransient<ISubsystemOperationTimeService, SubsystemOperationTimeService>();
services.AddTransient<IScheduleRepository, ScheduleRepository>();
services.AddTransient<IRepositoryWellRelated<OperationValueDto>, CrudWellRelatedRepositoryBase<OperationValueDto, OperationValue>>();
services.AddTransient<IUserSettingsRepository, UserSettingsRepository>();
services.AddTransient<IWellFinalDocumentsService, WellFinalDocumentsService>();
services.AddTransient<IFileCategoryService, FileCategoryService>();
services.AddTransient<ILimitingParameterService, LimitingParameterService>();
services.AddTransient<IProcessMapReportWellDrillingService, ProcessMapReportWellDrillingService>();
services.AddTransient<IProcessMapPlanImportService, ProcessMapPlanImportWellDrillingService>();
services.AddTransient<WellInfoService>();
services.AddTransient<IHelpPageService, HelpPageService>();
services.AddTransient<IAuthService, AuthService>();
services.AddTransient<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>, ProcessMapPlanRepository<ProcessMapPlanWellDrillingDto, ProcessMapWellDrilling>>();
services.AddTransient<IProcessMapPlanRepository<ProcessMapPlanWellReamDto>, ProcessMapPlanRepository<ProcessMapPlanWellReamDto, ProcessMapWellReam>>();
services.AddTransient<IDepositRepository, DepositRepository>();
services.AddTransient<IDrillingProgramService, DrillingProgramService>();
services.AddTransient<IEventService, EventService>();
services.AddTransient<FileService>();
services.AddTransient<IMeasureService, MeasureService>();
services.AddTransient<IMessageService, MessageService>();
services.AddTransient<IOperationsStatService, OperationsStatService>();
services.AddTransient<IReportService, ReportService>();
services.AddTransient<ISetpointsService, SetpointsService>();
services.AddTransient<ITelemetryService, TelemetryService>();
services.AddTransient<ITelemetryUserService, TelemetryUserService>();
services.AddTransient<ITimezoneService, TimezoneService>();
services.AddTransient<IWellService, WellService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IProcessMapReportWellDrillingExportService, ProcessMapReportWellDrillingExportService>();
services.AddTransient<IPlannedTrajectoryImportService, PlannedTrajectoryImportService>();
services.AddTransient<IWellOperationRepository, WellOperationRepository>();
services.AddTransient<IDailyReportService, DailyReportService>();
services.AddTransient<IDetectedOperationService, DetectedOperationService>();
services.AddTransient<ISubsystemOperationTimeService, SubsystemOperationTimeService>();
services.AddTransient<IScheduleRepository, ScheduleRepository>();
services.AddTransient<IRepositoryWellRelated<OperationValueDto>, CrudWellRelatedRepositoryBase<OperationValueDto, OperationValue>>();
services.AddTransient<IUserSettingsRepository, UserSettingsRepository>();
services.AddTransient<IWellFinalDocumentsService, WellFinalDocumentsService>();
services.AddTransient<IFileCategoryService, FileCategoryService>();
services.AddTransient<ILimitingParameterService, LimitingParameterService>();
services.AddTransient<IProcessMapReportWellDrillingService, ProcessMapReportWellDrillingService>();
services.AddTransient<IProcessMapPlanImportService, ProcessMapPlanImportWellDrillingService>();
services.AddTransient<WellInfoService>();
services.AddTransient<IHelpPageService, HelpPageService>();
services.AddTransient<TrajectoryService>();
services.AddTransient<TrajectoryService>();
services.AddTransient<IGtrRepository, GtrWitsRepository>();
services.AddTransient<IGtrRepository, GtrWitsRepository>();
services.AddTransient<NotificationService>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<ICrudRepository<NotificationCategoryDto>, CrudCacheRepositoryBase<NotificationCategoryDto,
NotificationCategory>>();
services.AddTransient<NotificationService>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<ICrudRepository<NotificationCategoryDto>, CrudCacheRepositoryBase<NotificationCategoryDto,
NotificationCategory>>();
services.AddTransient<IDrillTestRepository, DrillTestRepository>();
// admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheRepositoryBase<TelemetryDto, Telemetry>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService
services.AddTransient<ICrudRepository<DepositDto>, CrudCacheRepositoryBase<DepositDto, Deposit>>(s =>
new CrudCacheRepositoryBase<DepositDto, Deposit>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet.Include(d => d.Clusters)));
services.AddTransient<ICrudRepository<CompanyDto>, CrudCacheRepositoryBase<CompanyDto, Company>>(s =>
new CrudCacheRepositoryBase<CompanyDto, Company>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet.Include(c => c.CompanyType)));
// admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheRepositoryBase<TelemetryDto, Telemetry>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet.Include(t => t.Well))); // может быть включен в сервис TelemetryService
services.AddTransient<ICrudRepository<DepositDto>, CrudCacheRepositoryBase<DepositDto, Deposit>>(s =>
new CrudCacheRepositoryBase<DepositDto, Deposit>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet.Include(d => d.Clusters)));
services.AddTransient<ICrudRepository<CompanyDto>, CrudCacheRepositoryBase<CompanyDto, Company>>(s =>
new CrudCacheRepositoryBase<CompanyDto, Company>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet.Include(c => c.CompanyType)));
services.AddTransient<ICrudRepository<CompanyTypeDto>, CrudCacheRepositoryBase<CompanyTypeDto, CompanyType>>();
services.AddTransient<ICrudRepository<ClusterDto>, CrudCacheRepositoryBase<ClusterDto, Cluster>>(s =>
new CrudCacheRepositoryBase<ClusterDto, Cluster>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet
.Include(c => c.Wells)
.Include(c => c.Deposit))); // может быть включен в сервис ClusterService
services.AddTransient<ICrudRepository<CompanyTypeDto>, CrudCacheRepositoryBase<CompanyTypeDto, CompanyType>>();
services.AddTransient<ICrudRepository<ClusterDto>, CrudCacheRepositoryBase<ClusterDto, Cluster>>(s =>
new CrudCacheRepositoryBase<ClusterDto, Cluster>(
s.GetRequiredService<IAsbCloudDbContext>(),
s.GetRequiredService<IMemoryCache>(),
dbSet => dbSet
.Include(c => c.Wells)
.Include(c => c.Deposit))); // может быть включен в сервис ClusterService
services.AddTransient<ICrudRepository<DrillerDto>, CrudCacheRepositoryBase<DrillerDto, Driller>>();
services.AddTransient<ICrudRepository<DrillerDto>, CrudCacheRepositoryBase<DrillerDto, Driller>>();
services.AddTransient<IHelpPageRepository, HelpPageRepository>();
services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<IFileStorageRepository, FileStorageRepository>();
services.AddTransient<IWellCompositeRepository, WellCompositeRepository>();
services.AddTransient<IUserRoleRepository, UserRoleRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ILimitingParameterRepository, LimitingParameterRepository>();
services.AddTransient<ITelemetryWirelineRunOutRepository, TelemetryWirelineRunOutRepository>();
services.AddTransient<IWellFinalDocumentsRepository, WellFinalDocumentsRepository>();
services.AddTransient<ITrajectoryPlanRepository, TrajectoryPlanRepository>();
services.AddTransient<ITrajectoryFactRepository, TrajectoryFactRepository>();
services.AddTransient<IFaqRepository, FaqRepository>();
services.AddTransient<ISlipsStatService, SlipsStatService>();
services.AddTransient<IWellContactService, WellContactService>();
services.AddTransient<ICrudRepository<WellSectionTypeDto>, CrudCacheRepositoryBase<WellSectionTypeDto,
WellSectionType>>();
services.AddTransient<IHelpPageRepository, HelpPageRepository>();
services.AddTransient<IFileRepository, FileRepository>();
services.AddTransient<IFileStorageRepository, FileStorageRepository>();
services.AddTransient<IWellCompositeRepository, WellCompositeRepository>();
services.AddTransient<IUserRoleRepository, UserRoleRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ILimitingParameterRepository, LimitingParameterRepository>();
services.AddTransient<ITelemetryWirelineRunOutRepository, TelemetryWirelineRunOutRepository>();
services.AddTransient<IWellFinalDocumentsRepository, WellFinalDocumentsRepository>();
services.AddTransient<ITrajectoryPlanRepository, TrajectoryPlanRepository>();
services.AddTransient<ITrajectoryFactRepository, TrajectoryFactRepository>();
services.AddTransient<IFaqRepository, FaqRepository>();
services.AddTransient<ISlipsStatService, SlipsStatService>();
services.AddTransient<IWellContactService, WellContactService>();
services.AddTransient<ICrudRepository<WellSectionTypeDto>, CrudCacheRepositoryBase<WellSectionTypeDto,
WellSectionType>>();
// Subsystem service
services.AddTransient<ICrudRepository<SubsystemDto>, CrudCacheRepositoryBase<SubsystemDto, Subsystem>>();
services.AddTransient<ISubsystemService, SubsystemService>();
// Subsystem service
services.AddTransient<ICrudRepository<SubsystemDto>, CrudCacheRepositoryBase<SubsystemDto, Subsystem>>();
services.AddTransient<ISubsystemService, SubsystemService>();
services.AddTransient<ICrudRepository<PermissionDto>, CrudCacheRepositoryBase<PermissionDto, Permission>>();
services.AddTransient<ICrudRepository<PermissionDto>, CrudCacheRepositoryBase<PermissionDto, Permission>>();
// TelemetryData services
services.AddTransient<ITelemetryDataSaubService, TelemetryDataSaubService>();
services.AddTransient<ITelemetryDataService<TelemetryDataSpinDto>, TelemetryDataSpinService>();
// TelemetryData services
services.AddTransient<ITelemetryDataSaubService, TelemetryDataSaubService>();
services.AddTransient<ITelemetryDataService<TelemetryDataSpinDto>, TelemetryDataSpinService>();
// Wits
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record1Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record1Dto, AsbCloudDb.Model.WITS.Record1>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record7Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record7Dto, AsbCloudDb.Model.WITS.Record7>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record8Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record8Dto, AsbCloudDb.Model.WITS.Record8>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record50Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record50Dto, AsbCloudDb.Model.WITS.Record50>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record60Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record60Dto, AsbCloudDb.Model.WITS.Record60>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record61Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record61Dto, AsbCloudDb.Model.WITS.Record61>>();
// Wits
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record1Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record1Dto, AsbCloudDb.Model.WITS.Record1>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record7Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record7Dto, AsbCloudDb.Model.WITS.Record7>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record8Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record8Dto, AsbCloudDb.Model.WITS.Record8>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record50Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record50Dto, AsbCloudDb.Model.WITS.Record50>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record60Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record60Dto, AsbCloudDb.Model.WITS.Record60>>();
services.AddTransient<IWitsRecordRepository<AsbCloudApp.Data.WITS.Record61Dto>, WitsRecordRepository<AsbCloudApp.Data.WITS.Record61Dto, AsbCloudDb.Model.WITS.Record61>>();
services.AddTransient<IAutoGeneratedDailyReportService, AutoGeneratedDailyReportService>();
services.AddTransient<IAutoGeneratedDailyReportMakerService, AutoGeneratedDailyReportMakerService>();
services.AddTransient<IAutoGeneratedDailyReportService, AutoGeneratedDailyReportService>();
services.AddTransient<IReportMakerService<AutoGeneratedDailyReportDto>, AutoGeneratedDailyReportMakerService>();
services.AddTransient<IDrillTestReportService, DrillTestReportService>();
services.AddTransient<IReportMakerService<DrillTestReportDataDto>, DrillTestReportMakerService>();
services.AddTransient<IManualDirectoryRepository, ManualDirectoryRepository>();
services.AddTransient<IManualCatalogService, ManualCatalogService>();
services.AddTransient<ICrudRepository<ManualDto>, CrudRepositoryBase<ManualDto, Manual>>();
services.AddTransient<IManualDirectoryRepository, ManualDirectoryRepository>();
services.AddTransient<IManualCatalogService, ManualCatalogService>();
services.AddTransient<ICrudRepository<ManualDto>, CrudRepositoryBase<ManualDto, Manual>>();
services.AddTransient<IWellboreService, WellboreService>();
services.AddTransient<IWellboreService, WellboreService>();
services.AddTransient<IWellOperationExportService, WellOperationExportService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IWellOperationImportTemplateService, WellOperationImportTemplateService>();
services.AddTransient<IWellOperationExportService, WellOperationExportService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IWellOperationImportTemplateService, WellOperationImportTemplateService>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportDefaultOptionsDto>, WellOperationDefaultExcelParser>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto>, WellOperationGazpromKhantosExcelParser>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportDefaultOptionsDto>, WellOperationDefaultExcelParser>();
services.AddTransient<IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto>, WellOperationGazpromKhantosExcelParser>();
services.AddTransient<DetectedOperationExportService>();
return services;
services.AddTransient<DetectedOperationExportService>();
return services;
}
public static IServiceCollection AddTransientLazy<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
=> services.AddTransient<TService, TImplementation>()
.AddTransient(provider => new Lazy<TService>(provider.GetRequiredService<TService>));
public static IServiceCollection AddTransientLazy<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
=> services.AddTransient<TService, TImplementation>()
.AddTransient(provider => new Lazy<TService>(provider.GetRequiredService<TService>));
public static IServiceCollection AddTransientLazy<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
where TService : class
where TImplementation : class, TService
=> services.AddTransient<TService, TImplementation>(implementationFactory)
.AddTransient(provider => new Lazy<TService>(() => implementationFactory(provider)));
public static IServiceCollection AddTransientLazy<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
where TService : class
where TImplementation : class, TService
=> services.AddTransient<TService, TImplementation>(implementationFactory)
.AddTransient(provider => new Lazy<TService>(() => implementationFactory(provider)));
}

View File

@ -0,0 +1,82 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository
{
public class DrillTestRepository : IDrillTestRepository
{
private readonly IAsbCloudDbContext db;
public DrillTestRepository(IAsbCloudDbContext db)
{
this.db = db;
}
public async Task<IEnumerable<DrillTestDto>> GetAllAsync(int idTelemetry, FileReportRequest request, CancellationToken cancellationToken)
{
var query = db.DrillTests
.Where(d => d.IdTelemetry == idTelemetry)
.Include(d => d.Telemetry)
.AsNoTracking();
if (request.GeDate.HasValue)
{
var startDateUTC = new DateTimeOffset(request.GeDate.Value.Year, request.GeDate.Value.Month, request.GeDate.Value.Day, 0, 0, 0, TimeSpan.Zero);
query = query.Where(q => q.TimeStampStart >= startDateUTC);
}
if (request.LeDate.HasValue)
{
var finishDateUTC = new DateTimeOffset(request.LeDate.Value.Year, request.LeDate.Value.Month, request.LeDate.Value.Day, 0, 0, 0, TimeSpan.Zero);
query = query.Where(q => q.TimeStampStart <= finishDateUTC);
}
var entities = await query.ToListAsync(cancellationToken);
var dtos = entities.Select(e => Convert(e));
return dtos;
}
public async Task<DrillTestDto> GetAsync(int idTelemetry, int id, CancellationToken cancellationToken)
{
var drillTest = await db.DrillTests
.Where(d => d.Id == id)
.Include(d => d.Telemetry)
.Where(d => d.Telemetry.Id == idTelemetry)
.FirstOrDefaultAsync(cancellationToken);
if (drillTest is null)
throw new ArgumentInvalidException(new string[] { nameof(id), nameof(idTelemetry) }, $"Drill test with id: {id} and idTelemetry: {idTelemetry} does not exist.");
var dto = Convert(drillTest);
return dto;
}
public async Task<int> SaveDataAsync(int idTelemetry, DrillTestDto dto, CancellationToken token)
{
var entity = dto.Adapt<DrillTest>();
entity.IdTelemetry = idTelemetry;
db.DrillTests.Add(entity);
var result = await db.SaveChangesAsync(token);
return result;
}
private DrillTestDto Convert(DrillTest entity)
{
var dto = entity.Adapt<DrillTestDto>();
dto.TimeStampStart = dto.TimeStampStart.ToRemoteDateTime(dto.Telemetry?.TimeZone?.Hours ?? 0);
return dto;
}
}
}

View File

@ -147,17 +147,17 @@ namespace AsbCloudInfrastructure.Repository
public async Task<IEnumerable<FileInfoDto>> DeleteAsync(IEnumerable<int> ids, CancellationToken token)
{
var query = dbSetConfigured
.Where(f => ids.Contains(f.Id) && f.IsDeleted);
.Where(f => ids.Contains(f.Id));
var files = await query.ToListAsync(token);
var files = await query.ToListAsync(token);
var filesDtos = files.Select(x => Convert(x));
var filesDtos = files.Select(x => Convert(x));
db.Files.RemoveRange(query);
await db.SaveChangesAsync(token).ConfigureAwait(false);
db.Files.RemoveRange(query);
await db.SaveChangesAsync(token).ConfigureAwait(false);
return filesDtos;
}
return filesDtos;
}
public async Task<FileInfoDto> GetByMarkId(int idMark, CancellationToken token)
{

View File

@ -128,12 +128,14 @@ public class WellOperationRepository : IWellOperationRepository
operation.IdWell,
operation.IdType,
operation.IdWellSectionType,
operation.WellSectionType.Caption,
})
.Select(group => new
{
group.Key.IdWell,
group.Key.IdType,
group.Key.IdWellSectionType,
group.Key.Caption,
First = group
.OrderBy(operation => operation.DateStart)
@ -162,6 +164,8 @@ public class WellOperationRepository : IWellOperationRepository
IdType = item.IdType,
IdWellSectionType = item.IdWellSectionType,
Caption = item.Caption,
DateStart = item.First.DateStart,
DepthStart = item.First.DepthStart,

View File

@ -5,14 +5,16 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.AutogeneratedDailyReport;
using AsbCloudApp.Services.AutoGeneratedDailyReports;
using AsbCloudApp.Services;
using AsbCloudInfrastructure.Services.AutoGeneratedDailyReports.AutogeneratedDailyReportBlocks;
using ClosedXML.Excel;
namespace AsbCloudInfrastructure.Services.AutoGeneratedDailyReports;
public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMakerService
public class AutoGeneratedDailyReportMakerService : IReportMakerService<AutoGeneratedDailyReportDto>
{
private readonly string templateName = "AutogeneratedDailyReportTemplate.xlsx";
private readonly IEnumerable<IExcelBlockWriter> blockWriters = new List<IExcelBlockWriter>()
{
new HeadExcelBlockWriter(),
@ -20,10 +22,12 @@ public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMak
new LimitingParameterExcelBlockWriter(),
new TimeBalanceExcelBlockWriter()
};
public async Task<Stream> MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken)
public async Task<Stream> MakeReportAsync(AutoGeneratedDailyReportDto report, CancellationToken cancellationToken)
{
using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
using var excelTemplateStream = await Assembly
.GetExecutingAssembly()
.GetTemplateCopyStreamAsync(templateName, cancellationToken);
using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
@ -35,22 +39,6 @@ public class AutoGeneratedDailyReportMakerService : IAutoGeneratedDailyReportMak
return memoryStream;
}
private async Task<Stream> GetExcelTemplateStreamAsync(CancellationToken cancellationToken)
{
var resourceName = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("AutogeneratedDailyReportTemplate.xlsx"))!;
using var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
return memoryStream;
}
private void AddToWorkbook(XLWorkbook workbook, AutoGeneratedDailyReportDto report)
{

View File

@ -26,14 +26,14 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
private readonly ISubsystemOperationTimeService subsystemOperationTimeService;
private readonly ICrudRepository<SubsystemDto> subsystemRepository;
private readonly ILimitingParameterService limitingParameterService;
private readonly IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService;
private readonly IReportMakerService<AutoGeneratedDailyReportDto> autoGeneratedDailyReportMakerService;
public AutoGeneratedDailyReportService(IWellService wellService,
IWellOperationRepository wellOperationRepository,
ISubsystemOperationTimeService subsystemOperationTimeService,
ICrudRepository<SubsystemDto> subsystemRepository,
ILimitingParameterService limitingParameterService,
IAutoGeneratedDailyReportMakerService autoGeneratedDailyReportMakerService)
IReportMakerService<AutoGeneratedDailyReportDto> autoGeneratedDailyReportMakerService)
{
this.wellOperationRepository = wellOperationRepository;
this.wellService = wellService;
@ -44,7 +44,7 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
}
public async Task<PaginationContainer<AutoGeneratedDailyReportInfoDto>> GetListAsync(int idWell,
AutoGeneratedDailyReportRequest request,
FileReportRequest request,
CancellationToken cancellationToken)
{
var result = new PaginationContainer<AutoGeneratedDailyReportInfoDto>
@ -67,19 +67,19 @@ public class AutoGeneratedDailyReportService : IAutoGeneratedDailyReportService
if (datesRange is null)
return result;
if (request.StartDate.HasValue)
if (request.GeDate.HasValue)
{
var startDate = new DateTime(request.StartDate.Value.Year, request.StartDate.Value.Month,
request.StartDate.Value.Day);
var startDate = new DateTime(request.GeDate.Value.Year, request.GeDate.Value.Month,
request.GeDate.Value.Day);
if(startDate.Date >= datesRange.From.Date)
datesRange.From = startDate;
}
if (request.FinishDate.HasValue)
if (request.LeDate.HasValue)
{
var finishDate = new DateTime(request.FinishDate.Value.Year, request.FinishDate.Value.Month,
request.FinishDate.Value.Day);
var finishDate = new DateTime(request.LeDate.Value.Year, request.LeDate.Value.Month,
request.LeDate.Value.Day);
if (finishDate.Date <= datesRange.To.Date)
datesRange.To = finishDate;

View File

@ -95,12 +95,12 @@ public class WorkOperationDetection: Work
{
DateTime = d.DateTime,
IdUser = d.IdUser,
WellDepth = d.WellDepth ?? float.NaN,
Pressure = d.Pressure ?? float.NaN,
HookWeight = d.HookWeight ?? float.NaN,
BlockPosition = d.BlockPosition ?? float.NaN,
BitDepth = d.BitDepth ?? float.NaN,
RotorSpeed = d.RotorSpeed ?? float.NaN,
WellDepth = d.WellDepth,
Pressure = d.Pressure,
HookWeight = d.HookWeight,
BlockPosition = d.BlockPosition,
BitDepth = d.BitDepth,
RotorSpeed = d.RotorSpeed,
})
.OrderBy(d => d.DateTime);

View File

@ -0,0 +1,90 @@
using AsbCloudApp.Data.DrillTestReport;
using AsbCloudApp.Services;
using ClosedXML.Excel;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.DrillTestReport
{
public class DrillTestReportMakerService : IReportMakerService<DrillTestReportDataDto>
{
private readonly string templateName = "DrillTestReportTemplate.xlsx";
private readonly string sheetName = "Лист1";
private readonly int startRowNumber = 8;
public async Task<Stream> MakeReportAsync(DrillTestReportDataDto report, CancellationToken cancellationToken)
{
using var excelTemplateStream = await Assembly.GetExecutingAssembly().GetTemplateCopyStreamAsync(templateName, cancellationToken);
using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled);
AddToWorkbook(workbook, report);
MemoryStream memoryStream = new MemoryStream();
workbook.SaveAs(memoryStream, new SaveOptions { });
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
private void AddToWorkbook(XLWorkbook workbook, DrillTestReportDataDto report)
{
var drillTestEntities = report.Data.Params;
if (!drillTestEntities.Any())
return;
var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetName)
?? throw new FileFormatException($"Книга excel не содержит листа {sheetName}.");
sheet.Cell(4, 2).Value = report.Caption;
sheet.Cell(5, 2)._SetValue(report.Date, setAllBorders: false);
var rowNumber = startRowNumber;
var stepWithMaxDepthSpeed = drillTestEntities.OrderByDescending(p => p.DepthSpeed).FirstOrDefault()!.Step;
var startDepth = report.Data.DepthStart;
var startDate = report.Data.TimeStampStart;
foreach (var drillTestEntity in drillTestEntities)
{
var endDepth = startDepth + (drillTestEntity.DepthDrillStep ?? 0);
var endDateTime = startDate.AddSeconds(drillTestEntity.TimeDrillStep ?? 0);
sheet.Cell(rowNumber, 2).Value = startDepth;
sheet.Cell(rowNumber, 3).Value = endDepth;
sheet.Cell(rowNumber, 4).Value = drillTestEntity.DepthDrillStep;
sheet.Cell(rowNumber, 5).Value = drillTestEntity.Workload;
sheet.Cell(rowNumber, 6).Value = drillTestEntity.Speed;
var cell = sheet.Cell(rowNumber, 7);
cell._SetValue(startDate.DateTime);
cell = sheet.Cell(rowNumber, 8);
cell._SetValue(endDateTime.DateTime);
sheet.Cell(rowNumber, 9).Value = Math.Round((drillTestEntity.TimeDrillStep ?? 0) / (60 * 60), 2);
sheet.Cell(rowNumber, 10).Value = drillTestEntity.DepthSpeed;
if (drillTestEntity.Step == stepWithMaxDepthSpeed)
{
var currentCells = sheet.Row(rowNumber).Cells(1, 10);
currentCells.Style.Fill.BackgroundColor = XLColor.Yellow;
}
startDepth = endDepth;
startDate = endDateTime;
rowNumber++;
}
}
}
}

View File

@ -0,0 +1,99 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.DrillTestReport;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.DrillTestReport
{
public class DrillTestReportService : IDrillTestReportService
{
private readonly IWellService wellService;
private readonly IDrillTestRepository drillTestRepository;
private readonly ITelemetryService telemetryService;
private readonly IReportMakerService<DrillTestReportDataDto> drillTestReportMakerService;
public DrillTestReportService(
IWellService wellService,
IDrillTestRepository drillTestRepository,
ITelemetryService telemetryService,
IReportMakerService<DrillTestReportDataDto> drillTestReportMakerService)
{
this.wellService = wellService;
this.drillTestRepository = drillTestRepository;
this.telemetryService = telemetryService;
this.drillTestReportMakerService = drillTestReportMakerService;
}
public async Task<(string fileName, Stream stream)> GenerateAsync(int idWell, int id, CancellationToken cancellationToken)
{
var well = wellService.GetOrDefault(idWell);
if (well is null)
throw new ArgumentInvalidException(nameof(idWell), $"Well with id: {idWell} does not exist.");
if (well.IdTelemetry is null)
throw new ArgumentInvalidException(nameof(well.IdTelemetry), $"Well with id: {idWell} does not have telemetry.");
var dto = await drillTestRepository.GetAsync(well.IdTelemetry.Value, id, cancellationToken);
var report = new DrillTestReportDataDto()
{
Data = dto,
Caption = string.Format("Месторождение: {0}, куст: {1}, скважина: {2}",
well.Deposit ?? "-",
well.Cluster ?? "-",
well.Caption ?? "-"),
Date = DateTime.Now,
};
var fileName = string.Format("Drill_test_{0}.xlsx", dto.TimeStampStart.ToString("dd.mm.yyyy_HH_MM_ss"));
var stream = await drillTestReportMakerService.MakeReportAsync(report, cancellationToken);
return (fileName, stream);
}
public async Task<PaginationContainer<DrillTestReportInfoDto>> GetListAsync(int idWell, FileReportRequest request, CancellationToken cancellationToken)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
throw new Exception($"Telemetry with idWell: {idWell} does not exist.");
var result = new PaginationContainer<DrillTestReportInfoDto>
{
Skip = request.Skip ?? 0,
Take = request.Take ?? 10,
Items = Enumerable.Empty<DrillTestReportInfoDto>()
};
var reports = new List<DrillTestReportInfoDto>();
var timezone = telemetryService.GetTimezone(telemetry.Id);
var dtos = await drillTestRepository.GetAllAsync(telemetry.Id, request, cancellationToken);
foreach (var dto in dtos)
{
var remoteDateTime = dto.TimeStampStart.ToRemoteDateTime(timezone.Hours);
reports.Add(new DrillTestReportInfoDto
{
FileName = string.Format("Drill_test_{0}", dto.TimeStampStart.DateTime),
DrillDepth = (dto.Params
.Where(p => p.DepthDrillStep.HasValue)
.Sum(x => x.DepthDrillStep) ?? 0) + dto.DepthStart,
DateTime = dto.TimeStampStart.DateTime,
Id = dto.Id,
});
}
result.Items = reports;
return result;
}
}
}

View File

@ -513,7 +513,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
if (state.IdState == idStateCreating)
{
var workId = MakeWorkId(idWell);
if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w => w.Id == workId))
if (!backgroundWorker.Works.Any(w => w.Id == workId))
{
var well = (await wellService.GetOrDefaultAsync(idWell, token))!;
var resultFileName = $"Программа бурения {well.Cluster} {well.Caption}.pdf";
@ -542,7 +542,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
var work = Work.CreateByDelegate(workId, workAction);
work.OnErrorAsync = onErrorAction;
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
backgroundWorker.Enqueue(work);
}
}
}
@ -556,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
private async Task<int> RemoveDrillingProgramAsync(int idWell, CancellationToken token)
{
var workId = MakeWorkId(idWell);
backgroundWorker.WorkStore.TryRemoveFromRunOnceQueue(workId);
backgroundWorker.TryRemoveFromQueue(workId);
var filesIds = await context.Files
.Where(f => f.IdWell == idWell &&

View File

@ -52,12 +52,12 @@ namespace AsbCloudInfrastructure.Services.Email
}
var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
if (!backgroundWorker.WorkStore.RunOnceQueue.Any(w=>w.Id==workId))
if (!backgroundWorker.Works.Any(w=>w.Id==workId))
{
var workAction = MakeEmailSendWorkAction(notification);
var work = Work.CreateByDelegate(workId, workAction);
backgroundWorker.WorkStore.RunOnceQueue.Enqueue(work);
backgroundWorker.Enqueue(work);
}
return Task.CompletedTask;

View File

@ -20,20 +20,23 @@ namespace AsbCloudInfrastructure.Services
{
private readonly IAsbCloudDbContext db;
private readonly ITelemetryService telemetryService;
private readonly FileService fileService;
private readonly IWellService wellService;
private readonly BackgroundWorker backgroundWorkerService;
public int ReportCategoryId { get; private set; }
public ReportService(IAsbCloudDbContext db,
ITelemetryService telemetryService,
IWellService wellService,
ITelemetryService telemetryService,
IWellService wellService,
FileService fileService,
BackgroundWorker backgroundWorkerService)
{
this.db = db;
this.wellService = wellService;
this.backgroundWorkerService = backgroundWorkerService;
this.telemetryService = telemetryService;
this.fileService = fileService;
ReportCategoryId = db.FileCategories
.AsNoTracking()
.First(c => c.Name.Equals("Рапорт"))
@ -69,7 +72,7 @@ namespace AsbCloudInfrastructure.Services
progressHandler.Invoke(arg, id);
};
generator.Make(reportFileName);
var fileInfo = (await fileService.MoveAsync(idWell, idUser, ReportCategoryId, reportFileName, reportFileName, token))!;
progressHandler.Invoke(new
@ -95,7 +98,7 @@ namespace AsbCloudInfrastructure.Services
};
var work = Work.CreateByDelegate(workId, workAction);
backgroundWorkerService.WorkStore.RunOnceQueue.Enqueue(work);
backgroundWorkerService.Enqueue(work);
progressHandler.Invoke(new ReportProgressDto
{
@ -186,6 +189,16 @@ namespace AsbCloudInfrastructure.Services
return generator;
}
}
public async Task<int> DeleteAllOldReportsAsync(TimeSpan lifetime, CancellationToken token)
{
var lifeTimeStartDate = DateTime.UtcNow.Date - lifetime;
var fileIds = await db.ReportProperties
.Where(r => r.File.UploadDate.Date < lifeTimeStartDate)
.Select(r => r.IdFile)
.ToArrayAsync(token);
return await fileService.DeleteAsync(fileIds, token);
}
}
}

View File

@ -1,4 +1,6 @@
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
@ -18,12 +20,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
{
protected readonly IAsbCloudDbContext db;
protected readonly ITelemetryService telemetryService;
protected readonly TelemetryDataCache<TDto> telemetryDataCache;
protected readonly ITelemetryDataCache<TDto> telemetryDataCache;
public TelemetryDataBaseService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
TelemetryDataCache<TDto> telemetryDataCache)
ITelemetryDataCache<TDto> telemetryDataCache)
{
this.db = db;
this.telemetryService = telemetryService;
@ -203,41 +205,62 @@ namespace AsbCloudInfrastructure.Services.SAUB
}
/// <inheritdoc/>
public virtual async Task<DatesRangeDto?> GetRangeAsync(
int idWell,
DateTimeOffset start,
DateTimeOffset end,
CancellationToken token)
public async Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
?? throw new ArgumentInvalidException(nameof(idWell), $"По скважине id:{idWell} нет телеметрии");
if ((DateTimeOffset.UtcNow - geDate) < TimeSpan.FromHours(12))
{
// пробуем обойтись кешем
var cechedRange = telemetryDataCache.GetOrDefaultCachedaDateRange(telemetry.Id);
if (cechedRange?.From <= geDate)
{
var datesRange = new DatesRangeDto
{
From = geDate.DateTime,
To = cechedRange.To
};
if (leDate.HasValue && leDate > geDate)
datesRange.To = leDate.Value.Date;
return datesRange;
}
}
var query = db.Set<TEntity>()
.Where(entity => entity.IdTelemetry == telemetry.Id)
.Where(entity => entity.DateTime >= geDate.ToUniversalTime());
if(leDate.HasValue)
query = query.Where(entity => entity.DateTime <= leDate.Value.ToUniversalTime());
var gquery = query
.GroupBy(entity => entity.IdTelemetry)
.Select(group => new
{
MinDate = group.Min(entity => entity.DateTime),
MaxDate = group.Max(entity => entity.DateTime),
});
var result = await gquery.FirstOrDefaultAsync(token);
if (result is null)
return null;
var range = new DatesRangeDto
{
From = result.MinDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime,
To = result.MaxDate.ToOffset(TimeSpan.FromHours(telemetry.TimeZone!.Hours)).DateTime,
};
return range;
}
public DatesRangeDto? GetRange(int idWell)
{
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
if (telemetry is null)
return default;
var timezone = telemetryService.GetTimezone(telemetry.Id);
var startUtc = start.ToOffset(TimeSpan.Zero);
var endUtc = end.ToOffset(TimeSpan.Zero);
var dbSet = db.Set<TEntity>();
var query = dbSet
.Where(i => i.IdTelemetry == telemetry.Id)
.Where(i => i.DateTime >= startUtc)
.Where(i => i.DateTime <= endUtc)
.GroupBy(i => i.IdTelemetry)
.Select(g => new
{
DateStart = g.Min(i => i.DateTime),
DateEnd = g.Max(i => i.DateTime),
});
var data = await query.FirstOrDefaultAsync(token);
if (data is null)
return default;
return new DatesRangeDto
{
From = data.DateStart.ToRemoteDateTime(timezone.Hours),
To = data.DateEnd.ToRemoteDateTime(timezone.Hours),
};
return telemetryDataCache.GetOrDefaultDataDateRange(telemetry.Id);
}
public abstract TDto Convert(TEntity src, double timezoneOffset);

View File

@ -6,26 +6,24 @@ using System.Linq;
using Microsoft.EntityFrameworkCore;
using Mapster;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AsbCloudInfrastructure.Background;
using System.Threading;
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure.Services.SAUB
{
public class TelemetryDataCache<TDto>
where TDto : AsbCloudApp.Data.ITelemetryData
public class TelemetryDataCache<TDto> : ITelemetryDataCache<TDto> where TDto : AsbCloudApp.Data.ITelemetryData
{
class TelemetryDataCacheItem
{
public TDto? FirstByDate { get; init; }
public TDto FirstByDate { get; init; } = default!;
public CyclycArray<TDto> LastData { get; init; } = null!;
public double TimezoneHours { get; init; } = 5;
}
private IServiceProvider provider = null!;
private const int activeWellCapacity = 12 * 60 * 60;
private const int doneWellCapacity = 65 * 60;
@ -48,14 +46,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
instance = new TelemetryDataCache<TDto>();
var worker = provider.GetRequiredService<BackgroundWorker>();
var workId = $"Telemetry cache loading from DB {typeof(TEntity).Name}";
var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) => {
var work = Work.CreateByDelegate(workId, async (workId, provider, onProgress, token) =>
{
var db = provider.GetRequiredService<IAsbCloudDbContext>();
await instance.InitializeCacheFromDBAsync<TEntity>(db, onProgress, token);
});
work.Timeout = TimeSpan.FromMinutes(15);
worker.WorkStore.RunOnceQueue.Enqueue(work);
worker.Enqueue(work);
}
instance.provider = provider;
return instance;
}
@ -69,10 +67,9 @@ namespace AsbCloudInfrastructure.Services.SAUB
if (!range.Any())
return;
var newItems = range
.OrderBy(i => i.DateTime);
range = range.OrderBy(x => x.DateTime);
foreach (var item in newItems)
foreach (var item in range)
item.IdTelemetry = idTelemetry;
TelemetryDataCacheItem cacheItem;
@ -87,12 +84,12 @@ namespace AsbCloudInfrastructure.Services.SAUB
{
cacheItem = caches.GetOrAdd(idTelemetry, _ => new TelemetryDataCacheItem()
{
FirstByDate = newItems.ElementAt(0),
FirstByDate = range.ElementAt(0),
LastData = new CyclycArray<TDto>(activeWellCapacity)
});
}
cacheItem.LastData.AddRange(newItems);
cacheItem.LastData.AddRange(range);
}
/// <summary>
@ -107,14 +104,14 @@ namespace AsbCloudInfrastructure.Services.SAUB
/// <returns></returns>
public IEnumerable<TDto>? GetOrDefault(int idTelemetry, DateTime dateBegin, double intervalSec = 600d, int approxPointsCount = 1024)
{
if(!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
return null;
var cacheLastData = cacheItem.LastData;
if (!cacheLastData.Any() || cacheLastData[0].DateTime > dateBegin)
return null;
var dateEnd = dateBegin.AddSeconds(intervalSec);
var items = cacheLastData
.Where(i => i.DateTime >= dateBegin && i.DateTime <= dateEnd);
@ -127,7 +124,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
return items;
}
public TDto? GetLastOrDefault(int idTelemetry)
public virtual TDto? GetLastOrDefault(int idTelemetry)
{
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
return default;
@ -141,7 +138,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
return null;
var from = cacheItem.FirstByDate?.DateTime;
if(!cacheItem.LastData.Any())
if (!cacheItem.LastData.Any())
return null;
var to = cacheItem.LastData[^1].DateTime;
@ -150,42 +147,74 @@ namespace AsbCloudInfrastructure.Services.SAUB
return new DatesRangeDto { From = from.Value, To = to };
}
public DatesRangeDto? GetOrDefaultCachedaDateRange(int idTelemetry)
{
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
return null;
if (cacheItem.LastData.Count < 2)
return null;
var to = cacheItem.LastData[^1].DateTime;
var from = cacheItem.LastData[0].DateTime;
return new DatesRangeDto { From = from, To = to };
}
public (TDto First, TDto Last)? GetOrDefaultFirstLast(int idTelemetry)
{
if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem))
return null;
if (!cacheItem.LastData.Any())
return null;
var last = cacheItem.LastData[^1];
var first = cacheItem.FirstByDate;
return (first, last);
}
private async Task InitializeCacheFromDBAsync<TEntity>(IAsbCloudDbContext db, Action<string, double?> onProgress, CancellationToken token)
where TEntity : class, AsbCloudDb.Model.ITelemetryData
{
if (isLoading)
throw new Exception("Multiple cache loading detected.");
isLoading = true;
var defaultTimeout = db.Database.GetCommandTimeout();
db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
Well[] wells = await db.Set<Well>()
.Include(well => well.Telemetry)
.Include(well => well.Cluster)
.Where(well => well.IdTelemetry != null)
.ToArrayAsync(token);
if (isLoading)
throw new Exception("Multiple cache loading detected.");
var count = wells.Length;
var i = 0d;
foreach (Well well in wells)
{
var capacity = well.IdState == 1
? activeWellCapacity
: doneWellCapacity;
try
{
isLoading = true;
Well[] wells = await db.Set<Well>()
.Include(well => well.Telemetry)
.Include(well => well.Cluster)
.Where(well => well.IdTelemetry != null)
.ToArrayAsync(token);
var idTelemetry = well.IdTelemetry!.Value;
var hoursOffset = well.Timezone.Hours;
onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++/count);
var cacheItem = await GetOrDefaultCacheDataFromDbAsync<TEntity>(db, idTelemetry, capacity, hoursOffset, token);
if(cacheItem is not null)
caches.TryAdd(idTelemetry, cacheItem);
var count = wells.Length;
var i = 0d;
foreach (Well well in wells)
{
var capacity = well.IdState == 1
? activeWellCapacity
: doneWellCapacity;
var idTelemetry = well.IdTelemetry!.Value;
var hoursOffset = well.Timezone.Hours;
onProgress($"Loading for well: {well.Cluster?.Caption}/{well.Caption} (capacity:{capacity}) idTelemetry:{idTelemetry}", i++ / count);
var cacheItem = await GetOrDefaultCacheDataFromDbAsync<TEntity>(db, idTelemetry, capacity, hoursOffset, token);
if (cacheItem is not null)
caches.TryAdd(idTelemetry, cacheItem);
}
}
finally
{
isLoading = false;
db.Database.SetCommandTimeout(defaultTimeout);
}
isLoading = false;
db.Database.SetCommandTimeout(defaultTimeout);
}
private static async Task<TelemetryDataCacheItem?> GetOrDefaultCacheDataFromDbAsync<TEntity>(IAsbCloudDbContext db, int idTelemetry, int capacity, double hoursOffset, CancellationToken token)
@ -212,7 +241,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
var dtos = entities
.AsEnumerable()
.Reverse()
.Select(entity => {
.Select(entity =>
{
var dto = entity.Adapt<TDto>();
dto.DateTime = entity.DateTime.ToRemoteDateTime(hoursOffset);
return dto;

View File

@ -1,6 +1,7 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
@ -26,7 +27,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
IAsbCloudDbContext db,
ITelemetryService telemetryService,
ITelemetryUserService telemetryUserService,
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache)
{
this.telemetryUserService = telemetryUserService;
@ -43,37 +44,37 @@ namespace AsbCloudInfrastructure.Services.SAUB
.Where(t => t.BlockPosition > 0.0001)
.Where(t => t.WellDepth > 0.0001)
.Where(t => t.Mode != null)
.Where(t => modes.Contains(t.Mode!.Value))
.Where(t => modes.Contains(t.Mode))
.Where(t => t.WellDepth - t.BitDepth < 0.01)
.GroupBy(t => new {
t.DateTime.Hour,
WellDepthX10 = Math.Truncate(t.WellDepth!.Value * 10),
WellDepthX10 = Math.Truncate(t.WellDepth * 10),
t.Mode,
t.IdFeedRegulator})
.Select(g => new TelemetryDataSaubStatDto
{
Count = g.Count(),
IdMode = g.Key.Mode??0,
IdMode = g.Key.Mode,
IdFeedRegulator = g.Key.IdFeedRegulator,
DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
WellDepthMin = g.Min(t => t.WellDepth!.Value),
WellDepthMax = g.Max(t => t.WellDepth!.Value),
WellDepthMin = g.Min(t => t.WellDepth),
WellDepthMax = g.Max(t => t.WellDepth),
Pressure = g.Average(t => t.Pressure!.Value),
Pressure = g.Average(t => t.Pressure),
PressureSp = g.Average(t => t.PressureSp!.Value),
PressureIdle = g.Average(t => t.PressureIdle!.Value),
PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
PressureDelta = g.Average(t => t.Pressure!.Value - t.PressureIdle!.Value),
PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),
AxialLoad = g.Average(t => t.AxialLoad!.Value),
AxialLoad = g.Average(t => t.AxialLoad),
AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),
RotorTorque = g.Average(t => t.RotorTorque!.Value),
RotorTorque = g.Average(t => t.RotorTorque),
RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),

View File

@ -1,4 +1,5 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
@ -11,7 +12,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryDataSpinService(
IAsbCloudDbContext db,
ITelemetryService telemetryService,
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataCache)
: base(db, telemetryService, telemetryDataCache)
{ }

View File

@ -1,5 +1,6 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
@ -18,7 +19,8 @@ namespace AsbCloudInfrastructure.Services.SAUB
{
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly TelemetryDataCache<TelemetryDataSaubDto> dataSaubCache;
//TODO: методы использующие ITelemetryDataCache, скорее всего, тут не нужны
private readonly ITelemetryDataCache<TelemetryDataSaubDto> dataSaubCache;
private readonly ITimezoneService timezoneService;
public ITimezoneService TimeZoneService => timezoneService;
@ -26,7 +28,7 @@ namespace AsbCloudInfrastructure.Services.SAUB
public TelemetryService(
IAsbCloudDbContext db,
IMemoryCache memoryCache,
TelemetryDataCache<TelemetryDataSaubDto> dataSaubCache,
ITelemetryDataCache<TelemetryDataSaubDto> dataSaubCache,
ITimezoneService timezoneService)
{
this.db = db;

View File

@ -26,7 +26,7 @@ public class WorkSubsystemOperationTimeCalc : Work
public WorkSubsystemOperationTimeCalc()
: base("Subsystem operation time calc")
{
Timeout = TimeSpan.FromMinutes(20);
Timeout = TimeSpan.FromMinutes(30);
}
protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
@ -55,10 +55,12 @@ public class WorkSubsystemOperationTimeCalc : Work
{
IdTelemetry = outer,
inner.SingleOrDefault()?.LastDate,
});
})
.OrderByDescending(i => i.IdTelemetry);
var count = telemetryLastDetectedDates.Count();
var i = 0d;
foreach (var item in telemetryLastDetectedDates)
{
onProgressCallback($"Start handling telemetry: {item.IdTelemetry} from {item.LastDate}", i++ / count);
@ -277,11 +279,11 @@ public class WorkSubsystemOperationTimeCalc : Work
.Where(d => d.DateTime <= dateEnd)
.Where(d => d.WellDepth != null)
.Where(d => d.WellDepth > 0)
.GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10))
.GroupBy(d => Math.Ceiling(d.WellDepth * 10))
.Select(g => new
{
DateMin = g.Min(d => d.DateTime),
DepthMin = g.Min(d => d.WellDepth) ?? 0,
DepthMin = g.Min(d => d.WellDepth),
})
.OrderBy(i => i.DateMin)
.ToArrayAsync(token);

View File

@ -36,7 +36,7 @@ public class WellInfoService
var operationsStatService = services.GetRequiredService<IOperationsStatService>();
var processMapPlanWellDrillingRepository = services.GetRequiredService<IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto>>();
var subsystemOperationTimeService = services.GetRequiredService<ISubsystemOperationTimeService>();
var telemetryDataSaubCache = services.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>();
var telemetryDataSaubCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
var messageHub = services.GetRequiredService<IIntegrationEventHandler<UpdateWellInfoEvent>>();
var wells = await wellService.GetAllAsync(token);
@ -82,7 +82,7 @@ public class WellInfoService
}
var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id);
var wellLastFactSection = wellOperationsStat?.Sections.LastOrDefault(s => s.Fact is not null);
var wellLastFactSection = wellOperationsStat?.Sections.OrderBy(s => s.Fact?.WellDepthStart).LastOrDefault(s => s.Fact is not null);
currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd;
var wellProcessMaps = processMapPlanWellDrillings
@ -180,16 +180,16 @@ public class WellInfoService
public IEnumerable<int> IdsCompanies { get; set; } = null!;
}
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
private readonly TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
private readonly ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
private readonly IWitsRecordRepository<Record7Dto> witsRecord7Repository;
private readonly IWitsRecordRepository<Record1Dto> witsRecord1Repository;
private readonly IGtrRepository gtrRepository;
private static IEnumerable<WellMapInfoWithComanies> WellMapInfo = Enumerable.Empty<WellMapInfoWithComanies>();
public WellInfoService(
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache,
IWitsRecordRepository<Record7Dto> witsRecord7Repository,
IWitsRecordRepository<Record1Dto> witsRecord1Repository,
IGtrRepository gtrRepository)

View File

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.SAUB;
using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure.Services.WellOperationService;
@ -19,10 +20,10 @@ public class OperationsStatService : IOperationsStatService
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly IWellService wellService;
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService,
TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
public OperationsStatService(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
{
this.db = db;
this.memoryCache = memoryCache;

View File

@ -33,7 +33,8 @@ namespace AsbCloudInfrastructure.Services
.Include(w => w.Telemetry)
.Include(w => w.WellType)
.Include(w => w.RelationCompaniesWells)
.ThenInclude(r => r.Company);
.ThenInclude(r => r.Company)
.AsNoTracking();
public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService, WellInfoService wellInfoService)
: base(db, memoryCache, MakeQueryWell)
@ -105,19 +106,18 @@ namespace AsbCloudInfrastructure.Services
public async Task<WellMapInfoWithTelemetryStat?> GetOrDefaultStatAsync(int idWell, CancellationToken token)
{
var dto = wellInfoService.FirstOrDefault(well => well.Id == idWell);
if (dto is not null)
return dto;
var request = new WellRequest{Ids = new[] { idWell }};
var entities = await GetEntitiesAsync(request, token);
var entity = entities.FirstOrDefault();
if (entity is null)
var well = await GetOrDefaultAsync(idWell, token);
if (well is null)
return null;
var wellInfo = wellInfoService.FirstOrDefault(well => well.Id == idWell);
dto = entity.Adapt<WellMapInfoWithTelemetryStat>();
return dto;
if (wellInfo is null)
return well.Adapt<WellMapInfoWithTelemetryStat>();
wellInfo.IdState = well.IdState;
return wellInfo;
}
public async Task<IEnumerable<WellDto>> GetAsync(WellRequest request, CancellationToken token)

View File

@ -1,91 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using AsbCloudInfrastructure.Services.SAUB;
namespace AsbCloudInfrastructure.Services;
public class WellboreService : IWellboreService
{
const string WellboreNameFormat = "Ствол {0}";
private readonly IWellService wellService;
private readonly IWellOperationRepository wellOperationRepository;
private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
public WellboreService(IWellService wellService, IWellOperationRepository wellOperationRepository)
public WellboreService(
IWellService wellService,
IWellOperationRepository wellOperationRepository,
ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
{
this.wellService = wellService;
this.wellOperationRepository = wellOperationRepository;
}
public async Task<WellboreDto?> GetWellboreAsync(int idWell, int idSection, CancellationToken cancellationToken)
{
var request = new WellboreRequest
{
Ids = new (int, int?)[] { (idWell, idSection) },
Take = 1,
};
var data = await GetWellboresAsync(request, cancellationToken);
return data.FirstOrDefault();
this.telemetryDataCache = telemetryDataCache;
}
public async Task<IEnumerable<WellboreDto>> GetWellboresAsync(WellboreRequest request,
public async Task<IEnumerable<WellboreDto>> GetWellboresAsync(IEnumerable<int> idsWells,
CancellationToken token)
{
var wellbores = new List<WellboreDto>(request.Ids.Count());
var skip = request.Skip ?? 0;
var take = request.Take ?? 10;
var wellRequest = new WellRequest { Ids = idsWells };
var wells = await wellService.GetAsync(wellRequest, token);
var sections = wellOperationRepository.GetSectionTypes()
.ToDictionary(w => w.Id, w => w);
var rowSections = await wellOperationRepository.GetSectionsAsync(idsWells, token);
var groupedSections = rowSections
.Where(section => section.IdType == 1)
.GroupBy(s => s.IdWell);
var ids = request.Ids.GroupBy(i => i.idWell, i => i.idSection);
var wellbores = wells
.SelectMany(well => {
var wellSections = groupedSections.FirstOrDefault(group => group.Key == well.Id);
if (wellSections is not null)
return MakeWellboreBySections(wellSections, well);
else
return MakeWellboreDefault(well);
})
.OrderBy(w => w.Well.Id)
.ThenBy(w => w.Id);
var idsWells = request.Ids.Select(i => i.idWell);
return wellbores;
}
var allSections = await wellOperationRepository.GetSectionsAsync(idsWells, token);
private IEnumerable<WellboreDto> MakeWellboreDefault(WellDto well)
{
var wellbore = new WellboreDto {
Id = 1,
Name = string.Format(WellboreNameFormat, 1),
Well = well,
};
//if(well.)
foreach (var id in ids)
if(well.IdTelemetry is not null)
{
var well = await wellService.GetOrDefaultAsync(id.Key, token);
var dataCache = telemetryDataCache.GetOrDefaultFirstLast(well.IdTelemetry.Value);
if (dataCache is not null)
{
wellbore.DateStart = dataCache.Value.First.DateTime;
wellbore.DepthStart = dataCache.Value.First.WellDepth!.Value;
if (well is null)
continue;
var wellTimezoneOffset = TimeSpan.FromHours(well.Timezone.Hours);
var wellFactSections = allSections
.Where(section => section.IdWell == id.Key)
.Where(section => section.IdType == WellOperation.IdOperationTypeFact);
var idsSections = id
.Where(i => i.HasValue)
.Select(i => i!.Value);
if (idsSections.Any())
wellFactSections = wellFactSections
.Where(section => idsSections.Contains(section.IdWellSectionType));
var wellWellbores = wellFactSections.Select(section => new WellboreDto {
Id = section.IdWellSectionType,
Name = sections[section.IdWellSectionType].Caption,
Well = well.Adapt<WellWithTimezoneDto>(),
DateStart = section.DateStart.ToOffset(wellTimezoneOffset),
DateEnd = section.DateEnd.ToOffset(wellTimezoneOffset),
DepthStart = section.DepthStart,
DepthEnd = section.DepthEnd,
});
wellbores.AddRange(wellWellbores);
wellbore.DateEnd = dataCache.Value.Last.DateTime;
wellbore.DepthEnd = dataCache.Value.Last.WellDepth!.Value;
}
}
return wellbores
.OrderBy(w => w.Well.Id).ThenBy(w => w.Id)
.Skip(skip).Take(take);
}
return new[] { wellbore };
}
private IEnumerable<WellboreDto> MakeWellboreBySections(IEnumerable<SectionByOperationsDto> sections, WellDto well)
{
var orderedSections = sections.OrderBy(s => s.DateStart);
var wellbores = new List<WellboreDto>();
int wellboreId = 1;
SectionByOperationsDto? preSection = null;
WellboreDto? wellbore = null;
foreach (var section in orderedSections)
{
if (wellbore is null || wellbore.DepthEnd > section.DepthStart)
{
wellbore = new WellboreDto
{
Name = string.Format(WellboreNameFormat, wellboreId),
Id = wellboreId,
Well = well,
DateStart = section.DateStart,
DateEnd = section.DateEnd,
DepthStart = section.DepthStart,
DepthEnd = section.DepthEnd,
};
wellbores.Add(wellbore);
wellboreId++;
}
else
{
wellbore.DepthEnd = section.DepthEnd;
wellbore.DateEnd = section.DateEnd;
}
preSection = section;
}
if (wellbore is not null)
{
if (well.IdTelemetry is not null)
{
var dataCache = telemetryDataCache.GetOrDefaultFirstLast(well.IdTelemetry.Value);
if (dataCache is not null)
{
wellbore.DateEnd = dataCache.Value.Last.DateTime;
wellbore.DepthEnd = dataCache.Value.Last.WellDepth!.Value;
}
}
}
return wellbores;
}
}

View File

@ -1,8 +1,6 @@
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.DetectOperations;
using AsbCloudInfrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
@ -10,8 +8,9 @@ using System.Threading.Tasks;
using System.Threading;
using AsbCloudInfrastructure.Background;
using AsbCloudApp.Data.SAUB;
using AsbCloudInfrastructure.Services.SAUB;
using AsbCloudInfrastructure.Services.Subsystems;
using AsbCloudDb;
using AsbCloudApp.Repositories;
namespace AsbCloudInfrastructure
{
@ -23,19 +22,19 @@ namespace AsbCloudInfrastructure
var provider = scope.ServiceProvider;
var context = provider.GetRequiredService<IAsbCloudDbContext>();
context.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
context.Database.Migrate();
context.Database.EnshureCreatedAndMigrated();
// TODO: Сделать инициализацию кеша телеметрии более явной.
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>();
_ = provider.GetRequiredService<TelemetryDataCache<TelemetryDataSpinDto>>();
_ = provider.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
_ = provider.GetRequiredService<ITelemetryDataCache<TelemetryDataSpinDto>>();
var backgroundWorker = provider.GetRequiredService<BackgroundWorker>();
backgroundWorker.WorkStore.AddPeriodic<WellInfoService.WorkWellInfoUpdate>(TimeSpan.FromMinutes(30));
backgroundWorker.WorkStore.AddPeriodic<WorkOperationDetection>(TimeSpan.FromMinutes(15));
backgroundWorker.WorkStore.AddPeriodic<WorkSubsystemOperationTimeCalc>(TimeSpan.FromMinutes(30));
backgroundWorker.WorkStore.AddPeriodic<WorkLimitingParameterCalc>(TimeSpan.FromMinutes(30));
backgroundWorker.WorkStore.AddPeriodic(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
var backgroundWorker = provider.GetRequiredService<PeriodicBackgroundWorker>();
backgroundWorker.Add<WorkToDeleteOldReports>(TimeSpan.FromDays(1));
backgroundWorker.Add<WellInfoService.WorkWellInfoUpdate>(TimeSpan.FromMinutes(30));
backgroundWorker.Add<WorkOperationDetection>(TimeSpan.FromMinutes(15));
backgroundWorker.Add<WorkSubsystemOperationTimeCalc>(TimeSpan.FromMinutes(30));
backgroundWorker.Add<WorkLimitingParameterCalc>(TimeSpan.FromMinutes(30));
backgroundWorker.Add(MakeMemoryMonitoringWork(), TimeSpan.FromMinutes(1));
var notificationBackgroundWorker = provider.GetRequiredService<NotificationBackgroundWorker>();

View File

@ -8,119 +8,123 @@ namespace AsbCloudInfrastructure;
internal static class XLExtentions
{
internal static IXLRange _SetValue(this IXLRange range, object value)
{
var mergedRange = range.Merge();
mergedRange.FirstCell()._SetValue(value);
var colWidth = mergedRange.FirstCell().WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * mergedRange.FirstCell().Style.Font.FontSize);
if (value is string valueString && valueString.Length > maxCharsToWrap)
{
var row = mergedRange.FirstCell().WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + valueString.Length / maxCharsToWrap);
}
internal static IXLRange _SetValue(this IXLRange range, object value)
{
var mergedRange = range.Merge();
mergedRange.FirstCell()._SetValue(value);
var colWidth = mergedRange.FirstCell().WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * mergedRange.FirstCell().Style.Font.FontSize);
if (value is string valueString && valueString.Length > maxCharsToWrap)
{
var row = mergedRange.FirstCell().WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + valueString.Length / maxCharsToWrap);
}
mergedRange.Style.SetAllBorders()
.Alignment.SetWrapText(true);
return mergedRange;
}
mergedRange.Style.SetAllBorders()
.Alignment.SetWrapText(true);
return mergedRange;
}
internal static IXLCell _SetValue(this IXLCell cell, object value)
{
switch (value)
{
case DateTime dateTime:
cell._SetValue(dateTime);
break;
case IFormattable formattable:
cell._SetValue(formattable);
break;
case string valueString:
cell._SetValue(valueString);
break;
default:
cell.Value = value;
break;
}
internal static IXLCell _SetValue(this IXLCell cell, object value)
{
switch (value)
{
case DateTime dateTime:
cell._SetValue(dateTime);
break;
case IFormattable formattable:
cell._SetValue(formattable);
break;
case string valueString:
cell._SetValue(valueString);
break;
default:
cell.Value = value;
break;
}
return cell;
}
return cell;
}
internal static IXLCell _SetValue(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
internal static IXLCell _SetValue(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
return cell;
}
return cell;
}
internal static IXLCell _ValueNoBorder(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style.Alignment.WrapText = true;
internal static IXLCell _ValueNoBorder(this IXLCell cell, string value, bool adaptRowHeight = false)
{
cell.Value = value;
cell.Style.Alignment.WrapText = true;
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
cell.Value = value;
if (adaptRowHeight)
{
var colWidth = cell.WorksheetColumn().Width;
var maxCharsToWrap = colWidth / (0.1d * cell.Style.Font.FontSize);
if (value.Length > maxCharsToWrap)
{
var row = cell.WorksheetRow();
var baseHeight = row.Height;
row.Height = 0.5d * baseHeight * Math.Ceiling(1d + value.Length / maxCharsToWrap);
}
}
return cell;
}
return cell;
}
internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
internal static IXLCell _SetValue(this IXLCell cell, DateTime value, string dateFormat = "DD.MM.YYYY HH:MM:SS", bool setAllBorders = true)
{
cell.Value = value;
if (setAllBorders == true)
{
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
}
cell.Value = value;
cell.DataType = XLDataType.DateTime;
cell.Style.DateFormat.Format = "DD.MM.YYYY HH:MM:SS";
cell.Value = value;
return cell;
}
cell.DataType = XLDataType.DateTime;
cell.Style.DateFormat.Format = "DD.MM.YYYY HH:MM:SS";
internal static IXLCell _SetValue(this IXLCell cell, IFormattable value, string format = "0.00")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
return cell;
}
cell.Value = value;
internal static IXLCell _SetValue(this IXLCell cell, IFormattable value, string format = "0.00")
{
cell.Value = value;
cell.Style
.SetAllBorders()
.Alignment.WrapText = true;
cell.DataType = XLDataType.Number;
cell.Style.NumberFormat.Format = "0.00";
cell.Value = value;
return cell;
}
cell.DataType = XLDataType.Number;
cell.Style.NumberFormat.Format = "0.00";
return cell;
}
public static IXLCell SetVal(this IXLCell cell, double? value, string format = "0.00")
{
@ -158,60 +162,60 @@ internal static class XLExtentions
}
internal static IXLStyle SetAllBorders(this IXLStyle style, XLBorderStyleValues borderStyle = XLBorderStyleValues.Thin)
{
style.Border.RightBorder = borderStyle;
style.Border.LeftBorder = borderStyle;
style.Border.TopBorder = borderStyle;
style.Border.BottomBorder = borderStyle;
style.Border.InsideBorder = borderStyle;
style.Border.OutsideBorder = borderStyle;
return style;
}
{
style.Border.RightBorder = borderStyle;
style.Border.LeftBorder = borderStyle;
style.Border.TopBorder = borderStyle;
style.Border.BottomBorder = borderStyle;
style.Border.InsideBorder = borderStyle;
style.Border.OutsideBorder = borderStyle;
return style;
}
internal static IXLStyle SetBaseFont(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 10;
return style;
}
internal static IXLStyle SetBaseFont(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 10;
return style;
}
internal static IXLStyle SetH1(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 14;
return style;
}
internal static IXLStyle SetH1(this IXLStyle style)
{
style.Font.FontName = "Calibri";
style.Font.FontSize = 14;
return style;
}
/// <summary>
/// Костыль исправляющий проблему в библиотеке IXLRange Range(this IXLWorksheet, IXLAddress, IXLAddress) с кастингом IXLAddress к XLAddress.
/// </summary>
/// <param name="sheet"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
internal static IXLRange _Range(this IXLWorksheet sheet, CellAddress begin, CellAddress end)
=> sheet.Range(begin.RowNumber, begin.ColumnNumber, end.RowNumber, end.ColumnNumber);
internal static T? GetCellValue<T>(this IXLCell cell)
{
try
{
if (cell.IsEmpty() && default(T) == null)
return default;
if (typeof(T) != typeof(DateTime))
return (T)Convert.ChangeType(cell.GetFormattedString(), typeof(T), CultureInfo.InvariantCulture);
/// <summary>
/// Костыль исправляющий проблему в библиотеке IXLRange Range(this IXLWorksheet, IXLAddress, IXLAddress) с кастингом IXLAddress к XLAddress.
/// </summary>
/// <param name="sheet"></param>
/// <param name="begin"></param>
/// <param name="end"></param>
/// <returns></returns>
internal static IXLRange _Range(this IXLWorksheet sheet, CellAddress begin, CellAddress end)
=> sheet.Range(begin.RowNumber, begin.ColumnNumber, end.RowNumber, end.ColumnNumber);
internal static T? GetCellValue<T>(this IXLCell cell)
{
try
{
if (cell.IsEmpty() && default(T) == null)
return default;
if (typeof(T) != typeof(DateTime))
return (T)Convert.ChangeType(cell.GetFormattedString(), typeof(T), CultureInfo.InvariantCulture);
if (cell.Value is DateTime dateTime)
return (T)(object)dateTime;
return (T)(object)DateTime.FromOADate((double)cell.Value);
}
catch
{
throw new FileFormatException(
$"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
}
}
}
catch
{
throw new FileFormatException(
$"Лист '{cell.Worksheet.Name}'. Ячейка: ({cell.Address.RowNumber},{cell.Address.ColumnNumber}) содержит некорректное значение");
}
}
}

View File

@ -15,6 +15,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="MockQueryable.Moq" Version="6.0.1" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -48,11 +48,26 @@ namespace AsbCloudWebApi.Tests.Middlware
throw new NotImplementedException();
}
public DatesRangeDto? GetRange(int idWell, DateTimeOffset start, DateTimeOffset end)
{
throw new NotImplementedException();
}
public DatesRangeDto? GetRange(int idWell)
{
throw new NotImplementedException();
}
public Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset start, DateTimeOffset end, CancellationToken token)
{
throw new NotImplementedException();
}
public Task<DatesRangeDto?> GetRangeAsync(int idWell, DateTimeOffset geDate, DateTimeOffset? leDate, CancellationToken token)
{
throw new NotImplementedException();
}
public Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token) => throw new NotImplementedException();
public Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)

View File

@ -0,0 +1,113 @@
using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace AsbCloudWebApi.Tests.Services;
public class BackgroundWorkerTest
{
private IServiceProvider provider;
private BackgroundWorker service;
public BackgroundWorkerTest()
{
provider = Substitute.For<IServiceProvider, ISupportRequiredService>();
var serviceScope = Substitute.For<IServiceScope>();
var serviceScopeFactory = Substitute.For<IServiceScopeFactory>();
serviceScopeFactory.CreateScope().Returns(serviceScope);
((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
service = new BackgroundWorker(provider);
typeof(BackgroundWorker)
.GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(service, TimeSpan.FromMilliseconds(1));
}
[Fact]
public async Task Enqueue_n_works()
{
var workCount = 10;
var result = 0;
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
{
result++;
return Task.Delay(1);
}
//act
for (int i = 0; i < workCount; i++)
{
var work = Work.CreateByDelegate(i.ToString(), workAction);
service.Enqueue(work);
}
await service.ExecuteTask;
//assert
Assert.Equal(workCount, result);
}
[Fact]
public async Task Enqueue_continues_after_exceptions()
{
var expectadResult = 42;
var result = 0;
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
{
result = expectadResult;
return Task.CompletedTask;
}
var goodWork = Work.CreateByDelegate("", workAction);
Task failAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
=> throw new Exception();
var badWork = Work.CreateByDelegate("", failAction);
badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
//act
service.Enqueue(badWork);
service.Enqueue(goodWork);
await service.ExecuteTask;
//assert
Assert.Equal(expectadResult, result);
Assert.Equal(1, service.Felled.Count);
Assert.Equal(1, service.Done.Count);
}
[Fact]
public async Task TryRemove()
{
var workCount = 5;
var result = 0;
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
{
result++;
return Task.Delay(10);
}
//act
for (int i = 0; i < workCount; i++)
{
var work = Work.CreateByDelegate(i.ToString(), workAction);
service.Enqueue(work);
}
var removed = service.TryRemoveFromQueue((workCount - 1).ToString());
await service.ExecuteTask;
//assert
Assert.True(removed);
Assert.Equal(workCount - 1, result);
Assert.Equal(workCount - 1, service.Done.Count);
}
}

View File

@ -366,7 +366,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
Assert.Equal(2, state.IdState);
backgroundWorkerMock.Verify(s => s.Push(It.IsAny<Work>()));
backgroundWorkerMock.Verify(s => s.Enqueue(It.IsAny<Work>()));
}
[Fact]

View File

@ -0,0 +1,97 @@
using AsbCloudInfrastructure.Background;
using DocumentFormat.OpenXml.Drawing.Charts;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace AsbCloudWebApi.Tests.Services;
public class PeriodicBackgroundWorkerTest
{
private IServiceProvider provider;
private PeriodicBackgroundWorker service;
public PeriodicBackgroundWorkerTest()
{
provider = Substitute.For<IServiceProvider, ISupportRequiredService>();
var serviceScope = Substitute.For<IServiceScope>();
var serviceScopeFactory = Substitute.For<IServiceScopeFactory>();
serviceScopeFactory.CreateScope().Returns(serviceScope);
((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory);
service = new PeriodicBackgroundWorker(provider);
typeof(PeriodicBackgroundWorker)
.GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)?
.SetValue(service, TimeSpan.FromMilliseconds(1));
typeof(PeriodicBackgroundWorker)
.GetField("executePeriod", BindingFlags.NonPublic | BindingFlags.Instance)?
.SetValue(service, TimeSpan.FromMilliseconds(1));
}
[Fact]
public async Task WorkRunsTwice()
{
var workCount = 2;
var periodMs = 100d;
var period = TimeSpan.FromMilliseconds(periodMs);
var result = 0;
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
{
result++;
return Task.CompletedTask;
}
//act
var work = Work.CreateByDelegate("", workAction);
var stopwatch = Stopwatch.StartNew();
service.Add(work, period);
var delay = (periodMs / 20) + (periodMs * workCount) - stopwatch.ElapsedMilliseconds;
await Task.Delay(TimeSpan.FromMilliseconds(delay));
//assert
Assert.Equal(workCount, result);
}
[Fact]
public async Task Enqueue_continues_after_exceptions()
{
var expectadResult = 42;
var result = 0;
Task workAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
{
result = expectadResult;
return Task.CompletedTask;
}
var goodWork = Work.CreateByDelegate("", workAction);
Task failAction(string id, IServiceProvider services, Action<string, double?> callback, CancellationToken token)
=> throw new Exception();
var badWork = Work.CreateByDelegate("", failAction);
badWork.OnErrorAsync = (id, exception, token) => throw new Exception();
//act
service.Add(badWork, TimeSpan.FromSeconds(2));
service.Add(goodWork, TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromMilliseconds(20));
//assert
Assert.Equal(expectadResult, result);
Assert.Equal(1, badWork.CountErrors);
Assert.Equal(1, goodWork.CountComplete);
Assert.Equal(1, goodWork.CountStart);
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AsbCloudApp.Data.SAUB;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Background;
using AsbCloudInfrastructure.Services.SAUB;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Xunit;
namespace AsbCloudWebApi.Tests.ServicesTests.SAUB;
public class TelemetryDataSaubCacheTests
{
private const int idTelemetry = 1;
private readonly IEnumerable<TelemetryDataSaubDto> fakeTelemetries = new[]
{
new TelemetryDataSaubDto()
};
private readonly IServiceProvider serviceProviderMock = Substitute.For<IServiceProvider>();
private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
private readonly Type telemetryDataCacheType;
public TelemetryDataSaubCacheTests()
{
serviceProviderMock.GetService<BackgroundWorker>().Returns(new BackgroundWorker(serviceProviderMock));
telemetryDataCache = TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(serviceProviderMock);
telemetryDataCacheType = telemetryDataCache.GetType();
}
[Fact]
public void AddRange_ShouldReturn_AddedElementToCache()
{
//arrange
telemetryDataCacheType.GetField("isLoading", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(telemetryDataCache, false);
//act
telemetryDataCache.AddRange(idTelemetry, fakeTelemetries);
var lastTelemetry = telemetryDataCache.GetLastOrDefault(idTelemetry);
//assert
Assert.Equal(lastTelemetry, fakeTelemetries.Last());
}
[Fact]
public void AddRange_ShouldReturn_NotAddedToCache()
{
//arrange
telemetryDataCacheType.GetField("isLoading", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(telemetryDataCache, true);
//act
telemetryDataCache.AddRange(idTelemetry, fakeTelemetries);
var lastTelemetry = telemetryDataCache.GetLastOrDefault(idTelemetry);
//assert
Assert.NotEqual(lastTelemetry, fakeTelemetries.Last());
}
}

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