6.9 KiB
Skanowanie pól klasy biznesowych z DLL (Roslyn MetadataReference)
Narzędzie do odczytu rzeczywistych pól bazodanowych obiektu biznesowego ze skompilowanych bibliotek dodatku enova365/Soneta. Pozwala to na budowanie kodu opierającego się na tych klasach biznesowych, wyrażeń bindujących form.xml oraz (Warunki filtrujące)[./rowconditions.md].
Cel
W modelu Soneta klasa Row (np. DokumentHandlowy) udostępnia właściwości publiczne, które można wykorzystać w
generowanych kodzie biznesowym, oraz do budowania wyrażeń bindujących form.xml. Natomiast w
(warunkach filtrujących)[./rowconditions.md], można używać TYLKO pól bazodananowych udostępnianych przez to narzędzie.
Używaj tego narzędzia, gdy:
- piszesz kod operujący bezpośrednio na polach rekordu (np. w extenderze, workerze, datapack);
- chcesz zweryfikować rzeczywisty typ pola (np.
decimal?vsdecimal) bez czytania wygenerowanego kodu; - pracujesz na dodatku innej osoby i nie masz dostępu do źródeł, tylko do DLL.
- generujesz warunki filtrujące (serwerowe - LINQ)
- Przygotowujesz formularze form.xml.
Mechanizm
Skrypt używa Roslyn (Microsoft.CodeAnalysis.CSharp) i MetadataReference.CreateFromFile, co oznacza, że metadane są czytane bez ładowania IL do CLR — bezpiecznie, bez ryzyka konfliktów wersji, x86/x64 itp.
Algorytm:
- Zbierz wszystkie
*.dllz podanego katalogu i zarejestruj jakoMetadataReference. Dodatkowo dołącz wszystkie biblioteki runtime'u .NET z listy TPA (AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")) — bez tego Roslyn nie rozwiązuje typuSystem.ComponentModel.DescriptionAttribute(i podobnych) iConstructorArgumentsatrybutów zwraca pustą tablicę, przez coTytuł/Opiszostają puste. - Zbuduj
CSharpCompilationz tymi referencjami. - Przejdź rekurencyjnie po
IAssemblySymbol.GlobalNamespacekażdej referencji. - Znajdź pierwszy typ kończący się na
Module, który zawiera typ zagnieżdżony o nazwie{NazwaRekordu}Record. - Odczytaj publiczne pola (
IFieldSymbol,DeclaredAccessibility == Public) i ich typy → oznacz jako bazodanowe. - Znajdź publiczną klasę najwyższego poziomu o nazwie
{NazwaRekordu}(klasę biznesową, np.DokumentHandlowy) i wczytaj jej publiczne, instancyjneIPropertySymbol(wraz z dziedziczonymi). - Scal listy:
- property o nazwie unikalnej (brak takiego pola w rekordzie) → oznacz jako kalkulowane;
- property o nazwie pokrywającej się z polem rekordu → zachowaj znacznik bazodanowe, ale podmień typ na ten z property (bo property zwykle precyzuje typ, np. zwraca konkretny enum lub
RowzamiastGuid/int).
- Dla każdego wpisu odczytaj
TytułiOpis— pierwszy parametrstringkonstruktora atrybutu. Matching nazwy atrybutu jest dopasowywany doCaption/CaptionAttributeorazDescription/DescriptionAttribute(np.System.ComponentModel.DescriptionAttributeużywany w generowanym kodzie Soneta). Kolejność źródeł:- property klasy biznesowej (
{NazwaRekordu}) — z uwzględnieniem dziedziczenia (atrybut może być na property bazowej klasy, np.{NazwaRekordu}Row); - pole rekordu (
{NazwaModulu}+{NazwaRekordu}Record); - odpowiadający member w typie zagnieżdżonym
{NazwaModulu}+{NazwaRekordu}Row(fallback — przeszukiwany wraz z klasami bazowymi). To tam Soneta zwykle deklaruje[Description("...")]na publicznych property delegujących do pola rekordu.
- property klasy biznesowej (
- Rekurencja po subrowach — jeśli któreś z pól rekordu ma typ kończący się na
Record(np.CoreModule.DefinicjaNumeracjiRecord Numeracja), traktuj je jako subrow:- na bazie nazwy typu wylicz nazwę bazową (
DefinicjaNumeracjiRecord→DefinicjaNumeracji); - znajdź klasę biznesową (
DefinicjaNumeracji) oraz typ*Module+DefinicjaNumeracjiRow(mogą być w innym module — np.CoreModule); - powtórz całą procedurę (kroki 5–8) dla tego rekordu, używając prefiksu
Numeracja.w kluczach wyników (Numeracja.Pole1,Numeracja.Pole2, …). Rekurencja działa dowolnie głęboko (subrow w subrowie). Pętle (rekord zawierający siebie pośrednio) są zabezpieczone przez zbiór odwiedzonych typów.
- na bazie nazwy typu wylicz nazwę bazową (
- Wypisz tabelę markdown na stdout (kolumny:
Pole | Typ | Rodzaj | Tytuł | Opis).
Wymagania
- .NET SDK (8.0+)
dotnet-script:dotnet tool install -g dotnet-script
Uruchomienie
dotnet script ~/.claude/skills/soneta-programming/scripts/scan-props.csx \
-- <NazwaRow> <KatalogDll>
Przykład
dotnet script ~/.claude/skills/soneta-programming/scripts/scan-props.csx \
-- DokumentHandlowy ./bin/Debug/net8.0
Przykładowe wyjście
# Pola i właściwości klasy biznesowej: `Soneta.Handel.DokumentHandlowy`
Nazwa tabeli: `DokHandlowe`
- pola bazodanowe: 128
- pola kalkulowane (z klas biznesowych): 388
| Pole | Typ | Rodzaj | Tytuł | Opis |
|------|-----|--------|-------|------|
| Brutto | `decimal` | bazodanowe | Brutto | Wartość brutto dokumentu |
| DataDokumentu | `System.DateTime` | bazodanowe | Data dokumentu | |
| Kontrahent | `Soneta.Kontrahenci.Kontrahent` | bazodanowe | Kontrahent | |
| Netto | `decimal` | bazodanowe | Netto | |
| Numer | `string` | bazodanowe | Numer | |
| SaldoWaluta | `decimal` | | Saldo w walucie | |
| ... | ... | ... | ... | ... |
Kolumna Rodzaj ma wartość bazodanowe dla pól rekordu lub jest pusta dla właściwości kalkulowanych.
Kody wyjścia
| Kod | Znaczenie |
|---|---|
0 |
OK — wypisano tabelę pól |
1 |
Błąd argumentów / nie istnieje katalog / brak DLL |
2 |
Nie znaleziono typu *Module+{NazwaRekordu}Record w referencjach |
Ograniczenia
- Skanuje tylko górny poziom katalogu (
SearchOption.TopDirectoryOnly) — jeśli DLL są rozproszone, skopiuj je do jednego katalogu. - Zwraca pierwszy znaleziony typ pasujący do wzorca
*Module+{Nazwa}Record— jeśli dwa moduły mają taki sam zagnieżdżony rekord, dostaniesz tylko jeden (niedeterministycznie wg kolejności assembly). - Zwraca publiczne pola rekordu (
IFieldSymbol) oraz publiczne, instancyjne właściwości klasy biznesowej (IPropertySymbol, łącznie z dziedziczonymi). Pola rekordu = źródło prawdy o schemacie DB (rodzajbazodanowe); właściwości spoza rekordu = wyliczane w kodzie (rodzajkalkulowane). - Jeśli klasa biznesowa o nazwie
{NazwaRekordu}nie zostanie znaleziona w referencjach, skrypt zwraca tylko listę pól bazodanowych (z odpowiednią adnotacją w nagłówku) i kończy się kodem0. - Pierwsze uruchomienie pobiera pakiet NuGet
Microsoft.CodeAnalysis.CSharp— wymaga połączenia internetowego (kolejne odpalenia działają offline).
Powiązania
- Patrz datapack-guidedrow.md — struktury
GuidedRow/ExportedRowi mechanizm Datapack operujący na polach rekordu. - Patrz skill
soneta-business-xml— definicja schematu, z któregoBusinessGeneratorprodukuje klasęXxxRecord.