soneta-programming - redukcja duplikatów i mapa skilla

SKILL.md odchudzony z 493 do 333 linii: sekcje Serwisy, Tłumaczenia
i Log wyciągnięte do nowych referencji, sekcja Session skrócona do
fundamentów (szczegóły w session-login.md), dodana Mapa skilla.
W examples.md usunięte duplikaty z context.md i datapack-guidedrow.md,
dodane spisy treści do dużych referencji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcin Wojas
2026-05-16 22:55:21 +02:00
parent 4576f3135b
commit 20a45e06cd
8 changed files with 287 additions and 390 deletions
+47 -207
View File
@@ -5,22 +5,36 @@ description: >
obiektowo-relacyjne (Row, Table, Module), zarządzanie sesją (Session), logowanie
(Login, Database, BusApplication), paczki danych (Datapack, GuidedRow) oraz
kontekst (Context). Używaj gdy użytkownik pyta o podstawowe klasy logiki biznesowej,
strukturę obiektów ORM, sesje i transakcje, hierarchię klas Row/Table/Module,
strukturę obiektów ORM, sesje i transakcje, hierarchię klas Row/Table/Module,
mechanizm Datapack i synchronizację danych, lub kontekst aplikacji enova365.
---
# Soneta Programming Basics - Podstawowe klasy ORM
Skill zawiera dokumentację fundamentalnych klas logiki biznesowej platformy enova365/Soneta Enterprise. Klasy te
stanowią podstawę mapowania obiektowo-relacyjnego (ORM) i są niezbędne do tworzenia kodu i dodatków.
Skill zawiera dokumentację fundamentalnych klas logiki biznesowej platformy enova365/Soneta Enterprise. Klasy te stanowią podstawę mapowania obiektowo-relacyjnego (ORM) i są niezbędne do tworzenia kodu i dodatków.
## Mapa skilla
SKILL.md zawiera "duży obraz" - hierarchię klas, thread-safety, kanoniczne wzorce. Po szczegóły konkretnego tematu sięgaj do referencji:
| Temat | Gdzie szukać |
|---|---|
| Hierarchia ORM, Row / Table / Module, klucze, ISessionable | sekcje poniżej |
| Sesje, transakcje, Login, Database, BusApplication, optimistic locking | [references/session-login.md](references/session-login.md) |
| Paczki danych, Datapack, GuidedRow, ExportedRow, synchronizacja, blokady | [references/datapack-guidedrow.md](references/datapack-guidedrow.md) |
| Klasa Context - dane z UI, zaznaczenia, parametry workera | [references/context.md](references/context.md) |
| Serwisy biznesowe (App / Database / Login / Session scope) | [references/services.md](references/services.md) |
| Tłumaczenia (Translate, TranslateIgnore), ILogger, ActSource | [references/translations-logging.md](references/translations-logging.md) |
| Action result zwracany przez worker / extender / Command - raporty, dialogi, nawigacja | [references/action-result.md](references/action-result.md) |
| Gotowe wzorce kodu end-to-end (import, CRUD, obsługa błędów) | [references/examples.md](references/examples.md) |
## Architektura warstw
```
BusApplication.Instance (singleton) - multihreaded
BusApplication.Instance (singleton) - multithreaded
└── Database
└── Login
└── Session - singlethreaded
└── Session - single-threaded
└── Module
└── Table
└── Row
@@ -31,7 +45,7 @@ BusApplication.Instance (singleton) - multihreaded
| Poziom | Opis | Przykłady klas |
|--------|------|----------------|
| **1. Bazowe** | Klasy wspólne dla wszystkich modułów (Soneta.Business.dll) | `Row`, `Table`, `Module`, `Session`, `Context` |
| **2. Generowane** | Klasy generowane przez BusinessGenerator z *.business.xml (sufiksy: Row, Table, Module) | `TowarRow`, `TowarTable`, `TowaryModule` |
| **2. Generowane** | Klasy generowane przez BusinessGenerator z `*.business.xml` (sufiksy: Row, Table, Module) | `TowarRow`, `TowarTable`, `TowaryModule` |
| **3. Implementowane** | Klasy konkretne tworzone przez programistę | `Towar`, `Towary` (bez sufiksów) |
**BusinessGenerator** jest automatycznie uruchamiany podczas kompilacji dla plików `*.business.xml`. Szczegółowy opis definiowania business.xml znajduje się w skill **soneta-business-xml**.
@@ -41,7 +55,7 @@ BusApplication.Instance (singleton) - multihreaded
```
Row (abstrakcyjna)
└── GuidedRow (+ Guid, Attachments, ChangeInfos)
└── ExportedRow (+ Exported flag)
└── ExportedRow (+ Exported flag - rozróżnia dane konfiguracyjne od operacyjnych)
Table (abstrakcyjna)
└── GuidedTable (indeksator po Guid)
@@ -51,6 +65,8 @@ Module (abstrakcyjna)
└── [NazwaModulu]Module (np. TowaryModule)
```
Szczegóły GuidedRow / ExportedRow (flaga `Exported`, atrybuty `guided` / `relguided` w business.xml, ChangeInfos, blokady) - patrz [references/datapack-guidedrow.md](references/datapack-guidedrow.md).
## Thread-safety
### Obiekty single-threaded (NIE współdzielić między wątkami)
@@ -68,76 +84,27 @@ Module (abstrakcyjna)
## Klasa Session - fundamenty
Session to kluczowa klasa do zarządzania danymi. **Każda operacja na danych wymaga sesji.**
### Tworzenie sesji
Session to kluczowa klasa do zarządzania danymi. **Każda operacja na danych wymaga sesji.** Sesja jest single-threaded i implementuje `IDisposable` - zawsze opakowuj w `using`.
```csharp
// Przez Login
Session session = login.CreateSession(readOnly: false, config: false, name: "MojaSesja");
```
### Typy sesji
| Typ | ReadOnly | Config | Użycie |
|-----|----------|--------|--------|
| Edycyjna operacyjna | false | false | Modyfikacja dokumentów, kartotek |
| ReadOnly operacyjna | true | false | Odczyt danych transakcyjnych |
| Edycyjna konfiguracyjna | false | true | Modyfikacja ustawień |
| ReadOnly konfiguracyjna | true | true | Odczyt konfiguracji |
**WAŻNE:** W sesji operacyjnej nie można modyfikować obiektów konfiguracyjnych - wymagana jest sesja konfiguracyjna (`config: true`).
### Transakcje biznesowe (WAŻNE!)
**Każda zmiana obiektu MUSI być w transakcji biznesowej** otwieranej przez `Session.Logout(editMode: true)`:
- Dodawanie nowych obiektów
- Modyfikacja właściwości (properties)
- Kasowanie obiektów
**Każda modyfikacja obiektu MUSI być w transakcji biznesowej** otwieranej przez `Session.Logout(editMode: true)` - dotyczy dodawania, modyfikacji właściwości oraz kasowania:
```csharp
// Logout(editMode: true) - transakcja edycyjna (można modyfikować)
using (var transaction = session.Logout(editMode: true))
{
towar.Nazwa = "Zmieniona nazwa";
transaction.Commit(); // lub CommitUI()
}
// Logout(editMode: false) - transakcja tylko do odczytu
// (modyfikacje możliwe tylko w zagnieżdżonej transakcji edycyjnej)
using (var readTransaction = session.Logout(editMode: false))
{
// odczyt danych...
// zagnieżdżona transakcja edycyjna
using (var editTransaction = session.Logout(editMode: true))
{
var cena = towar.Ceny["Hurtowa"];
cena.Netto = new DoubleCy(100m);
editTransaction.Commit();
}
readTransaction.Commit(); // WYMAGANE! Inaczej zmiany z zagnieżdżonych transakcji przepadną
transaction.Commit(); // Commit() w kodzie biznesowym
// transaction.CommitUI(); // CommitUI() w kodzie UI (worker, extender, Command)
}
session.Save(); // zapis do bazy - optimistic-lock; konflikty wykrywane tu
```
**Brak Commit() = automatyczny rollback przy Dispose()** (dotyczy też transakcji tylko do odczytu!)
**Brak Commit() = automatyczny rollback przy Dispose()** - dotyczy też transakcji tylko do odczytu.
### Optimistic locking
Zmiany wykonywane są w trybie **optimistic-lock**:
- Zmiany kumulują się w sesji
- `Session.Save()` zapisuje wszystkie zmiany razem
- Konflikty wykrywane w momencie zapisu
### Ważne zasady
- Session implementuje `IDisposable` - **zawsze wywołuj Dispose()** lub używaj `using`
- Wiele sesji może współistnieć jednocześnie
- Sesja konfiguracyjna używa cache'a (optymalizacja odczytów)
- Sesja operacyjna zawsze czyta z bazy (aktualność danych)
- **Nie mieszaj obiektów z różnych sesji** - użyj `session.Get(obiekt)` aby doczytać obiekt w bieżącej sesji
- ID identyfikuje obiekt w tabeli, może powtarzać się w różnych tabelach
Pełna dokumentacja (typy sesji edycyjna / readonly / konfiguracyjna, transakcje zagnieżdżone, Commit vs CommitUI, optimistic locking, mieszanie obiektów z różnych sesji przez `session.Get(obiekt)`) - patrz [references/session-login.md](references/session-login.md).
## Klasa Module
@@ -254,165 +221,35 @@ var tm = TowaryModule.GetInstance(sessionable);
## Kod biznesowy vs UI
Kod biznesowy realizuje operacje logiki biznesowej (jak backend).
Kod UI (fronend) jest odpowiedzialny za prezentację danych i interakcję z użytkownikiem.
Kod biznesowy może być umieszczony w tej samej klasie z kodem UI.
Kod UI to np:
Kod biznesowy realizuje operacje logiki biznesowej (jak backend). Kod UI (frontend) jest odpowiedzialny za prezentację danych i interakcję z użytkownikiem. Kod biznesowy może być umieszczony w tej samej klasie z kodem UI.
Kod UI to np.:
- obiekty `View`, `ViewInfo`, extender
- metody sterujące `IsReadOnlyXxx`, `IsVisibleXxxx`, `GetListXxx`, `IsEnabledXxx`, `GetNameXxx`, `GetAppearanceXxx`
### Ważne zasady do stosowania w kodzie biznesowym
- Nie używaj żadnych obiektów kodu UI, w szczególności `View` - zamiast tego możesz użyć `SubTable[condition]`
- Nie należy stosować warunków na prawa dostępu (np `if (Table.AccessRight == AccessRights.Denied) {...}`)
- Nie używaj żadnych obiektów kodu UI, w szczególności `View` - zamiast tego możesz użyć `SubTable[condition]`.
- Nie należy stosować warunków na prawa dostępu (np. `if (Table.AccessRight == AccessRights.Denied) {...}`).
## Serwisy
## Metadane modułów, tabel, kluczy, pól
Pozwalają tworzyć obiekty (komponenty), których czas życia będzie zależał od scope:
- App (BusApplication.Instance)
- Database
- Login
- Session (default - nie trzeba określać w deklaracji)
Dostęp do metadanych obiektów biznesowych jest dostępny przez metody `static` klasy `ApplicationInfo`.
Umieszczając deklarację interface serwisu w assembly wspólnym, pozwalają na udostępnianie serwisów między modułami,
nawet gdy nie ma odpowiedniej referencji.
* **Tylko serwisy scope Session są single-threded, pozostałe są multi-threaded.**
* Serwis może być `IDisposable`.
* Dla serwisów App, Database, Login nie przechowuj obiektów sesyjnych.
* `[RequireOwnService]` tylko dla serwisów, które nie mogą być nadpisywane.
### Przykład deklaracji
```csharp
[assembly: Service<MyNamespace.IRegistry, MyNamespace.Registry>(ServiceScope.Login)]
namespace MyNamespace;
[RequireServiceScope(ServiceScope.Login)]
public interface IRegistry {
void Method();
}
internal sealed class Registry : IRegistry {
public void Method() {}
}
```
### Odczytanie serwisu w kodzie
```csharp
IRegistry registerRequired = login.GetRequiredService<IRegistry>();
IRegistry? registerOptional = login.GetService<IRegistry>();
foreach (IRegistry registers in login.GetServices<IRegistry>())
{
}
```
### Użycie serwisu w worker lub extender - obiekt tworzony przez Context.CreareObject()
```csharp
// Rozwiązanie lepsze
class MyWorker1(IRegistry registry) {
}
class MyWorker2 {
[Context]
private IRegistry Registry { get; set; }
}
```
## Metadane modułów, tabel, kluczy, pól, itp
Dostęp do metadanych obiektów biznesowych dostępny przez metody `static` klasy `ApplicationInfo`.
Odczytanie informacji o tabli `TableInfo info = ApplicationInfo.GetTableInfo(nazwaTabeli)`. Istnieje tylko jedna
referencja obiektu TableInfo dla tabeli. Można używać `ReferenceEquals`, `Dictionary`, itp.
Odczytanie wszystkich tabel `ApplicationInfo.GetTablesInfo()`, a np tabel dla modułu `ApplicationInfo.GetModuleInfo
(moduleName).TableInfos`.
- Odczyt informacji o tabeli: `TableInfo info = ApplicationInfo.GetTableInfo(nazwaTabeli)`. Istnieje tylko jedna referencja obiektu `TableInfo` dla tabeli - można używać `ReferenceEquals`, `Dictionary`, itp.
- Wszystkie tabele: `ApplicationInfo.GetTablesInfo()`.
- Tabele dla modułu: `ApplicationInfo.GetModuleInfo(moduleName).TableInfos`.
### Wykorzystuj `TableInfo` do weryfikacji tabeli
```csharp
Row row1 = ...;
Row row2 = ...;
if (row1.Table.TableInfo==row2.Table.TableInfo) {
if (row1.Table.TableInfo == row2.Table.TableInfo) {
// Ta sama tabela, nawet gdy różne sesje
}
```
## Tłumaczenie i formatowanie napisów, tekstów i string
Biblioteka obsługuje słowniki tłumaczące napisy w aplikacji Soneta.
* Tłumaczone napisy muszą używać metody typu string-extender `"napis dotłumaczenia".Translate()`.
* Tłumaczenie tekstów formatowanych przez `"napis {0} z wartością {1}".TranslateFormat(arg0, arg1)`.
* Gdy string ma być zignorowany przez tłumacza, MUSISZ zaznaczyć do metodą `"nie tłumaczymy".TranslateIgnore()`,inaczej błąd kompilacji.
* Jeżeli w metodzie lub klasie jest więcej napisów do zignorowania użyj atrybutu `[TranslateIgnore]`.
* Parametr metody jest ignorowany przez tłumacza, użyj atrybutu `[TranslateIgnore]` na parametrze.
## Log zmian i obserwowalność
Używaj standardowy narzędzi do logowania `ILogger<T>`. Użyj `[TranslateIgnore]` w metodzie wywołującej log.
Używaj `logger.IsEnable(LogLevel)` kiedy parametry wymagają dodatkowych operacji.
### Użycie `ILogger` oraz exception log
```csharp
class Test
{
private readonly logger = BusApplication.Instance.GetRequiredService<ILogger<Test>>();
public decimal Kwota;
[TranslateIgnore]
public void Metoda()
{
try {
logger.LogInformation("Wywołanie metody {nazwa}", nameof(Validate));
if (kwota<0)
logger.LogWarning("Kwota {kwota} nie może być ujemna w metodzie '{metoda}'", Kwota, nameof(Validate));
}
catch (Exception ex) {
// Sposób na wrzucenie exception do trace
ex.Log<Test>();
throw;
}
}
}
```
### Śledzenie czasu wykonania z obsługą exception
```csharp
class Test {
private static readonly ActSource actSource = new(nameof(Test), ActSource.TraceLevel.Default);
public void Action() {
using var activity = actSource.Start();
try {
// Algorytm do śledzenia
}
catch (Exception ex) {
activity.AddExceptionWithError(ex);
throw;
}
}
}
```
## Szczegółowa dokumentacja
- **[references/session-login.md](references/session-login.md)** - BusApplication, Database, Login, Session
- **[references/datapack-guidedrow.md](references/datapack-guidedrow.md)** - Paczki danych, GuidedRow, ExportedRow, synchronizacja
- **[references/context.md](references/context.md)** - Klasa Context, komunikacja UI ↔ logika
- **[references/examples.md](references/examples.md)** - Przykłady kodu i wzorce użycia
- **[references/action-result.md](references/action-result.md)** - Action result zwracane przez worker/extender/Command
(typy obsługiwane przez ResultHandler) i własne handlery
## Szybki start - wzorce kodu
### Odczyt danych
@@ -420,7 +257,7 @@ class Test {
```csharp
using (var session = login.CreateSession(readOnly: true, config: false, name: "Odczyt"))
{
var tm = session.GetTowary(); // Extension method
var tm = session.GetTowary();
foreach (Towar t in tm.Towary.WgKodu)
{
Console.WriteLine($"{t.Kod}: {t.Nazwa}");
@@ -434,7 +271,7 @@ using (var session = login.CreateSession(readOnly: true, config: false, name: "O
using (var session = login.CreateSession(readOnly: false, config: false, name: "Dodawanie"))
{
var tm = session.GetTowary();
using (var transaction = session.Logout(editMode: true))
{
var towar = new Towar();
@@ -443,7 +280,7 @@ using (var session = login.CreateSession(readOnly: false, config: false, name: "
towar.Nazwa = "Nowy towar";
transaction.Commit();
}
session.Save();
}
```
@@ -467,6 +304,8 @@ using (var session = login.CreateSession(readOnly: false, config: false, name: "
}
```
Więcej wzorców (kasowanie, obsługa błędów, pełny import end-to-end) - patrz [references/examples.md](references/examples.md).
## Konwencje nazewnicze
| Element | Konwencja | Przykład |
@@ -485,6 +324,7 @@ using (var session = login.CreateSession(readOnly: false, config: false, name: "
| Identyfikatory systemowe | **angielski** | `Session`, `Context`, `Row`, `Table`, `Module` |
**Można łączyć polski i angielski** w nazwach metod i klas:
```csharp
RetrieveTowary()
UpdateKontrahent()