188 lines
6 KiB
C#
188 lines
6 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks;
|
||
using Sozsoft.Platform.Entities;
|
||
using Volo.Abp.Domain.Repositories;
|
||
using Volo.Abp.Domain.Services;
|
||
using Volo.Abp.MultiTenancy;
|
||
|
||
namespace Sozsoft.Mcp.Services;
|
||
|
||
/// <summary>
|
||
/// Sema bilgisinin tek kaynagi: <c>Sas_H_DbTableColumn</c> tablosundaki JSON veri sozlugu.
|
||
/// Sozluk kullaniciya gorunen basliklari ve lookup iliskilerini birlikte tasidigi icin canli
|
||
/// katalog yerine bu kullanilir — modelin gordugu adlar ekranda gorunen adlarla ayni olur.
|
||
/// </summary>
|
||
public interface IMcpSchemaDictionary
|
||
{
|
||
/// <summary>Sozlugun bulunup bulunmadigi ve ne zaman tazelendigi; yoksa null.</summary>
|
||
Task<McpDictionarySnapshot?> GetSnapshotAsync();
|
||
|
||
/// <summary>Sozlukteki listeler; sozluk yoksa bos liste.</summary>
|
||
Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync();
|
||
|
||
/// <summary>
|
||
/// Istegin kapsamina dusen sozluk kiraci adi. Sozluk ureten prosedurun
|
||
/// <c>@TenantName</c> parametresiyle ayni degeri tasimasi gerekir.
|
||
/// </summary>
|
||
string ResolveTenantName();
|
||
|
||
/// <summary>
|
||
/// Aksan ve buyuk/kucuk harf duyarsiz karsilastirma anahtari; "Musteri" aramasi
|
||
/// "Müşteri" basligini da bulur.
|
||
/// </summary>
|
||
string Normalize(string? value);
|
||
}
|
||
|
||
public class McpSchemaDictionary(
|
||
IRepository<DbTableColumn, Guid> dictionaryRepository,
|
||
ICurrentTenant currentTenant)
|
||
: DomainService, IMcpSchemaDictionary
|
||
{
|
||
private static readonly JsonSerializerOptions DictionaryJsonOptions = new()
|
||
{
|
||
PropertyNameCaseInsensitive = true
|
||
};
|
||
|
||
private static readonly CultureInfo TurkishCulture = CultureInfo.GetCultureInfo("tr-TR");
|
||
|
||
public string ResolveTenantName()
|
||
=> currentTenant.Name ?? McpConsts.DefaultDictionaryTenantName;
|
||
|
||
public async Task<McpDictionarySnapshot?> GetSnapshotAsync()
|
||
{
|
||
// Tazelik bilgisi icin JsonData (buyuk sozluklerde megabaytlar) okunmaz.
|
||
using (currentTenant.Change(null))
|
||
{
|
||
var query = await ActiveRowsAsync(ResolveTenantName());
|
||
|
||
return query
|
||
.Select(row => new McpDictionarySnapshot(
|
||
row.CacheKey,
|
||
row.RefreshedAt,
|
||
row.FormCount,
|
||
row.FieldCount,
|
||
row.TenantName,
|
||
row.DefinitionCultureName,
|
||
row.UiCultureName))
|
||
.FirstOrDefault();
|
||
}
|
||
}
|
||
|
||
public async Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync()
|
||
{
|
||
var json = await GetJsonDataAsync();
|
||
if (string.IsNullOrWhiteSpace(json))
|
||
{
|
||
return [];
|
||
}
|
||
|
||
var document = JsonSerializer.Deserialize<DictionaryDocument>(json, DictionaryJsonOptions);
|
||
|
||
return document?.ListForms ?? [];
|
||
}
|
||
|
||
public string Normalize(string? value)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(value))
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
var decomposed = value.ToLower(TurkishCulture).Normalize(NormalizationForm.FormD);
|
||
var builder = new StringBuilder(decomposed.Length);
|
||
|
||
foreach (var character in decomposed)
|
||
{
|
||
if (CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.NonSpacingMark)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
builder.Append(character switch
|
||
{
|
||
'ı' => 'i',
|
||
'ğ' => 'g',
|
||
'ş' => 's',
|
||
_ => character
|
||
});
|
||
}
|
||
|
||
return builder.ToString();
|
||
}
|
||
|
||
private async Task<string?> GetJsonDataAsync()
|
||
{
|
||
// Sozluk host kapsamli bir tabloda tutulur; kiraci ayrimi TenantName kolonundadir.
|
||
using (currentTenant.Change(null))
|
||
{
|
||
var query = await ActiveRowsAsync(ResolveTenantName());
|
||
|
||
return query.Select(row => row.JsonData).FirstOrDefault();
|
||
}
|
||
}
|
||
|
||
private async Task<IQueryable<DbTableColumn>> ActiveRowsAsync(string tenantName)
|
||
{
|
||
var query = await dictionaryRepository.GetQueryableAsync();
|
||
|
||
return query
|
||
.Where(row => row.DictionaryType == McpConsts.DictionaryType
|
||
&& row.IsActive
|
||
&& row.TenantName == tenantName)
|
||
.OrderByDescending(row => row.RefreshedAt);
|
||
}
|
||
|
||
/// <summary>Sozluk JSON'unun kok nesnesi.</summary>
|
||
private sealed class DictionaryDocument
|
||
{
|
||
public List<McpDictionaryListForm>? ListForms { get; set; }
|
||
}
|
||
}
|
||
|
||
/// <summary>Sozluk satirinin tazelik bilgisi.</summary>
|
||
public sealed record McpDictionarySnapshot(
|
||
string? CacheKey,
|
||
DateTime RefreshedAt,
|
||
int FormCount,
|
||
int FieldCount,
|
||
string? TenantName,
|
||
string? DefinitionCultureName,
|
||
string? UiCultureName);
|
||
|
||
/// <summary>
|
||
/// Sozlukteki bir liste. Alan adlari <c>dbo.Sas_H_ListFormFieldsDictionaryJson</c> prosedurunun
|
||
/// urettigi JSON ile birebir ayni olmak zorundadir.
|
||
/// </summary>
|
||
public class McpDictionaryListForm
|
||
{
|
||
public string? ListFormCode { get; set; }
|
||
public string? DisplayName { get; set; }
|
||
public string? Title { get; set; }
|
||
public string? Description { get; set; }
|
||
public string? TableName { get; set; }
|
||
public string? SqlSourceName { get; set; }
|
||
public string? SourceKind { get; set; }
|
||
public string? KeyFieldName { get; set; }
|
||
public string? SearchText { get; set; }
|
||
public List<McpDictionaryField>? Fields { get; set; }
|
||
}
|
||
|
||
/// <summary>Sozlukteki bir sutun.</summary>
|
||
public class McpDictionaryField
|
||
{
|
||
public string? Field { get; set; }
|
||
public string? DisplayName { get; set; }
|
||
public string? SqlDataType { get; set; }
|
||
public bool IsVisible { get; set; }
|
||
public bool IsBooleanColumn { get; set; }
|
||
public string? LookupTableName { get; set; }
|
||
public string? LookupValueColumn { get; set; }
|
||
public string? LookupDisplayColumn { get; set; }
|
||
public bool IsSensitive { get; set; }
|
||
public bool IsDefaultSelectColumn { get; set; }
|
||
}
|