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:
+43
-203
@@ -11,16 +11,30 @@ description: >
|
|||||||
|
|
||||||
# Soneta Programming Basics - Podstawowe klasy ORM
|
# Soneta Programming Basics - Podstawowe klasy ORM
|
||||||
|
|
||||||
Skill zawiera dokumentację fundamentalnych klas logiki biznesowej platformy enova365/Soneta Enterprise. Klasy te
|
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.
|
||||||
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
|
## Architektura warstw
|
||||||
|
|
||||||
```
|
```
|
||||||
BusApplication.Instance (singleton) - multihreaded
|
BusApplication.Instance (singleton) - multithreaded
|
||||||
└── Database
|
└── Database
|
||||||
└── Login
|
└── Login
|
||||||
└── Session - singlethreaded
|
└── Session - single-threaded
|
||||||
└── Module
|
└── Module
|
||||||
└── Table
|
└── Table
|
||||||
└── Row
|
└── Row
|
||||||
@@ -31,7 +45,7 @@ BusApplication.Instance (singleton) - multihreaded
|
|||||||
| Poziom | Opis | Przykłady klas |
|
| Poziom | Opis | Przykłady klas |
|
||||||
|--------|------|----------------|
|
|--------|------|----------------|
|
||||||
| **1. Bazowe** | Klasy wspólne dla wszystkich modułów (Soneta.Business.dll) | `Row`, `Table`, `Module`, `Session`, `Context` |
|
| **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) |
|
| **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**.
|
**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)
|
Row (abstrakcyjna)
|
||||||
└── GuidedRow (+ Guid, Attachments, ChangeInfos)
|
└── GuidedRow (+ Guid, Attachments, ChangeInfos)
|
||||||
└── ExportedRow (+ Exported flag)
|
└── ExportedRow (+ Exported flag - rozróżnia dane konfiguracyjne od operacyjnych)
|
||||||
|
|
||||||
Table (abstrakcyjna)
|
Table (abstrakcyjna)
|
||||||
└── GuidedTable (indeksator po Guid)
|
└── GuidedTable (indeksator po Guid)
|
||||||
@@ -51,6 +65,8 @@ Module (abstrakcyjna)
|
|||||||
└── [NazwaModulu]Module (np. TowaryModule)
|
└── [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
|
## Thread-safety
|
||||||
|
|
||||||
### Obiekty single-threaded (NIE współdzielić między wątkami)
|
### Obiekty single-threaded (NIE współdzielić między wątkami)
|
||||||
@@ -68,76 +84,27 @@ Module (abstrakcyjna)
|
|||||||
|
|
||||||
## Klasa Session - fundamenty
|
## Klasa Session - fundamenty
|
||||||
|
|
||||||
Session to kluczowa klasa do zarządzania danymi. **Każda operacja na danych wymaga 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`.
|
||||||
|
|
||||||
### Tworzenie sesji
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Przez Login
|
|
||||||
Session session = login.CreateSession(readOnly: false, config: false, name: "MojaSesja");
|
Session session = login.CreateSession(readOnly: false, config: false, name: "MojaSesja");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Typy sesji
|
**Każda modyfikacja obiektu MUSI być w transakcji biznesowej** otwieranej przez `Session.Logout(editMode: true)` - dotyczy dodawania, modyfikacji właściwości oraz kasowania:
|
||||||
|
|
||||||
| 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
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Logout(editMode: true) - transakcja edycyjna (można modyfikować)
|
|
||||||
using (var transaction = session.Logout(editMode: true))
|
using (var transaction = session.Logout(editMode: true))
|
||||||
{
|
{
|
||||||
towar.Nazwa = "Zmieniona nazwa";
|
towar.Nazwa = "Zmieniona nazwa";
|
||||||
transaction.Commit(); // lub CommitUI()
|
transaction.Commit(); // Commit() w kodzie biznesowym
|
||||||
}
|
// transaction.CommitUI(); // CommitUI() w kodzie UI (worker, extender, Command)
|
||||||
|
|
||||||
// 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ą
|
|
||||||
}
|
}
|
||||||
|
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
|
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).
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
## Klasa Module
|
## Klasa Module
|
||||||
|
|
||||||
@@ -254,83 +221,24 @@ var tm = TowaryModule.GetInstance(sessionable);
|
|||||||
|
|
||||||
## Kod biznesowy vs UI
|
## Kod biznesowy vs UI
|
||||||
|
|
||||||
Kod biznesowy realizuje operacje logiki biznesowej (jak backend).
|
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 (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 UI to np:
|
|
||||||
- obiekty `View`, `ViewInfo`, extender
|
- obiekty `View`, `ViewInfo`, extender
|
||||||
- metody sterujące `IsReadOnlyXxx`, `IsVisibleXxxx`, `GetListXxx`, `IsEnabledXxx`, `GetNameXxx`, `GetAppearanceXxx`
|
- metody sterujące `IsReadOnlyXxx`, `IsVisibleXxxx`, `GetListXxx`, `IsEnabledXxx`, `GetNameXxx`, `GetAppearanceXxx`
|
||||||
|
|
||||||
### Ważne zasady do stosowania w kodzie biznesowym
|
### 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 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 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:
|
Dostęp do metadanych obiektów biznesowych jest dostępny przez metody `static` klasy `ApplicationInfo`.
|
||||||
- App (BusApplication.Instance)
|
|
||||||
- Database
|
|
||||||
- Login
|
|
||||||
- Session (default - nie trzeba określać w deklaracji)
|
|
||||||
|
|
||||||
Umieszczając deklarację interface serwisu w assembly wspólnym, pozwalają na udostępnianie serwisów między modułami,
|
- Odczyt informacji o tabeli: `TableInfo info = ApplicationInfo.GetTableInfo(nazwaTabeli)`. Istnieje tylko jedna referencja obiektu `TableInfo` dla tabeli - można używać `ReferenceEquals`, `Dictionary`, itp.
|
||||||
nawet gdy nie ma odpowiedniej referencji.
|
- Wszystkie tabele: `ApplicationInfo.GetTablesInfo()`.
|
||||||
|
- Tabele dla modułu: `ApplicationInfo.GetModuleInfo(moduleName).TableInfos`.
|
||||||
* **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`.
|
|
||||||
|
|
||||||
### Wykorzystuj `TableInfo` do weryfikacji tabeli
|
### Wykorzystuj `TableInfo` do weryfikacji tabeli
|
||||||
|
|
||||||
@@ -342,77 +250,6 @@ if (row1.Table.TableInfo==row2.Table.TableInfo) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
## Szybki start - wzorce kodu
|
||||||
|
|
||||||
### Odczyt danych
|
### Odczyt danych
|
||||||
@@ -420,7 +257,7 @@ class Test {
|
|||||||
```csharp
|
```csharp
|
||||||
using (var session = login.CreateSession(readOnly: true, config: false, name: "Odczyt"))
|
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)
|
foreach (Towar t in tm.Towary.WgKodu)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{t.Kod}: {t.Nazwa}");
|
Console.WriteLine($"{t.Kod}: {t.Nazwa}");
|
||||||
@@ -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
|
## Konwencje nazewnicze
|
||||||
|
|
||||||
| Element | Konwencja | Przykład |
|
| 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` |
|
| Identyfikatory systemowe | **angielski** | `Session`, `Context`, `Row`, `Table`, `Module` |
|
||||||
|
|
||||||
**Można łączyć polski i angielski** w nazwach metod i klas:
|
**Można łączyć polski i angielski** w nazwach metod i klas:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
RetrieveTowary()
|
RetrieveTowary()
|
||||||
UpdateKontrahent()
|
UpdateKontrahent()
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ Akcje (metody worker, akcje extender, handlery Command, callbacki) zwracają **a
|
|||||||
sterujący tym, co stanie się w UI po wykonaniu logiki biznesowej. Typ zwróconego obiektu decyduje
|
sterujący tym, co stanie się w UI po wykonaniu logiki biznesowej. Typ zwróconego obiektu decyduje
|
||||||
o sposobie obsługi.
|
o sposobie obsługi.
|
||||||
|
|
||||||
|
## Spis treści
|
||||||
|
|
||||||
|
- [Najważniejsza zasada](#najważniejsza-zasada)
|
||||||
|
- [Wartości specjalne](#wartości-specjalne) - `null`, `string`, `bool`
|
||||||
|
- [Sterowanie aktualnym formularzem](#sterowanie-aktualnym-formularzem) - close, refresh
|
||||||
|
- [Okna informacyjne i pytania do użytkownika](#okna-informacyjne-i-pytania-do-użytkownika) - MessageBox, potwierdzenia
|
||||||
|
- [Otwieranie obiektów i okien](#otwieranie-obiektów-i-okien) - edycja, podgląd
|
||||||
|
- [Nawigacja po programie](#nawigacja-po-programie) - listy, foldery, URL
|
||||||
|
- [Pliki i strumienie](#pliki-i-strumienie) - pobieranie plików, podgląd
|
||||||
|
- [Raporty](#raporty) - generowanie i parametry raportów
|
||||||
|
- [Dialogi parametrów](#dialogi-parametrów) - kreatory, dialog z ContextBase
|
||||||
|
- [Operacje klienta i bezpieczeństwo](#operacje-klienta-i-bezpieczeństwo)
|
||||||
|
- [Tabela szybkiego wyboru](#tabela-szybkiego-wyboru) - mapa typ → efekt UI
|
||||||
|
|
||||||
## Najważniejsza zasada
|
## Najważniejsza zasada
|
||||||
|
|
||||||
**Nie wywołuj sam UI z poziomu worker/extender.** Zamiast pokazywać MessageBox, otwierać formularz
|
**Nie wywołuj sam UI z poziomu worker/extender.** Zamiast pokazywać MessageBox, otwierać formularz
|
||||||
|
|||||||
@@ -309,6 +309,35 @@ public class TowarExtender
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Akcja workera w menu Czynności
|
||||||
|
|
||||||
|
Worker może udostępniać akcję uruchamianą z menu - zaznaczone obiekty trafiają do property opisanej `[Context]`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[assembly: Worker<WyslijEmailWorker, Kontrahent>]
|
||||||
|
|
||||||
|
public class WyslijEmailWorker
|
||||||
|
{
|
||||||
|
[Context]
|
||||||
|
public Kontrahent[] Kontrahenci { get; set; }
|
||||||
|
|
||||||
|
[Context]
|
||||||
|
public Context Context { get; set; }
|
||||||
|
|
||||||
|
[Action("Wyślij email")]
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
foreach (var k in Kontrahenci)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(k.Email))
|
||||||
|
WyslijEmail(k.Email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WyslijEmail(string email) { /* ... */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Dobre praktyki
|
## Dobre praktyki
|
||||||
|
|
||||||
1. **Używaj Get<T>, GetOrDefault<T>, GetRequired<T>** zamiast indeksatora - bezpieczniejsze
|
1. **Używaj Get<T>, GetOrDefault<T>, GetRequired<T>** zamiast indeksatora - bezpieczniejsze
|
||||||
|
|||||||
@@ -19,20 +19,7 @@ Faktura bez pozycji nie jest kompletną fakturą - wszystkie te obiekty stanowi
|
|||||||
|
|
||||||
## Hierarchia klas Row i Table
|
## Hierarchia klas Row i Table
|
||||||
|
|
||||||
```
|
Hierarchię ogólną opisuje [SKILL.md](../SKILL.md#hierarchia-głównych-klas). W skrócie: `Row → GuidedRow → ExportedRow` (po stronie wierszy) oraz `Table → GuidedTable → ExportedTable` (po stronie tabel). Szczegóły właściwości - sekcje poniżej.
|
||||||
Row (abstrakcyjna) Table (abstrakcyjna)
|
|
||||||
│ └── ID: int └── GuidedTable (indeksator po Guid)
|
|
||||||
│ └── State: RowState └── ExportedTable
|
|
||||||
│
|
|
||||||
└── GuidedRow
|
|
||||||
│ └── Guid: System.Guid
|
|
||||||
│ └── Attachments
|
|
||||||
│ └── FirstChangeInfo, LastChangeInfo
|
|
||||||
│ └── Note
|
|
||||||
│
|
|
||||||
└── ExportedRow
|
|
||||||
└── Exported: bool
|
|
||||||
```
|
|
||||||
|
|
||||||
## Atrybut guided w business.xml
|
## Atrybut guided w business.xml
|
||||||
|
|
||||||
@@ -130,6 +117,31 @@ foreach (Attachment att in row.Attachments)
|
|||||||
Attachment img = row.DefaultImage;
|
Attachment img = row.DefaultImage;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dodawanie załącznika z transakcją
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public void DodajZalacznik(Login login, Towar towar, byte[] plik, string nazwa)
|
||||||
|
{
|
||||||
|
using (var session = login.CreateSession(false, false, "DodawanieZalacznika"))
|
||||||
|
{
|
||||||
|
// Doczytaj towar w bieżącej sesji (mieszanie obiektów z różnych sesji jest błędem)
|
||||||
|
var towarInSession = session.Get(towar);
|
||||||
|
|
||||||
|
using (var transaction = session.Logout(editMode: true))
|
||||||
|
{
|
||||||
|
var attachment = new Attachment(towarInSession, AttachmentType.Attachments);
|
||||||
|
towarInSession.Module.Business.Attachments.AddRow(attachment);
|
||||||
|
|
||||||
|
attachment.Name = nazwa;
|
||||||
|
attachment.RawData = plik;
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
session.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## ExportedRow
|
## ExportedRow
|
||||||
|
|
||||||
Rozszerza `GuidedRow` o flagę `Exported`.
|
Rozszerza `GuidedRow` o flagę `Exported`.
|
||||||
|
|||||||
@@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
Praktyczne przykłady użycia podstawowych klas logiki biznesowej enova365/Soneta.
|
Praktyczne przykłady użycia podstawowych klas logiki biznesowej enova365/Soneta.
|
||||||
|
|
||||||
|
## Spis treści
|
||||||
|
|
||||||
|
- [Ważne zasady](#ważne-zasady) - thread-safety, transakcje, mieszanie sesji
|
||||||
|
- [Dostęp do danych](#dostęp-do-danych) - iteracja po kluczach, wyszukiwanie, filtrowanie
|
||||||
|
- [Tworzenie obiektów](#tworzenie-obiektów) - AddRow, walidacja, AddingRow
|
||||||
|
- [Modyfikacja obiektów](#modyfikacja-obiektów)
|
||||||
|
- [Usuwanie obiektów](#usuwanie-obiektów)
|
||||||
|
- [Praca z kontekstem](#praca-z-kontekstem) → szczegółowo w [context.md](context.md)
|
||||||
|
- [Praca z GuidedRow](#praca-z-guidedrow) → szczegółowo w [datapack-guidedrow.md](datapack-guidedrow.md)
|
||||||
|
- [Dane konfiguracyjne vs operacyjne](#dane-konfiguracyjne-vs-operacyjne) - `ExecuteConfig`
|
||||||
|
- [Pełny przykład - import towarów](#pełny-przykład---import-towarów)
|
||||||
|
- [Obsługa błędów](#obsługa-błędów) - try/catch wokół transakcji
|
||||||
|
|
||||||
## Ważne zasady
|
## Ważne zasady
|
||||||
|
|
||||||
### Thread-safety
|
### Thread-safety
|
||||||
@@ -260,178 +273,11 @@ public void UsunTowar(Login login, string kod)
|
|||||||
|
|
||||||
## Praca z kontekstem
|
## Praca z kontekstem
|
||||||
|
|
||||||
### Worker wyliczający właściwość
|
Przykłady Workera z `[Context]`, klasy parametrów dziedziczącej z `ContextBase`, akcji w menu Czynności i współdzielenia wartości przez Context - patrz [context.md](context.md).
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Rejestracja workera na poziomie assembly
|
|
||||||
[assembly: Worker<TowarWorker, Towar>]
|
|
||||||
|
|
||||||
public class TowarWorker
|
|
||||||
{
|
|
||||||
[Context]
|
|
||||||
public Magazyn MagazynFiltra { get; set; }
|
|
||||||
|
|
||||||
[Context]
|
|
||||||
public Towar Towar { get; set; }
|
|
||||||
|
|
||||||
public decimal StanMagazynowy
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (MagazynFiltra != null)
|
|
||||||
{
|
|
||||||
return PoliczStan(Towar, MagazynFiltra);
|
|
||||||
}
|
|
||||||
return PoliczStanCalkowity(Towar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private decimal PoliczStan(Towar towar, Magazyn magazyn)
|
|
||||||
{
|
|
||||||
// Implementacja...
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private decimal PoliczStanCalkowity(Towar towar)
|
|
||||||
{
|
|
||||||
// Implementacja...
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Akcja workera w menu Czynności
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Rejestracja workera na poziomie assembly
|
|
||||||
[assembly: Worker<WyslijEmailWorker, Kontrahent>]
|
|
||||||
|
|
||||||
public class WyslijEmailWorker
|
|
||||||
{
|
|
||||||
[Context]
|
|
||||||
public Kontrahent[] Kontrahenci { get; set; }
|
|
||||||
|
|
||||||
[Context]
|
|
||||||
public Context Context { get; set; }
|
|
||||||
|
|
||||||
[Action("Wyślij email")]
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
foreach (var k in Kontrahenci)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(k.Email))
|
|
||||||
{
|
|
||||||
WyslijEmail(k.Email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WyslijEmail(string email)
|
|
||||||
{
|
|
||||||
// Implementacja...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Klasa parametrów (ContextBase)
|
|
||||||
|
|
||||||
Klasa `ContextBase` jest przeznaczona do budowania klas parametrów, nie workerów:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class FiltryTowarow(Context context) : ContextBase(context)
|
|
||||||
{
|
|
||||||
public Magazyn Magazyn { get; set; }
|
|
||||||
|
|
||||||
[Caption("Typ towaru")] // Etykieta w UI, gdy inna niż nazwa property
|
|
||||||
public TypTowaru? Typ { get; set; }
|
|
||||||
|
|
||||||
public bool TylkoAktywne { get; set; } = true;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Uwaga:** W klasach parametrów atrybut `[Context]` nie jest wymagany.
|
|
||||||
|
|
||||||
**Współdzielenie wartości przez Context** - wartości parametrów można przechowywać w obiekcie Context, co pozwala na współdzielenie między różnymi klasami parametrów:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class FiltryTowarow(Context context) : ContextBase(context)
|
|
||||||
{
|
|
||||||
public Magazyn Magazyn
|
|
||||||
{
|
|
||||||
get => Context.GetOrDefault<Magazyn>();
|
|
||||||
set => Context.Set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Caption("Typ towaru")]
|
|
||||||
public TypTowaru? Typ
|
|
||||||
{
|
|
||||||
get => Context.GetOrDefault<TypTowaru?>();
|
|
||||||
set => Context.Set(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Praca z GuidedRow
|
## Praca z GuidedRow
|
||||||
|
|
||||||
### Dostęp do historii zmian
|
Przykłady dostępu do historii zmian (`ChangeInfos`) i pracy z załącznikami (dodawanie z transakcją, odczyt, `DefaultImage`) - patrz [datapack-guidedrow.md](datapack-guidedrow.md).
|
||||||
|
|
||||||
```csharp
|
|
||||||
public void PokazHistorie(Session session, Kontrahent kontrahent)
|
|
||||||
{
|
|
||||||
var bm = session.GetBusiness();
|
|
||||||
|
|
||||||
Console.WriteLine($"Historia zmian dla: {kontrahent.Nazwa}");
|
|
||||||
|
|
||||||
foreach (ChangeInfo ci in bm.ChangeInfos[kontrahent])
|
|
||||||
{
|
|
||||||
Console.WriteLine($" {ci.Time}: {ci.Operator}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skróty
|
|
||||||
Console.WriteLine($"Utworzono: {kontrahent.FirstChangeInfo?.Time}");
|
|
||||||
Console.WriteLine($"Ostatnia zmiana: {kontrahent.LastChangeInfo?.Time}");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Praca z załącznikami
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public void DodajZalacznik(Login login, Towar towar, byte[] plik, string nazwa)
|
|
||||||
{
|
|
||||||
using (var session = login.CreateSession(false, false, "DodawanieZalacznika"))
|
|
||||||
{
|
|
||||||
// Doczytaj towar w bieżącej sesji
|
|
||||||
var towarInSession = session.Get(towar);
|
|
||||||
|
|
||||||
using (var transaction = session.Logout(editMode: true))
|
|
||||||
{
|
|
||||||
var attachment = new Attachment(towarInSession, AttachmentType.Attachments);
|
|
||||||
towarInSession.Module.Business.Attachments.AddRow(attachment);
|
|
||||||
|
|
||||||
attachment.Name = nazwa;
|
|
||||||
attachment.RawData = plik;
|
|
||||||
|
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
session.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WyswietlZalaczniki(Towar towar)
|
|
||||||
{
|
|
||||||
foreach (Attachment att in towar.Attachments)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"- {att.Name} ({att.RawData.Length} bajtów)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domyślne zdjęcie
|
|
||||||
using var defaultImage = towar.DefaultImage;
|
|
||||||
if (defaultImage != null)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Zdjęcie główne: {defaultImage.FileName}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dane konfiguracyjne vs operacyjne
|
## Dane konfiguracyjne vs operacyjne
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Serwisy biznesowe
|
||||||
|
|
||||||
|
Serwisy pozwalają tworzyć obiekty (komponenty), których czas życia zależy od scope:
|
||||||
|
|
||||||
|
- **App** (BusApplication.Instance) - cały okres działania aplikacji
|
||||||
|
- **Database** - per baza danych
|
||||||
|
- **Login** - per zalogowany użytkownik
|
||||||
|
- **Session** (default - nie trzeba określać w deklaracji)
|
||||||
|
|
||||||
|
Umieszczając deklarację interfejsu serwisu w assembly wspólnym, można udostępniać serwisy między modułami, nawet gdy nie ma między nimi referencji.
|
||||||
|
|
||||||
|
## Zasady
|
||||||
|
|
||||||
|
- **Tylko serwisy scope Session są single-threaded**, pozostałe są multi-threaded.
|
||||||
|
- Serwis może być `IDisposable`.
|
||||||
|
- Dla serwisów App, Database, Login **nie przechowuj obiektów sesyjnych** (Session, Row, Module, Table, Context).
|
||||||
|
- `[RequireOwnService]` tylko dla serwisów, które nie mogą być nadpisywane.
|
||||||
|
|
||||||
|
## Deklaracja
|
||||||
|
|
||||||
|
```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() { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Odczyt serwisu
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
IRegistry registerRequired = login.GetRequiredService<IRegistry>();
|
||||||
|
IRegistry? registerOptional = login.GetService<IRegistry>();
|
||||||
|
|
||||||
|
foreach (IRegistry registers in login.GetServices<IRegistry>())
|
||||||
|
{
|
||||||
|
// wiele implementacji
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Scope odpowiada obiektowi, z którego pobieramy serwis: `BusApplication.Instance.GetRequiredService<T>()`, `database.GetRequiredService<T>()`, `login.GetRequiredService<T>()`, `session.GetRequiredService<T>()`.
|
||||||
|
|
||||||
|
## Użycie w worker / extender
|
||||||
|
|
||||||
|
Obiekt jest tworzony przez `Context.CreateObject()`, więc zależności są wstrzykiwane automatycznie.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Rozwiązanie lepsze - constructor injection
|
||||||
|
class MyWorker1(IRegistry registry)
|
||||||
|
{
|
||||||
|
// używaj registry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternatywa - atrybut [Context] na property
|
||||||
|
class MyWorker2
|
||||||
|
{
|
||||||
|
[Context]
|
||||||
|
private IRegistry Registry { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -288,6 +288,23 @@ using (var session = login.CreateSession(readOnly: false, config: false, name: "
|
|||||||
| `transaction.CommitUI()` | Zatwierdza + odświeża UI |
|
| `transaction.CommitUI()` | Zatwierdza + odświeża UI |
|
||||||
| `transaction.Dispose()` | Bez Commit = rollback (także zagnieżdżonych zmian) |
|
| `transaction.Dispose()` | Bez Commit = rollback (także zagnieżdżonych zmian) |
|
||||||
|
|
||||||
|
### Commit() vs CommitUI()
|
||||||
|
|
||||||
|
- `Commit()` - używaj w **kodzie biznesowym** (worker bez interakcji UI, kod uruchamiany w tle, testy, kod konsolowy).
|
||||||
|
- `CommitUI()` - używaj w **kodzie UI** (worker uruchamiany z menu Czynności, extender, Command). Dodatkowo wymusza odświeżenie powiązanych widoków.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Action("Aktualizuj")]
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
using (var transaction = Towar.Session.Logout(editMode: true))
|
||||||
|
{
|
||||||
|
Towar.Nazwa = NowaNazwa;
|
||||||
|
transaction.CommitUI(); // UI - odśwież listę / formularz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Kompletny przykład
|
## Kompletny przykład
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Tłumaczenia i logowanie
|
||||||
|
|
||||||
|
Soneta dostarcza dwa fundamenty obserwowalności i lokalizacji, które działają razem - tłumaczenia napisów oraz log zmian / śledzenie czasu wykonania.
|
||||||
|
|
||||||
|
## Tłumaczenie i formatowanie napisów
|
||||||
|
|
||||||
|
Biblioteka obsługuje słowniki tłumaczące napisy w aplikacji Soneta.
|
||||||
|
|
||||||
|
- Tłumaczone napisy muszą używać metody string-extender: `"napis do tłumaczenia".Translate()`.
|
||||||
|
- Tłumaczenie tekstów formatowanych: `"napis {0} z wartością {1}".TranslateFormat(arg0, arg1)`.
|
||||||
|
- Gdy string ma być **zignorowany** przez tłumacza, MUSISZ oznaczyć go metodą `"nie tłumaczymy".TranslateIgnore()` - inaczej błąd kompilacji.
|
||||||
|
- Jeśli w metodzie lub klasie jest wiele napisów do zignorowania, użyj atrybutu `[TranslateIgnore]` na metodzie / klasie.
|
||||||
|
- Parametr metody ignorowany przez tłumacza - atrybut `[TranslateIgnore]` na parametrze.
|
||||||
|
|
||||||
|
## Log zmian i obserwowalność
|
||||||
|
|
||||||
|
Używaj standardowych narzędzi do logowania `ILogger<T>`. Użyj `[TranslateIgnore]` w metodzie wywołującej log (komunikaty logu nie są tłumaczone). Używaj `logger.IsEnabled(LogLevel)` kiedy parametry wymagają dodatkowych operacji.
|
||||||
|
|
||||||
|
### Użycie `ILogger` oraz exception log
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
class Test
|
||||||
|
{
|
||||||
|
private readonly ILogger<Test> logger =
|
||||||
|
BusApplication.Instance.GetRequiredService<ILogger<Test>>();
|
||||||
|
|
||||||
|
public decimal Kwota;
|
||||||
|
|
||||||
|
[TranslateIgnore]
|
||||||
|
public void Metoda()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.LogInformation("Wywołanie metody {nazwa}", nameof(Metoda));
|
||||||
|
if (Kwota < 0)
|
||||||
|
logger.LogWarning("Kwota {kwota} nie może być ujemna w metodzie '{metoda}'", Kwota, nameof(Metoda));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user