15 KiB
name, description
| name | description |
|---|---|
| soneta-programming | Fundamentalne klasy ORM platformy enova365/Soneta Enterprise. Obejmuje mapowanie 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, 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.
Architektura warstw
BusApplication.Instance (singleton) - multihreaded
└── Database
└── Login
└── Session - singlethreaded
└── Module
└── Table
└── Row
3 poziomy logiki biznesowej
| 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 |
| 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.
Hierarchia głównych klas
Row (abstrakcyjna)
└── GuidedRow (+ Guid, Attachments, ChangeInfos)
└── ExportedRow (+ Exported flag)
Table (abstrakcyjna)
└── GuidedTable (indeksator po Guid)
└── ExportedTable
Module (abstrakcyjna)
└── [NazwaModulu]Module (np. TowaryModule)
Thread-safety
Obiekty single-threaded (NIE współdzielić między wątkami)
SessionModuleTableRowContext- oraz wszystkie klasy pochodne
Obiekty multi-threaded (można współdzielić)
BusApplicationDatabaseLogin
Klasa Session - fundamenty
Session to kluczowa klasa do zarządzania danymi. Każda operacja na danych wymaga sesji.
Tworzenie sesji
// 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
// 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ą
}
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żywajusing - 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
Moduł grupuje logicznie powiązane tabele. Nie ma odwzorowania w bazie danych.
// Dostęp do modułu - extension method (zalecane)
var tm = session.GetTowary();
var hm = session.GetHandel();
var crm = session.GetCRM();
var bm = session.GetBusiness();
// Dostęp do tabel
Towary towary = tm.Towary;
Jednostki jednostki = tm.Jednostki;
// Moduł implementuje ISessionable
Session s = tm.Session;
Klasa Table
Reprezentuje tabelę w bazie danych. Udostępnia dostęp do kolekcji wierszy.
var towary = tm.Towary;
// Iteracja po kluczu podstawowym
foreach (Towar t in towary.WgKodu) { ... }
// Iteracja po innym kluczu
foreach (Towar t in towary.WgNazwy) { ... }
// Właściwości
Table.AccessRight // Prawa dostępu
Table.Session // Sesja (ISessionable)
Table.Module // Moduł nadrzędny
Table.PrimaryKey // Klucz podstawowy
Klasa Row
Reprezentuje pojedynczy wiersz (rekord) z tabeli.
Właściwości bazowe
Row.ID // int - PODSTAWOWY identyfikator obiektu w tabeli (autoincrement, Primary Key)
Row.State // RowState - stan obiektu w sesji
Row.Table // Table - tabela nadrzędna
Row.Module // Module - moduł
Row.Session // Session - sesja
ID jest automatycznie generowany przez bazę danych i stanowi klucz główny (Primary Key) tabeli.
Stany obiektu (RowState)
| Stan | Opis |
|---|---|
Detached |
Nowy obiekt, nie przypisany do tabeli |
Unchanged |
Wczytany z bazy, bez zmian |
Modified |
Zmodyfikowany w sesji |
Added |
Nowy, dodany do tabeli, nie zapisany w bazie |
Deleted |
Skasowany, do usunięcia z bazy |
Klucze i indeksy
Definiowane w *.business.xml, mapowane na indeksy w bazie danych.
<key name="WgKodu" keyunique="true" keyprimary="true">
<keycol name="Kod"/>
</key>
| Atrybut | Znaczenie |
|---|---|
keyprimary="true" |
Klucz podstawowy (domyślne sortowanie) |
keyunique="true" |
Wartości unikalne w tabeli |
Uwaga: keyprimary w business.xml to nie to samo co Primary Key w bazie (który jest zawsze na kolumnie ID).
Interfejs ISessionable
public interface ISessionable {
Session Session { get; }
}
Implementują go: Session, Module, Table, Row, Context, Key.
Używany jako argument funkcji wymagających kontekstu sesji:
// Metoda statyczna GetInstance - akceptuje ISessionable
public static TowaryModule GetInstance(ISessionable session)
// Extension method - wygodniejsza składnia (tylko dla Session)
public static TowaryModule GetTowary(this Session session)
Zalecane użycie:
// Extension method (prostsze)
var tm = session.GetTowary();
var tm = context.Session.GetTowary();
var tm = towar.Session.GetTowary();
// GetInstance (gdy mamy ISessionable, ale property Session jest niedostępne)
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:
- 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) {...})
Serwisy
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)
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
[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
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()
// 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
Row row1 = ...;
Row row2 = ...;
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
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
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 - BusApplication, Database, Login, Session
- references/datapack-guidedrow.md - Paczki danych, GuidedRow, ExportedRow, synchronizacja
- references/context.md - Klasa Context, komunikacja UI ↔ logika
- references/examples.md - Przykłady kodu i wzorce użycia
- 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
using (var session = login.CreateSession(readOnly: true, config: false, name: "Odczyt"))
{
var tm = session.GetTowary(); // Extension method
foreach (Towar t in tm.Towary.WgKodu)
{
Console.WriteLine($"{t.Kod}: {t.Nazwa}");
}
}
Tworzenie nowego obiektu
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();
tm.Towary.AddRow(towar);
towar.Kod = "NOWY001";
towar.Nazwa = "Nowy towar";
transaction.Commit();
}
session.Save();
}
Modyfikacja istniejącego obiektu
using (var session = login.CreateSession(readOnly: false, config: false, name: "Edycja"))
{
var tm = session.GetTowary();
var towar = tm.Towary.WgKodu["STARY001"];
if (towar != null)
{
using (var transaction = session.Logout(editMode: true))
{
towar.Nazwa = "Zmieniona nazwa";
transaction.Commit();
}
}
session.Save();
}
Konwencje nazewnicze
| Element | Konwencja | Przykład |
|---|---|---|
| Klasa wiersza (Row) | PascalCase, l.poj. | Towar, Kontrahent |
| Klasa tabeli | PascalCase, l.mn. | Towary, Kontrahenci |
| Klasa modułu | Nazwa + Module | TowaryModule |
| Klucz | Wg + nazwa kolumny | WgKodu, WgNazwy |
| Namespace | Soneta.NazwaModułu | Soneta.Towary |
Język identyfikatorów
| Typ | Język | Przykłady |
|---|---|---|
| Logika biznesowa | polski | Towar, Kontrahent, DokumentHandlowy, Faktura |
| Identyfikatory systemowe | angielski | Session, Context, Row, Table, Module |
Można łączyć polski i angielski w nazwach metod i klas:
RetrieveTowary()
UpdateKontrahent()
GetDokumentyHandlowe()
CreateFaktura()