refactor(infrastructure): align persistence with procedure result models
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using DigitalData.TaskFlow.Application.Common.Interfaces;
|
||||
using DigitalData.TaskFlow.Infrastructure.Options;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
using DigitalData.TaskFlow.Infrastructure.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -13,23 +12,14 @@ public static class DependencyInjection
|
||||
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString("TaskFlowDb");
|
||||
var databaseOptions = configuration.GetSection(DatabaseOptions.SectionName).Get<DatabaseOptions>() ?? new DatabaseOptions();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
throw new InvalidOperationException("Connection string 'TaskFlowDb' is not configured.");
|
||||
}
|
||||
|
||||
services.AddDbContext<TaskFlowDbContext>(options =>
|
||||
{
|
||||
options.UseSqlServer(connectionString, sqlOptions =>
|
||||
{
|
||||
sqlOptions.CommandTimeout(databaseOptions.CommandTimeoutSeconds);
|
||||
});
|
||||
options.UseSqlServer(connectionString);
|
||||
});
|
||||
|
||||
services.Configure<DatabaseOptions>(configuration.GetSection(DatabaseOptions.SectionName));
|
||||
|
||||
services.AddScoped<IWorkflowRepository, WorkflowRepository>();
|
||||
services.AddScoped<IDocumentRepository, DocumentRepository>();
|
||||
|
||||
|
||||
@@ -7,20 +7,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="11.0.0-rc.1.26425.128" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="11.0.0-rc.1.26425.128" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="11.0.0-rc.1.26425.128" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\DigitalData.TaskFlow.Application\DigitalData.TaskFlow.Application.csproj" />
|
||||
<ProjectReference Include="..\..\core\DigitalData.TaskFlow.Domain\DigitalData.TaskFlow.Domain.csproj" />
|
||||
<ProjectReference Include="..\..\core\DigitalData.TaskFlow.Application\DigitalData.TaskFlow.Application.csproj" />
|
||||
<ProjectReference Include="..\..\core\DigitalData.TaskFlow.Domain\DigitalData.TaskFlow.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Options\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Holds infrastructure database options.
|
||||
/// </summary>
|
||||
public sealed class DatabaseOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name.
|
||||
/// </summary>
|
||||
public const string SectionName = "Database";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets command timeout in seconds for SQL operations.
|
||||
/// </summary>
|
||||
public int CommandTimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets object type passed to workflow listing procedure.
|
||||
/// </summary>
|
||||
public string WorkflowListingObjectType { get; set; } = "Ferdinand";
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stored procedure row shape for workflow listing data.
|
||||
/// </summary>
|
||||
public sealed class WorkflowListingReadModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the process identifier.
|
||||
/// </summary>
|
||||
[Column("processid")]
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile identifier.
|
||||
/// </summary>
|
||||
[Column("profileid")]
|
||||
public byte ProfileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the voucher type.
|
||||
/// </summary>
|
||||
[Column("belegart")]
|
||||
public string VoucherType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the voucher number.
|
||||
/// </summary>
|
||||
[Column("belegnr")]
|
||||
public string VoucherNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the voucher date.
|
||||
/// </summary>
|
||||
[Column("belegdatum")]
|
||||
public DateTime VoucherDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cost center.
|
||||
/// </summary>
|
||||
[Column("kostenstelle")]
|
||||
public string CostCenter { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cost center description.
|
||||
/// </summary>
|
||||
[Column("kostenstellenbezeichnung")]
|
||||
public string CostCenterDescription { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the client number.
|
||||
/// </summary>
|
||||
[Column("mandantnr")]
|
||||
public short ClientNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the client name.
|
||||
/// </summary>
|
||||
[Column("mandantbez")]
|
||||
public string ClientName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the supplier number.
|
||||
/// </summary>
|
||||
[Column("lieferantnr")]
|
||||
public string SupplierNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the supplier name.
|
||||
/// </summary>
|
||||
[Column("lieferantbez")]
|
||||
public string SupplierName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the net amount.
|
||||
/// </summary>
|
||||
[Column("netto")]
|
||||
public double NetAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gross amount.
|
||||
/// </summary>
|
||||
[Column("brutto")]
|
||||
public double GrossAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the discount amount.
|
||||
/// </summary>
|
||||
[Column("skontobetrag")]
|
||||
public double DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currency.
|
||||
/// </summary>
|
||||
[Column("waehrung")]
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the priority.
|
||||
/// </summary>
|
||||
[Column("priority")]
|
||||
public byte Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source group text.
|
||||
/// </summary>
|
||||
[Column("GROUP_TEXT")]
|
||||
public string GroupText { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using DigitalData.TaskFlow.Domain.Entities;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence.Models;
|
||||
using DigitalData.TaskFlow.Domain.ProcedureResults;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
@@ -10,29 +9,33 @@ namespace DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
public sealed class TaskFlowDbContext(DbContextOptions<TaskFlowDbContext> options) : DbContext(options)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the keyless result model used for workflow listing procedure reads.
|
||||
/// Gets the keyless result model used for workflow procedure reads.
|
||||
/// </summary>
|
||||
public DbSet<WorkflowListingReadModel> WorkflowListingReadModels => Set<WorkflowListingReadModel>();
|
||||
public DbSet<Workflow> Workflows => Set<Workflow>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyless entity mapped to TBCUST_DOC_BASE64.
|
||||
/// Gets the keyless result model used for document procedure reads.
|
||||
/// </summary>
|
||||
public DbSet<CustDocBase64> CustDocBase64 => Set<CustDocBase64>();
|
||||
public DbSet<Document> Documents => Set<Document>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<WorkflowListingReadModel>(entity =>
|
||||
modelBuilder.Entity<Workflow>(entity =>
|
||||
{
|
||||
entity.HasNoKey();
|
||||
entity.ToView(null);
|
||||
entity.Property(x => x.Code).HasColumnName("CODE").HasColumnType("int");
|
||||
entity.Property(x => x.Result).HasColumnName("RESULT").HasColumnType("nvarchar(max)");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CustDocBase64>(entity =>
|
||||
modelBuilder.Entity<Document>(entity =>
|
||||
{
|
||||
entity.HasNoKey();
|
||||
entity.ToTable("TBCUST_DOC_BASE64", "dbo");
|
||||
entity.ToView(null);
|
||||
entity.Property(x => x.Code).HasColumnName("CODE").HasColumnType("int");
|
||||
entity.Property(x => x.Result).HasColumnName("RESULT").HasColumnType("nvarchar(max)");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# DigitalData.TaskFlow.Infrastructure
|
||||
|
||||
Dieses Projekt implementiert technische Integrationen der Anwendung.
|
||||
|
||||
## Zweck
|
||||
|
||||
Die Infrastructure-Schicht setzt die in Application definierten Abstraktionen konkret um. Dazu gehoeren Datenbankzugriffe (EF Core, Stored Procedures) und spaeter ggf. DMS- oder externe Service-Anbindungen.
|
||||
|
||||
## Aktueller Inhalt
|
||||
|
||||
- `DependencyInjection.cs`
|
||||
- Einstiegspunkt fuer die Registrierung technischer Dienste.
|
||||
- `Options/`
|
||||
- Platz fuer Infrastruktur-spezifische Optionen (falls benoetigt).
|
||||
|
||||
## Geplante Inhalte (naechste Phasen)
|
||||
|
||||
- `Persistence/`
|
||||
- `TaskFlowDbContext` mit SQL-Server-Konfiguration.
|
||||
- Entity-Konfigurationen inklusive Feld-Mapping (DB-Namen -> PascalCase-Modelle).
|
||||
- `Repositories/`
|
||||
- Implementierungen fuer:
|
||||
- Workflow-Lesezugriff (u. a. `PRCUST_F19_GET_LISTING_DATA`)
|
||||
- Workflow-Closing (Write-Procedure)
|
||||
- Dokument-Binary-Zugriff
|
||||
- `Persistence/Models/`
|
||||
- Keyless Modelle fuer Procedure-Ergebnisse.
|
||||
|
||||
## Wichtiger Hinweis zu DB-Spaltennamen
|
||||
|
||||
- Spaltennamen in `Persistence/Models` (z. B. `WorkflowListingReadModel`) basieren aktuell auf:
|
||||
- read-only Abfragen auf `sys.columns` (u. a. `TBCUST_F19_LISTING_RESULT`),
|
||||
- beobachteten Result-Sets von `PRCUST_F19_GET_LISTING_DATA` im `table`-Modus.
|
||||
- Vor produktiven Aenderungen muss das finale SP-Resultset read-only verifiziert werden, idealerweise mit
|
||||
`sp_describe_first_result_set` oder einer gleichwertigen Metadata-Pruefung.
|
||||
- Domain-Modelle bleiben davon getrennt: DB-Feldnamen werden nur in Infrastructure gemappt, nicht in Domain.
|
||||
|
||||
## Architektur-Regeln
|
||||
|
||||
- Keine Controller- oder HTTP-Logik.
|
||||
- Keine fachliche Regel-Definition; nur technische Umsetzung.
|
||||
- SQL-Zugriff stets parameterisiert und sicher.
|
||||
|
||||
## Rolle im Gesamtsystem
|
||||
|
||||
Infrastructure kapselt alle externen Abhaengigkeiten. Dadurch bleibt die Application-Schicht testbar und von Technologie-Details entkoppelt.
|
||||
@@ -1,25 +1,22 @@
|
||||
using DigitalData.TaskFlow.Application.Common.Interfaces;
|
||||
using DigitalData.TaskFlow.Domain.Documents;
|
||||
using DigitalData.TaskFlow.Domain.ProcedureResults;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Document repository implementation for binary document retrieval.
|
||||
/// Document repository implementation for reading document listing data and applying close operations.
|
||||
/// </summary>
|
||||
public sealed class DocumentRepository : IDocumentRepository
|
||||
public sealed class DocumentRepository(TaskFlowDbContext context) : IDocumentRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves binary document content by identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">Document identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Binary document payload when found; otherwise <see langword="null"/>.</returns>
|
||||
public Task<DocumentBinaryResult?> GetAsync(string id, CancellationToken cancellationToken)
|
||||
public async Task<Document?> GetAsync(int userId, long processId, long docId, CancellationToken cancellationToken)
|
||||
{
|
||||
_ = id;
|
||||
_ = cancellationToken;
|
||||
var rows = await context.Documents
|
||||
.FromSqlInterpolated(@$"EXEC [PRCUST_F19_GET_DOCUMENT] @pRESULT_TYPE = 'json', @pOBJECT_TYPE = 'Ferdinand', @pUSER_ID = {userId}, @pPROCESS_ID = {processId}, @pDOC_ID = {docId}")
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
throw new NotSupportedException(
|
||||
"Document retrieval procedure/DMS contract is not finalized yet. Configure final integration before enabling GetAsync.");
|
||||
return rows.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,288 +1,22 @@
|
||||
using DigitalData.TaskFlow.Application.Common.Interfaces;
|
||||
using DigitalData.TaskFlow.Domain.Closing;
|
||||
using DigitalData.TaskFlow.Domain.Workflows;
|
||||
using DigitalData.TaskFlow.Infrastructure.Options;
|
||||
using DigitalData.TaskFlow.Domain.ProcedureResults;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence.Models;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Workflow repository implementation for reading workflow listing data and applying close operations.
|
||||
/// </summary>
|
||||
public sealed class WorkflowRepository(TaskFlowDbContext dbContext, IOptions<DatabaseOptions> databaseOptions) : IWorkflowRepository
|
||||
public sealed class WorkflowRepository(TaskFlowDbContext context) : IWorkflowRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves grouped workflow data for a user.
|
||||
/// </summary>
|
||||
/// <param name="wisagId">WISAG user identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Grouped workflow response.</returns>
|
||||
public async Task<WorkflowSourcesResponse> GetAsync(int wisagId, CancellationToken cancellationToken)
|
||||
public async Task<Workflow?> GetByWisagIdAsync(int wisagId, CancellationToken cancellationToken)
|
||||
{
|
||||
var objectType = string.IsNullOrWhiteSpace(databaseOptions.Value.WorkflowListingObjectType)
|
||||
? "Ferdinand"
|
||||
: databaseOptions.Value.WorkflowListingObjectType;
|
||||
|
||||
var jsonPayload = await ExecuteListingJsonAsync(wisagId, objectType, cancellationToken);
|
||||
if (string.IsNullOrWhiteSpace(jsonPayload))
|
||||
{
|
||||
return new WorkflowSourcesResponse { Sources = [] };
|
||||
}
|
||||
|
||||
using var document = JsonDocument.Parse(jsonPayload);
|
||||
if (!document.RootElement.TryGetProperty("response", out var responseElement))
|
||||
{
|
||||
return new WorkflowSourcesResponse { Sources = [] };
|
||||
}
|
||||
|
||||
if (TryReadMetaCode(responseElement) == 2)
|
||||
{
|
||||
return new WorkflowSourcesResponse { Sources = [] };
|
||||
}
|
||||
|
||||
var sources = new List<SourceBlock>();
|
||||
if (!responseElement.TryGetProperty("sources", out var sourcesElement) || sourcesElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return new WorkflowSourcesResponse { Sources = sources };
|
||||
}
|
||||
|
||||
foreach (var sourceElement in sourcesElement.EnumerateArray())
|
||||
{
|
||||
if (!sourceElement.TryGetProperty("source", out var sourceNameElement))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sourceName = sourceNameElement.GetString();
|
||||
if (string.Equals(sourceName, "Ferdinand", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sources.Add(new FerdinandSourceBlock
|
||||
{
|
||||
Source = "Ferdinand",
|
||||
Profile = ReadArray(sourceElement, "profile", "profiles"),
|
||||
Listing = ReadArray(sourceElement, "Listing", "Listings"),
|
||||
Approvals = ReadArray(sourceElement, "approvals"),
|
||||
Document = ReadArray(sourceElement, "document", "documents"),
|
||||
ClosingMethod = ReadArray(sourceElement, "closing_method", "closing_methods"),
|
||||
RejectionReason = ReadArray(sourceElement, "rejection_reason", "rejection_reasons")
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Equals(sourceName, "Contracts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sources.Add(new ContractsSourceBlock
|
||||
{
|
||||
Source = "Contracts",
|
||||
Profile = ReadArray(sourceElement, "profile", "profiles"),
|
||||
Listing = ReadArray(sourceElement, "Listing", "Listings"),
|
||||
Approvals = ReadArray(sourceElement, "approvals"),
|
||||
Document = ReadArray(sourceElement, "document", "documents"),
|
||||
ClosingMethod = ReadArray(sourceElement, "closing_method", "closing_methods")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new WorkflowSourcesResponse { Sources = sources };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a closing decision to a workflow item.
|
||||
/// </summary>
|
||||
/// <param name="wisagId">WISAG user identifier.</param>
|
||||
/// <param name="source">Source system name.</param>
|
||||
/// <param name="processId">Process identifier.</param>
|
||||
/// <param name="decision">Closing decision payload.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Result of the close operation.</returns>
|
||||
public Task<CloseWorkflowResult> CloseAsync(
|
||||
int wisagId,
|
||||
string source,
|
||||
int processId,
|
||||
CloseWorkflowDecision decision,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_ = decision;
|
||||
|
||||
if (!string.Equals(source, "Ferdinand", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.NotFound
|
||||
});
|
||||
}
|
||||
|
||||
return EvaluateCloseOutcomeAsync(wisagId, source, processId, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<CloseWorkflowResult> EvaluateCloseOutcomeAsync(
|
||||
int wisagId,
|
||||
string source,
|
||||
int processId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var rows = await ExecuteListingTableAsync(wisagId, source, cancellationToken);
|
||||
if (rows.Any(row => row.ProcessId == processId))
|
||||
{
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = true,
|
||||
Outcome = CloseWorkflowOutcome.Closed
|
||||
};
|
||||
}
|
||||
|
||||
var metaCode = await TryGetWorkflowMetaCodeAsync(wisagId, source, cancellationToken);
|
||||
if (metaCode == 2)
|
||||
{
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.NoAccess
|
||||
};
|
||||
}
|
||||
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = 0,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.NotFound
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<int?> TryGetWorkflowMetaCodeAsync(int wisagId, string objectType, CancellationToken cancellationToken)
|
||||
{
|
||||
var jsonPayload = await ExecuteListingJsonAsync(wisagId, objectType, cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(jsonPayload))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(jsonPayload);
|
||||
|
||||
return document.RootElement.TryGetProperty("response", out var responseElement)
|
||||
? TryReadMetaCode(responseElement)
|
||||
: null;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<WorkflowListingReadModel>> ExecuteListingTableAsync(
|
||||
int wisagId,
|
||||
string objectType,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
const string resultType = "table";
|
||||
|
||||
return await dbContext
|
||||
.WorkflowListingReadModels
|
||||
.FromSqlInterpolated($"EXEC dbo.PRCUST_F19_GET_LISTING_DATA @pUSER_ID={wisagId}, @pRESULT_TYPE={resultType}, @pOBJECT_TYPE={objectType}")
|
||||
var rows = await context.Workflows
|
||||
.FromSqlInterpolated(@$"EXEC [PRCUST_F19_GET_LISTING_DATA] @pUSER_ID = {wisagId}, @pRESULT_TYPE = 'json', @pOBJECT_TYPE = 'Ferdinand'")
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<string?> ExecuteListingJsonAsync(int wisagId, string objectType, CancellationToken cancellationToken)
|
||||
{
|
||||
const string commandText = "EXEC dbo.PRCUST_F19_GET_LISTING_DATA @pUSER_ID, @pRESULT_TYPE, @pOBJECT_TYPE";
|
||||
const string resultType = "json";
|
||||
|
||||
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
|
||||
command.CommandText = commandText;
|
||||
|
||||
command.Parameters.Add(new SqlParameter("@pUSER_ID", wisagId));
|
||||
command.Parameters.Add(new SqlParameter("@pRESULT_TYPE", resultType));
|
||||
command.Parameters.Add(new SqlParameter("@pOBJECT_TYPE", objectType));
|
||||
|
||||
if (command.Connection is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (command.Connection.State != System.Data.ConnectionState.Open)
|
||||
{
|
||||
await command.Connection.OpenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
do
|
||||
{
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
if (reader.IsDBNull(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidate = reader.GetValue(i) as string;
|
||||
if (string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var jsonStart = candidate.IndexOf('{');
|
||||
if (jsonStart < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var jsonPayload = candidate[jsonStart..].Trim();
|
||||
if (!string.IsNullOrWhiteSpace(jsonPayload))
|
||||
{
|
||||
return jsonPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (await reader.NextResultAsync(cancellationToken));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? TryReadMetaCode(JsonElement responseElement)
|
||||
{
|
||||
if (!responseElement.TryGetProperty("meta", out var metaElement) || metaElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (metaElement.GetArrayLength() == 0 || !metaElement[0].TryGetProperty("code", out var codeElement))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return codeElement.GetInt32();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<JsonElement> ReadArray(JsonElement sourceElement, params string[] propertyNames)
|
||||
{
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
if (!sourceElement.TryGetProperty(propertyName, out var propertyElement)
|
||||
|| propertyElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return propertyElement.EnumerateArray().Select(item => item.Clone()).ToList();
|
||||
}
|
||||
|
||||
return [];
|
||||
return rows.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user