PlotDirector/PlotLine/Data/NameIntelligenceRepository.cs

162 lines
9.5 KiB
C#

using System.Data;
using Dapper;
using Microsoft.Data.SqlClient;
using PlotLine.Models;
namespace PlotLine.Data;
public interface INameIntelligenceRepository
{
Task<NameReferenceImportStatus?> GetLatestImportAsync(string packageId, string version, CancellationToken cancellationToken = default);
Task<int> StartImportAsync(string packageId, string version, string checksum, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default);
Task CompleteImportAsync(int importId, bool succeeded, int localeRows, int givenNameRows, int relationshipRows, int usageRows, string? errorSummary, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default);
Task UpsertLocalesAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default);
Task UpsertGivenNamesAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default);
Task UpsertRelationshipsAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default);
Task UpsertUsagesAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default);
Task<IReadOnlyList<NameLocale>> ListLocalesAsync(CancellationToken cancellationToken = default);
Task<NameRecognitionResult> RecogniseAsync(string normalisedName, string? localeCode, int? birthYear, CancellationToken cancellationToken = default);
Task<IReadOnlyList<NameRelationshipReference>> ListRelationshipsAsync(int givenNameId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<NameSearchResult>> SearchAsync(int userId, NameSearchFilter filter, CancellationToken cancellationToken = default);
Task<IReadOnlyList<NameUsageDecoration>> ListUsedDecorationsAsync(int userId, int projectId, int? bookId, string normalisedName, string usedScope, CancellationToken cancellationToken = default);
}
public sealed class NameIntelligenceRepository(ISqlConnectionFactory connectionFactory) : INameIntelligenceRepository
{
public async Task<NameReferenceImportStatus?> GetLatestImportAsync(string packageId, string version, CancellationToken cancellationToken = default)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<NameReferenceImportStatus>(Command(
"dbo.NameReferenceImport_GetLatest",
new { PackageID = packageId, SemanticVersion = version },
cancellationToken: cancellationToken));
}
public async Task<int> StartImportAsync(string packageId, string version, string checksum, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default)
=> await connection.QuerySingleAsync<int>(Command(
"dbo.NameReferenceImport_Start",
new { PackageID = packageId, SemanticVersion = version, PackageChecksum = checksum },
transaction,
cancellationToken));
public Task CompleteImportAsync(int importId, bool succeeded, int localeRows, int givenNameRows, int relationshipRows, int usageRows, string? errorSummary, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default)
=> connection.ExecuteAsync(Command(
"dbo.NameReferenceImport_Complete",
new
{
NameReferenceImportID = importId,
Succeeded = succeeded,
LocaleRows = localeRows,
GivenNameRows = givenNameRows,
RelationshipRows = relationshipRows,
UsageRows = usageRows,
ErrorSummary = errorSummary
},
transaction,
cancellationToken));
public Task UpsertLocalesAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default)
=> ExecuteTableAsync("dbo.NameLocale_BulkUpsert", "Rows", "dbo.NameLocaleImportTable", rows, connection, transaction, cancellationToken);
public Task UpsertGivenNamesAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default)
=> ExecuteTableAsync("dbo.GivenName_BulkUpsert", "Rows", "dbo.GivenNameImportTable", rows, connection, transaction, cancellationToken);
public Task UpsertRelationshipsAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default)
=> ExecuteTableAsync("dbo.NameRelationship_BulkUpsert", "Rows", "dbo.NameRelationshipImportTable", rows, connection, transaction, cancellationToken);
public Task UpsertUsagesAsync(DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken = default)
=> ExecuteTableAsync("dbo.NameUsage_BulkUpsert", "Rows", "dbo.NameUsageImportTable", rows, connection, transaction, cancellationToken);
public async Task<IReadOnlyList<NameLocale>> ListLocalesAsync(CancellationToken cancellationToken = default)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<NameLocale>(Command("dbo.NameLocale_List", cancellationToken: cancellationToken));
return rows.ToList();
}
public async Task<NameRecognitionResult> RecogniseAsync(string normalisedName, string? localeCode, int? birthYear, CancellationToken cancellationToken = default)
{
using var connection = connectionFactory.CreateConnection();
using var grid = await connection.QueryMultipleAsync(Command(
"dbo.GivenName_Recognise",
new { NormalisedName = normalisedName, LocaleCode = localeCode, BirthYear = birthYear },
cancellationToken: cancellationToken));
var name = await grid.ReadSingleOrDefaultAsync<GivenNameReference>();
if (name is null)
{
return new NameRecognitionResult();
}
var usage = await grid.ReadSingleOrDefaultAsync<NameUsageProfile>();
var related = (await grid.ReadAsync<NameRelationshipReference>()).ToList();
return new NameRecognitionResult
{
Recognised = true,
Name = name,
Usage = usage,
RelatedForms = related,
Message = "Recognised given-name form."
};
}
public async Task<IReadOnlyList<NameRelationshipReference>> ListRelationshipsAsync(int givenNameId, CancellationToken cancellationToken = default)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<NameRelationshipReference>(Command(
"dbo.GivenName_Relationships",
new { GivenNameID = givenNameId },
cancellationToken: cancellationToken));
return rows.ToList();
}
public async Task<IReadOnlyList<NameSearchResult>> SearchAsync(int userId, NameSearchFilter filter, CancellationToken cancellationToken = default)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<NameSearchResult>(Command(
"dbo.GivenName_Search",
new
{
UserID = userId,
filter.ProjectID,
filter.BookID,
Search = NullIfWhiteSpace(filter.Search),
LocaleCode = NullIfWhiteSpace(filter.LocaleCode),
filter.BirthYear,
GenderPreference = NullIfWhiteSpace(filter.GenderPreference),
filter.GenderThreshold,
NameType = NullIfWhiteSpace(filter.NameType),
filter.MinimumUsage,
filter.UsedScope,
filter.UsedOnly,
filter.Page,
filter.PageSize
},
cancellationToken: cancellationToken));
return rows.ToList();
}
public async Task<IReadOnlyList<NameUsageDecoration>> ListUsedDecorationsAsync(int userId, int projectId, int? bookId, string normalisedName, string usedScope, CancellationToken cancellationToken = default)
{
using var connection = connectionFactory.CreateConnection();
var rows = await connection.QueryAsync<NameUsageDecoration>(Command(
"dbo.GivenName_UsedDecorations",
new { UserID = userId, ProjectID = projectId, BookID = bookId, NormalisedName = normalisedName, UsedScope = usedScope },
cancellationToken: cancellationToken));
return rows.ToList();
}
public IDbConnection CreateConnection() => connectionFactory.CreateConnection();
private static async Task ExecuteTableAsync(string procedure, string parameterName, string typeName, DataTable rows, IDbConnection connection, IDbTransaction transaction, CancellationToken cancellationToken)
{
var parameters = new DynamicParameters();
parameters.Add(parameterName, rows.AsTableValuedParameter(typeName));
await connection.ExecuteAsync(Command(procedure, parameters, transaction, cancellationToken));
}
private static CommandDefinition Command(string commandText, object? parameters = null, IDbTransaction? transaction = null, CancellationToken cancellationToken = default)
=> new(commandText, parameters, transaction, commandType: CommandType.StoredProcedure, cancellationToken: cancellationToken);
private static string? NullIfWhiteSpace(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}