Przemianowałem istnijące skille na prostsze nazwy - porządki
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
# Klasa Context
|
||||
|
||||
Dokumentacja klasy Context odpowiedzialnej za komunikację między warstwą logiki biznesowej a interfejsem graficznym.
|
||||
|
||||
## Czym jest Context?
|
||||
|
||||
Context to **kontener par klucz-wartość**, gdzie:
|
||||
- **Klucz** = typ (Type)
|
||||
- **Wartość** = obiekt tego typu (object)
|
||||
|
||||
Kontekst jest stale aktualizowany podczas pracy z programem i przechowuje informacje o aktualnym stanie interfejsu.
|
||||
|
||||
## Zawartość kontekstu
|
||||
|
||||
Przykładowa zawartość przy otwartej liście kontrahentów:
|
||||
|
||||
| Typ | Opis |
|
||||
|-----|------|
|
||||
| `SelectedCounter` | Liczba zaznaczonych wierszy na gridzie |
|
||||
| `Kontrahent[]` | Kolekcja zaznaczonych kontrahentów |
|
||||
| `UILocation` | Aktywny element interfejsu |
|
||||
| `INavigatorContext` | Kontekst grida (zaznaczenia, focus) |
|
||||
| `View` | Źródło danych grida |
|
||||
| `Params` | Klasa parametrów filtrów |
|
||||
| `Login` | Zalogowany użytkownik |
|
||||
|
||||
## Odczyt z kontekstu
|
||||
|
||||
### Metody GetOrDefault i GetRequired (zalecane)
|
||||
|
||||
```csharp
|
||||
public void Action(Context context)
|
||||
{
|
||||
// Zwraca obiekt lub null, gdy brak obiektu
|
||||
Kontrahent? knt = context.GetOrDefault<Kontrahent>();
|
||||
|
||||
// Zwraca obiekt lub wyjątek, gdy brak obiektu
|
||||
Kontrahent knt2 = context.GetRequired<Kontrahent>();
|
||||
}
|
||||
```
|
||||
|
||||
### Przez indeksator
|
||||
|
||||
```csharp
|
||||
public void Action(Context cx)
|
||||
{
|
||||
// Rzuca wyjątek gdy brak obiektu w kontekście
|
||||
Kontrahent knt = (Kontrahent)cx[typeof(Kontrahent)];
|
||||
|
||||
// Bez wyjątku - drugi parametr
|
||||
Kontrahent knt2 = (Kontrahent)cx[typeof(Kontrahent), false];
|
||||
}
|
||||
```
|
||||
|
||||
### Przez metodę Get<T> (bezpieczna)
|
||||
|
||||
```csharp
|
||||
public void Action(Context cx)
|
||||
{
|
||||
// Zwraca true jeśli znaleziono, false jeśli nie
|
||||
if (cx.Get(out DokumentHandlowy dokument))
|
||||
{
|
||||
// dokument znaleziony
|
||||
}
|
||||
else
|
||||
{
|
||||
// dokument nie znaleziony (dokument = null)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Zapis do kontekstu
|
||||
|
||||
```csharp
|
||||
public void Action(Context cx)
|
||||
{
|
||||
// Przez indeksator z określeniem typu
|
||||
Kontrahent knt = ...;
|
||||
cx[typeof(Kontrahent)] = knt;
|
||||
|
||||
// Przez metodę Set
|
||||
DokumentHandlowy dok = ...;
|
||||
cx.Set(dok);
|
||||
}
|
||||
```
|
||||
|
||||
## Zastosowania
|
||||
|
||||
### 1. Filtry na listach głównych
|
||||
|
||||
Klasy parametrów filtrów dziedziczą z `ContextBase` i są automatycznie w kontekście.
|
||||
|
||||
```csharp
|
||||
// Definicja klasy parametrów (w ViewInfo)
|
||||
public class TowaryParams(Context context) : ContextBase(context)
|
||||
{
|
||||
public Magazyn? Magazyn
|
||||
{
|
||||
get => Context.GetOrDefault<Magazyn>();
|
||||
set => Context.Set(value);
|
||||
}
|
||||
|
||||
public TypTowaru Typ {
|
||||
get => Context.GetOrDefault<TypTowaru>();
|
||||
set => Context.Set(value);
|
||||
}
|
||||
|
||||
[Accessor(AutoChange = true)]
|
||||
public string SearchString { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- Bindowanie w viewform.xml -->
|
||||
<Field CaptionHtml="Magazyn" EditValue="{TowaryParams.Magazyn}"/>
|
||||
<Field CaptionHtml="Typ" EditValue="{TowaryParams.Typ}"/>
|
||||
<Field CaptionHtml="Szukaj" EditValue="{TowaryParams.SearchString}"/>
|
||||
```
|
||||
|
||||
Wartości filtrów są dostępne przez kontekst dla:
|
||||
- Widoków (filtrowanie danych)
|
||||
- Workerów (właściwości wyliczane)
|
||||
- Wydruków
|
||||
|
||||
### 2. Wartości domyślne nowych obiektów
|
||||
|
||||
Kontekst używany do inicjalizacji nowych obiektów wartościami z filtrów. Uzupełniane są właściwości zaznaczone atrybutem `[Context]`, który oznacza próbę odczytania wartości do ustawienia property z kontekstu.
|
||||
|
||||
```
|
||||
Lista faktur:
|
||||
Filtr Magazyn: "Sklep"
|
||||
Filtr Kontrahent: "Drynda"
|
||||
↓
|
||||
Nowy dokument:
|
||||
Magazyn: "Sklep" (z kontekstu - property z [Context])
|
||||
Kontrahent: "Drynda" (z kontekstu - property z [Context])
|
||||
```
|
||||
|
||||
### 3. Wydruki
|
||||
|
||||
Wydruki mają dostęp do obiektów z kontekstu jako źródła danych.
|
||||
|
||||
```csharp
|
||||
// W kodzie wydruku
|
||||
Context cx = ...;
|
||||
if (cx.Get(out Kontrahent[] kontrahenci))
|
||||
{
|
||||
// kontrahenci[] = zaznaczone na liście
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Workery - atrybut [Context]
|
||||
|
||||
Workery mogą pobierać parametry z kontekstu automatycznie.
|
||||
|
||||
```csharp
|
||||
public class MojWorker
|
||||
{
|
||||
[Context] // Pobierane z kontekstu
|
||||
public Magazyn Magazyn { get; set; }
|
||||
|
||||
[Context] // Jeśli brak w kontekście - okno parametrów
|
||||
public Kontrahent Kontrahent { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Właściwości wyliczane zależne od filtrów
|
||||
|
||||
```csharp
|
||||
// Worker wyliczający stan magazynowy
|
||||
public class StanMagazynu : IWorker
|
||||
{
|
||||
public object Compute(Context cx, object source)
|
||||
{
|
||||
Towar towar = source as Towar;
|
||||
|
||||
// Pobranie magazynu z kontekstu (z filtra)
|
||||
if (cx.Get(out Magazyn magazyn))
|
||||
{
|
||||
return towar.GetStan(magazyn);
|
||||
}
|
||||
return towar.GetStanCalkowity();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Klasa ContextBase
|
||||
|
||||
Bazowa klasa dla obiektów automatycznie umieszczanych w kontekście.
|
||||
|
||||
```csharp
|
||||
public class MojaKlasaParametrow(Context context) : ContextBase(context)
|
||||
{
|
||||
[Accessor(AutoChange = true)]
|
||||
public Date DataOd { get; set; }
|
||||
|
||||
[Accessor(AutoChange = true)]
|
||||
public Date DataDo { get; set; }
|
||||
|
||||
public Kontrahent Kontrahent {
|
||||
get => Context.GetOrDefault<Kontrahent>();
|
||||
set => Context.Set(value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Obiekty dziedziczące z `ContextBase` i nie tylko:
|
||||
- Są automatycznie dodawane do kontekstu
|
||||
- Wywoływane jest zdarzenie `OnChanged` przy zmianie właściwości
|
||||
- Obsługują bindowanie do kontrolek UI
|
||||
- Właściwości połączone z UI (formularze, parametry) mogą być zadeklarowane z `[Accessor(AutoChange = true)]`, dzięki czemu Accessor automatycznie uruchomi mechanizm powiadamiania o zmianach i nie będzie konieczne wywołanie `Session.InvokeChanged()` lub `Context.InvokeChanged()`
|
||||
|
||||
## Interfejs INavigatorContext
|
||||
|
||||
Dostępny w kontekście gdy aktywna jest lista (grid).
|
||||
|
||||
```csharp
|
||||
public void Action(Context cx)
|
||||
{
|
||||
if (cx.Get(out INavigatorContext nav))
|
||||
{
|
||||
// Wiersz z focusem
|
||||
object current = nav.Current;
|
||||
|
||||
// Zaznaczone wiersze
|
||||
IEnumerable selected = nav.Selected;
|
||||
|
||||
// Liczba zaznaczonych
|
||||
int count = nav.SelectedCount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kolekcje zaznaczonych obiektów
|
||||
|
||||
W kontekście znajdują się tablice zaznaczonych obiektów.
|
||||
|
||||
```csharp
|
||||
public void Action(Context cx)
|
||||
{
|
||||
// Zaznaczeni kontrahenci
|
||||
if (cx.Get(out Kontrahent[] kontrahenci))
|
||||
{
|
||||
foreach (var k in kontrahenci)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// Zaznaczone dokumenty
|
||||
if (cx.Get(out DokumentHandlowy[] dokumenty))
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Context implementuje ISessionable
|
||||
|
||||
```csharp
|
||||
public void Action(Context cx)
|
||||
{
|
||||
// Dostęp do sesji przez kontekst
|
||||
Session session = cx.Session;
|
||||
|
||||
// Dostęp do modułu przez sesję
|
||||
var tm = session.GetTowary();
|
||||
}
|
||||
```
|
||||
|
||||
## Pełny przykład - Worker z kontekstem
|
||||
|
||||
```csharp
|
||||
[assembly: Worker<TowarExtender, Towar>]
|
||||
|
||||
namespace Soneta.Towary;
|
||||
|
||||
public class TowarExtenderParams(Context context) : ContextBase(context)
|
||||
{
|
||||
[Accessor(AutoChange = true)]
|
||||
[Caption("Magazyn filtrowania")]
|
||||
public Magazyn MagazynFiltra { get; set; }
|
||||
}
|
||||
|
||||
public class TowarExtender
|
||||
{
|
||||
[Context]
|
||||
public TowarExtenderParams Params { get; set; }
|
||||
|
||||
[Context]
|
||||
public Towar Towar { get; set; }
|
||||
|
||||
public decimal StanWMagazynie =>
|
||||
Params.MagazynFiltra != null
|
||||
? PoliczStanMagazynu(Towar, Params.MagazynFiltra)
|
||||
: PoliczStanMagazynu(Towar);
|
||||
|
||||
private decimal PoliczStanMagazynu(Towar towar, Magazyn magazyn)
|
||||
{
|
||||
// Wyliczyć stan we wskazanym magazynie
|
||||
return 0;
|
||||
}
|
||||
|
||||
private decimal PoliczStanMagazynu(Towar towar)
|
||||
{
|
||||
// Wyliczyć stan w całej firmie
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dobre praktyki
|
||||
|
||||
1. **Używaj Get<T>, GetOrDefault<T>, GetRequired<T>** zamiast indeksatora - bezpieczniejsze
|
||||
2. **Sprawdzaj obecność** obiektów w kontekście przed użyciem
|
||||
3. **Dziedzicz z ContextBase** dla własnych klas parametrów i pamiętaj o konstruktorze `(Context context)`
|
||||
4. **Używaj [Context]** w workerach dla parametrów z kontekstu
|
||||
5. **Stosuj `[Accessor(AutoChange = true)]`** lub `InvokeChanged()` dla powiadamiania UI o zmianach
|
||||
|
||||
## Typowe typy w kontekście
|
||||
|
||||
| Typ | Kiedy dostępny |
|
||||
|-----|----------------|
|
||||
| `Login` | Zawsze po zalogowaniu |
|
||||
| `Database` | Zawsze po zalogowaniu |
|
||||
| `LicencjaProgramu` | Zawsze po zalogowaniu |
|
||||
| `Session` | Gdy aktywny widok z danymi |
|
||||
| `View` | Gdy aktywna lista |
|
||||
| `INavigatorContext` | Gdy aktywna lista |
|
||||
| `[Obiekt][]` | Zaznaczenia na liście |
|
||||
| `[ViewInfo]+Params` | Klasa parametrów widoku |
|
||||
| `UILocation` | Lokalizacja w UI |
|
||||
@@ -0,0 +1,245 @@
|
||||
# Datapack i GuidedRow - Paczki danych
|
||||
|
||||
Dokumentacja mechanizmu paczek danych (Datapack) służącego do grupowania powiązanych obiektów i ich synchronizacji między bazami danych.
|
||||
|
||||
## Czym jest Datapack?
|
||||
|
||||
**Datapack** to struktura obiektów różnych typów powiązanych relacjami, które tworzą logiczną całość.
|
||||
|
||||
### Przykład: Dokument handlowy
|
||||
|
||||
```
|
||||
DokumentHandlowy (Root)
|
||||
├── PozycjaDokHandlowego[] (Child)
|
||||
├── SumaVAT[] (Child)
|
||||
└── Platnosc[] (Child)
|
||||
```
|
||||
|
||||
Faktura bez pozycji nie jest kompletną fakturą - wszystkie te obiekty stanowią jedną paczkę danych.
|
||||
|
||||
## Hierarchia klas Row i Table
|
||||
|
||||
```
|
||||
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 definicji tabeli określa rolę obiektu w strukturze Datapack.
|
||||
|
||||
| Wartość | Klasa bazowa | Opis |
|
||||
|---------|--------------|------|
|
||||
| `Root` | `GuidedRow` | Główny element paczki danych. Posiada GUID. |
|
||||
| `Exported` | `ExportedRow` | Jak Root + flaga Exported (czy wyeksportowany) |
|
||||
| `Child` | `Row` | Element podrzędny w paczce. Musi mieć relację `relguided`. |
|
||||
| `None` | `Row` | Nie uczestniczy w mechanizmie Datapack |
|
||||
|
||||
**Domyślnie:** `guided="Child"`
|
||||
|
||||
### Przykłady definicji
|
||||
|
||||
```xml
|
||||
<!-- ROOT - główna kartoteka towaru -->
|
||||
<table name="Towar" tablename="Towary" guided="Root">
|
||||
...
|
||||
</table>
|
||||
|
||||
<!-- EXPORTED - dokument do synchronizacji -->
|
||||
<table name="DokumentHandlowy" tablename="DokHandlowe" guided="Exported">
|
||||
...
|
||||
</table>
|
||||
|
||||
<!-- CHILD - pozycje dokumentu (domyślnie) -->
|
||||
<table name="PozycjaDokHandlowego" tablename="PozycjeDokHan">
|
||||
<col name="Dokument" type="DokumentHandlowy"
|
||||
relguided="inner" delete="cascade"
|
||||
children="Pozycje"/>
|
||||
...
|
||||
</table>
|
||||
```
|
||||
|
||||
## Atrybut relguided
|
||||
|
||||
Określa relację obiektu Child do obiektu Root w strukturze Datapack.
|
||||
|
||||
| Wartość | Opis |
|
||||
|---------|------|
|
||||
| `inner` | Obiekt podrzędny zagnieżdżony wewnątrz roota w XML |
|
||||
| `outer` | Obiekt podrzędny poza rootem w XML (ale powiązany) |
|
||||
| (puste) | Relacja nie jest częścią Datapack |
|
||||
|
||||
### Kiedy używać relguided
|
||||
|
||||
```xml
|
||||
<!-- Pozycja dokumentu - CZĘŚĆ datapacka dokumentu -->
|
||||
<col name="Dokument" type="DokumentHandlowy"
|
||||
relguided="inner" <!-- ← powiązanie w Datapack -->
|
||||
delete="cascade"
|
||||
children="Pozycje"/>
|
||||
|
||||
<!-- Towar na pozycji - NIE jest częścią datapacka dokumentu -->
|
||||
<col name="Towar" type="Towar" <!-- ← brak relguided -->
|
||||
children="Pozycje"/>
|
||||
```
|
||||
|
||||
**Reguły:**
|
||||
- Tabela Child może mieć **tylko jedną** kolumnę z `relguided`
|
||||
- Tabela Root **nie posiada** relacji `relguided` (jest korzeniem paczki)
|
||||
|
||||
## GuidedRow - szczegóły
|
||||
|
||||
### Właściwości
|
||||
|
||||
| Właściwość | Typ | Opis |
|
||||
|------------|-----|------|
|
||||
| `Guid` | `System.Guid` | Globalnie unikalny identyfikator |
|
||||
| `Attachments` | `AttachmentCollection` | Kolekcja załączników |
|
||||
| `DefaultImage` | `Attachment` | Domyślne zdjęcie obiektu |
|
||||
| `Note` | `string` | Notatka tekstowa |
|
||||
| `FirstChangeInfo` | `ChangeInfo` | Pierwsza zmiana w historii |
|
||||
| `LastChangeInfo` | `ChangeInfo` | Ostatnia zmiana w historii |
|
||||
| `IsAdded` | `bool` | Czy nowo dodany |
|
||||
| `IsModified` | `bool` | Czy zmodyfikowany |
|
||||
| `IsDeleted` | `bool` | Czy skasowany |
|
||||
|
||||
### Załączniki
|
||||
|
||||
Załączniki można podpinać **tylko** do obiektów wywodzących się z `GuidedRow`.
|
||||
|
||||
```csharp
|
||||
GuidedRow row = ...;
|
||||
|
||||
// Dostęp do załączników
|
||||
foreach (Attachment att in row.Attachments)
|
||||
{
|
||||
Console.WriteLine(att.Nazwa);
|
||||
}
|
||||
|
||||
// Domyślne zdjęcie
|
||||
Attachment img = row.DefaultImage;
|
||||
```
|
||||
|
||||
## ExportedRow
|
||||
|
||||
Rozszerza `GuidedRow` o flagę `Exported`.
|
||||
|
||||
### Użycie
|
||||
|
||||
Obiekty, które po eksporcie do systemów zewnętrznych **nie powinny być modyfikowane**.
|
||||
|
||||
**Przykład:** Przelew wyeksportowany do systemu bankowego - po zaksięgowaniu nie powinien być zmieniany w ERP.
|
||||
|
||||
```xml
|
||||
<table name="PrzelewBase" tablename="Przelewy" guided="Exported">
|
||||
...
|
||||
</table>
|
||||
```
|
||||
|
||||
### Właściwość
|
||||
|
||||
| Właściwość | Typ | Opis |
|
||||
|------------|-----|------|
|
||||
| `Exported` | `bool` | `true` = obiekt wyeksportowany, nie modyfikować |
|
||||
|
||||
## Historia zmian (ChangeInfos)
|
||||
|
||||
System loguje zmiany na obiektach `GuidedRow`.
|
||||
|
||||
### Tabela ChangeInfos
|
||||
|
||||
| Kolumna | Opis |
|
||||
|---------|------|
|
||||
| `SourceTable` | Nazwa tabeli źródłowej |
|
||||
| `SourceGuid` | GUID zmodyfikowanego obiektu |
|
||||
| `Time` | Data i czas zmiany |
|
||||
| `Operator` | Kto dokonał zmiany |
|
||||
|
||||
### Dostęp do historii
|
||||
|
||||
```csharp
|
||||
var bm = session.GetBusiness();
|
||||
|
||||
// Iteracja po historii zmian kontrahenta
|
||||
Kontrahent knt = ...;
|
||||
foreach (ChangeInfo ci in bm.ChangeInfos[knt])
|
||||
{
|
||||
Console.WriteLine($"{ci.Time}: {ci.Operator}");
|
||||
}
|
||||
|
||||
// Skróty na obiekcie
|
||||
Console.WriteLine($"Utworzono: {knt.FirstChangeInfo?.Time}");
|
||||
Console.WriteLine($"Ostatnia zmiana: {knt.LastChangeInfo?.Time}");
|
||||
```
|
||||
|
||||
**Uwaga:** Zmiany na obiektach Child są rejestrowane na poziomie Root.
|
||||
|
||||
## Blokady
|
||||
|
||||
Modyfikacja **dowolnego** obiektu w strukturze Datapack zakłada blokadę na poziomie **Root**.
|
||||
|
||||
```
|
||||
DokumentHandlowy ← BLOKADA
|
||||
├── PozycjaDokHandlowego (modyfikacja tutaj)
|
||||
└── SumaVAT
|
||||
```
|
||||
|
||||
Nie można równolegle edytować obiektów należących do jednego Datapacka.
|
||||
|
||||
## GuidedTable
|
||||
|
||||
Odpowiednik `GuidedRow` dla tabel - dodaje indeksator po GUID.
|
||||
|
||||
```csharp
|
||||
GuidedTable<Towar> towary = ...;
|
||||
|
||||
// Pobranie obiektu po GUID
|
||||
Guid guid = new Guid("65336878-70cf-4e64-bd72-b742cd26a657");
|
||||
Towar towar = towary[guid];
|
||||
```
|
||||
|
||||
## Wzorce użycia
|
||||
|
||||
### Definiowanie struktury Datapack
|
||||
|
||||
```xml
|
||||
<!-- 1. Root - główny obiekt -->
|
||||
<table name="Zamowienie" tablename="Zamowienia" guided="Root">
|
||||
<key name="WgNumeru" keyprimary="true" keyunique="true">
|
||||
<keycol name="Numer"/>
|
||||
</key>
|
||||
<col name="Numer" type="string" length="30" required="true"/>
|
||||
<col name="Data" type="date" required="true"/>
|
||||
<col name="Kontrahent" type="Kontrahent" required="true"/>
|
||||
</table>
|
||||
|
||||
<!-- 2. Child - pozycje zamówienia -->
|
||||
<table name="PozycjaZamowienia" tablename="PozycjeZamowien">
|
||||
<!-- Jedna relacja relguided="inner" -->
|
||||
<col name="Zamowienie" type="Zamowienie"
|
||||
required="true" readonly="true"
|
||||
relguided="inner" delete="cascade"
|
||||
children="Pozycje" keyprimary="true"/>
|
||||
<col name="Lp" type="int" required="true"/>
|
||||
<col name="Towar" type="Towar" required="true"/>
|
||||
<col name="Ilosc" type="double" required="true"/>
|
||||
</table>
|
||||
```
|
||||
|
||||
## Podsumowanie atrybutów
|
||||
|
||||
| Atrybut | Gdzie | Wartości | Opis |
|
||||
|---------|-------|----------|------|
|
||||
| `guided` | `<table>` | Root, Exported, Child, None | Rola w Datapack |
|
||||
| `relguided` | `<col>` | inner, outer, (puste) | Relacja Child→Root |
|
||||
| `delete` | `<col>` | cascade, restrict, setnull | Akcja przy usuwaniu roota |
|
||||
@@ -0,0 +1,643 @@
|
||||
# Przykłady kodu - Podstawowe klasy ORM
|
||||
|
||||
Praktyczne przykłady użycia podstawowych klas logiki biznesowej enova365/Soneta.
|
||||
|
||||
## Ważne zasady
|
||||
|
||||
### Thread-safety
|
||||
|
||||
**Obiekty single-threaded** - nie współdziel między wątkami:
|
||||
- `Session`, `Module`, `Table`, `Row`, `Context`
|
||||
|
||||
**Obiekty multi-threaded** - można współdzielić:
|
||||
- `BusApplication`, `Database`, `Login`
|
||||
|
||||
Każdy wątek powinien tworzyć własną sesję (Login można współdzielić).
|
||||
|
||||
### Extension methods dla modułów
|
||||
|
||||
Dostęp do modułów przez extension methods:
|
||||
|
||||
```csharp
|
||||
var tm = session.GetTowary();
|
||||
var hm = session.GetHandel();
|
||||
var crm = session.GetCRM();
|
||||
var kadry = session.GetKadry();
|
||||
var bm = session.GetBusiness();
|
||||
```
|
||||
|
||||
### Transakcje biznesowe
|
||||
|
||||
**Każda zmiana obiektu MUSI być w transakcji** `Session.Logout(editMode: true)`:
|
||||
|
||||
```csharp
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
// Zmiany: dodawanie, modyfikacja, kasowanie
|
||||
obiekt.Wlasciwosc = nowaWartosc;
|
||||
transaction.Commit(); // Zatwierdza zmiany
|
||||
}
|
||||
// Brak Commit() = automatyczny rollback
|
||||
|
||||
session.Save(); // Zapis do bazy danych
|
||||
```
|
||||
|
||||
## Dostęp do danych
|
||||
|
||||
### Odczyt listy towarów
|
||||
|
||||
```csharp
|
||||
using Soneta.Business;
|
||||
using Soneta.Towary;
|
||||
|
||||
public void WyswietlTowary(Login login)
|
||||
{
|
||||
// Sesja tylko do odczytu
|
||||
using (var session = login.CreateSession(true, false, "OdczytTowarow"))
|
||||
{
|
||||
var tm = session.GetTowary(); // Extension method
|
||||
|
||||
// Iteracja po kluczu podstawowym (WgKodu)
|
||||
foreach (Towar t in tm.Towary.WgKodu)
|
||||
{
|
||||
Console.WriteLine($"{t.Kod}: {t.Nazwa}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Wyszukiwanie po kluczu
|
||||
|
||||
```csharp
|
||||
public Towar ZnajdzTowar(Session session, string kod)
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
// Wyszukiwanie po kluczu unikalnym
|
||||
return tm.Towary.WgKodu[kod];
|
||||
}
|
||||
```
|
||||
|
||||
### Iteracja z filtrowaniem
|
||||
|
||||
```csharp
|
||||
public void WyswietlAktywneTowary(Session session)
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
foreach (Towar t in tm.Towary.WgKodu)
|
||||
{
|
||||
// Filtrowanie w kodzie
|
||||
if (t.Typ == TypTowaru.Towar)
|
||||
{
|
||||
Console.WriteLine(t.Nazwa);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tworzenie obiektów
|
||||
|
||||
### Dodawanie nowego towaru
|
||||
|
||||
```csharp
|
||||
public void DodajTowar(Login login)
|
||||
{
|
||||
// Sesja edycyjna
|
||||
using (var session = login.CreateSession(false, false, "DodawanieTowaru"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
// Transakcja biznesowa - wymagana!
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
// Utworzenie nowego obiektu
|
||||
var towar = new Towar();
|
||||
|
||||
// Dodanie do tabeli (zmiana stanu na Added)
|
||||
tm.Towary.AddRow(towar);
|
||||
|
||||
// Ustawienie właściwości
|
||||
towar.Kod = "NOWY001";
|
||||
towar.Nazwa = "Nowy towar";
|
||||
towar.Typ = TypTowaru.Towar;
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
// Zapisanie do bazy
|
||||
session.Save();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dodawanie dokumentu z pozycjami
|
||||
|
||||
```csharp
|
||||
public void UtworzFakture(Login login, Kontrahent kontrahentZInnejSesji,
|
||||
List<(Towar towar, int ilosc)> pozycjeZInnejSesji)
|
||||
{
|
||||
using (var session = login.CreateSession(false, false, "TworzenieFaktury"))
|
||||
{
|
||||
var hm = session.GetHandel();
|
||||
|
||||
// WAŻNE: Obiekty z innej sesji trzeba doczytać w bieżącej sesji!
|
||||
var kontrahent = session.Get(kontrahentZInnejSesji);
|
||||
|
||||
// Cała operacja w jednej transakcji
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
// Utworzenie nagłówka dokumentu
|
||||
var faktura = new DokumentHandlowy();
|
||||
hm.DokHandlowe.AddRow(faktura);
|
||||
|
||||
faktura.Definicja = hm.DefDokHandlowe.WgSymbolu["FV"];
|
||||
faktura.Kontrahent = kontrahent;
|
||||
faktura.Data = Date.Today;
|
||||
|
||||
// Dodanie pozycji
|
||||
int lp = 1;
|
||||
foreach (var (towarZInnejSesji, ilosc) in pozycjeZInnejSesji)
|
||||
{
|
||||
// Doczytaj towar w bieżącej sesji
|
||||
var towar = session.Get(towarZInnejSesji);
|
||||
|
||||
var poz = new PozycjaDokHandlowego(faktura);
|
||||
faktura.Pozycje.AddRow(poz);
|
||||
|
||||
poz.Towar = towar;
|
||||
poz.Ilosc = new Quantity(ilosc, towar.Jednostka.Kod);
|
||||
poz.Lp = lp++;
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
session.Save();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**WAŻNE:** W jednej sesji nie można mieszać obiektów z różnych sesji. Użyj `session.Get(obiekt)` aby doczytać obiekt w bieżącej sesji.
|
||||
|
||||
## Modyfikacja obiektów
|
||||
|
||||
### Aktualizacja pojedynczego obiektu
|
||||
|
||||
```csharp
|
||||
public void ZmienNazweTowaru(Login login, string kod, string nowaNazwa)
|
||||
{
|
||||
using (var session = login.CreateSession(false, false, "EdycjaTowaru"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
var towar = tm.Towary.WgKodu[kod];
|
||||
|
||||
if (towar != null)
|
||||
{
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
towar.Nazwa = nowaNazwa;
|
||||
transaction.Commit();
|
||||
}
|
||||
session.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Aktualizacja z transakcją biznesową
|
||||
|
||||
```csharp
|
||||
public void AktualizujCeny(Login login, string nazwaCeny, decimal procentPodwyzki)
|
||||
{
|
||||
using (var session = login.CreateSession(false, false, "AktualizacjaCen"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
foreach (Towar t in tm.Towary.WgKodu)
|
||||
{
|
||||
// Transakcja biznesowa - WYMAGANA dla każdej zmiany!
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
var cena = t.Ceny[nazwaCeny];
|
||||
if (cena != null)
|
||||
{
|
||||
cena.Netto = new DoubleCy(cena.Netto.Value * (1 + procentPodwyzki / 100));
|
||||
}
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
session.Save(); // Zapisuje wszystkie zmiany do bazy
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usuwanie obiektów
|
||||
|
||||
### Usuwanie obiektu
|
||||
|
||||
```csharp
|
||||
public void UsunTowar(Login login, string kod)
|
||||
{
|
||||
using (var session = login.CreateSession(false, false, "UsuwanieTowaru"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
var towar = tm.Towary.WgKodu[kod];
|
||||
|
||||
if (towar != null)
|
||||
{
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
towar.Delete(); // Zmiana stanu na Deleted
|
||||
transaction.Commit();
|
||||
}
|
||||
session.Save(); // Fizyczne usunięcie z bazy
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Praca z kontekstem
|
||||
|
||||
### Worker wyliczający właściwość
|
||||
|
||||
```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
|
||||
|
||||
### Dostęp do historii zmian
|
||||
|
||||
```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
|
||||
|
||||
### Odczyt danych konfiguracyjnych
|
||||
|
||||
```csharp
|
||||
// Odczyt pojedynczej wartości
|
||||
var opisDlaSzt = login.ExecuteConfig(configSession =>
|
||||
configSession.GetTowary().Jednostki.WgKodu["szt"]?.Opis);
|
||||
|
||||
// Odczyt listy wartości prostych
|
||||
public string[] PobierzKodyJednostek(Login login)
|
||||
{
|
||||
return login.ExecuteConfig(configSession =>
|
||||
{
|
||||
var tm = configSession.GetTowary();
|
||||
return tm.Jednostki.WgKodu.Select(j => j.Kod).ToArray();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**WAŻNE:** Używając `ExecuteConfig()` nie można zwracać obiektów sesyjnych z sesji konfiguracyjnej, ponieważ może być używana w innym wątku do innych celów. Zwracaj tylko wartości proste lub kopie danych.
|
||||
|
||||
```csharp
|
||||
public void OdczytKonfiguracji(Login login)
|
||||
{
|
||||
// Własna sesja konfiguracyjna - gdy potrzebny dostęp do obiektów
|
||||
using (var session = login.CreateSession(true, true, "Konfiguracja"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
foreach (Jednostka j in tm.Jednostki.WgKodu)
|
||||
{
|
||||
Console.WriteLine($"{j.Kod}: {j.Opis}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Modyfikacja danych konfiguracyjnych
|
||||
|
||||
```csharp
|
||||
public void DodajJednostke(Login login, string kod, string opis)
|
||||
{
|
||||
// Sesja edycyjna konfiguracyjna
|
||||
using (var session = login.CreateSession(false, true, "DodawanieJednostki"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
var jednostka = new Jednostka();
|
||||
tm.Jednostki.AddRow(jednostka);
|
||||
|
||||
jednostka.Kod = kod;
|
||||
jednostka.Opis = opis;
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
session.Save();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Pełny przykład - import towarów
|
||||
|
||||
```csharp
|
||||
public class ImportTowarow
|
||||
{
|
||||
public void Importuj(Login login, string sciezkaPliku)
|
||||
{
|
||||
var dane = WczytajZPliku(sciezkaPliku);
|
||||
|
||||
using (var session = login.CreateSession(false, false, "ImportTowarow"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
int dodano = 0;
|
||||
int zaktualizowano = 0;
|
||||
|
||||
// Cały import w jednej transakcji
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
foreach (var wiersz in dane)
|
||||
{
|
||||
// Sprawdź czy towar istnieje
|
||||
var towar = tm.Towary.WgKodu[wiersz.Kod];
|
||||
|
||||
if (towar == null)
|
||||
{
|
||||
// Dodaj nowy
|
||||
towar = new Towar();
|
||||
tm.Towary.AddRow(towar);
|
||||
towar.Kod = wiersz.Kod;
|
||||
dodano++;
|
||||
}
|
||||
else
|
||||
{
|
||||
zaktualizowano++;
|
||||
}
|
||||
|
||||
// Ustaw/aktualizuj właściwości
|
||||
towar.Nazwa = wiersz.Nazwa;
|
||||
var cena = towar.Ceny["Hurtowa"];
|
||||
cena.Netto = new DoubleCy(wiersz.Cena);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
session.Save();
|
||||
|
||||
Console.WriteLine($"Import zakończony:");
|
||||
Console.WriteLine($" Dodano: {dodano}");
|
||||
Console.WriteLine($" Zaktualizowano: {zaktualizowano}");
|
||||
}
|
||||
}
|
||||
|
||||
private List<DaneImportu> WczytajZPliku(string sciezka)
|
||||
{
|
||||
// Implementacja wczytywania z CSV/Excel...
|
||||
return new List<DaneImportu>();
|
||||
}
|
||||
|
||||
private class DaneImportu
|
||||
{
|
||||
public string Kod { get; set; }
|
||||
public string Nazwa { get; set; }
|
||||
public decimal Cena { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Obsługa błędów
|
||||
|
||||
### Wzorzec try-catch z sesją
|
||||
|
||||
```csharp
|
||||
public void BezpiecznaOperacja(Login login)
|
||||
{
|
||||
using (var session = login.CreateSession(false, false, "Operacja"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
// Operacje na danych...
|
||||
var towar = new Towar();
|
||||
tm.Towary.AddRow(towar);
|
||||
towar.Kod = "TEST";
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
session.Save();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Logowanie błędu
|
||||
Console.WriteLine($"Błąd: {ex.Message}");
|
||||
// Zmiany nie zostały zatwierdzone (brak Commit lub Save)
|
||||
// Sesja zostanie automatycznie zwolniona przez using
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Wzorzec z wieloma operacjami
|
||||
|
||||
```csharp
|
||||
public void WieleOperacji(Login login, List<string> kody)
|
||||
{
|
||||
using (var session = login.CreateSession(false, false, "WieleOperacji"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
var bledy = new List<string>();
|
||||
|
||||
foreach (var kod in kody)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
var towar = tm.Towary.WgKodu[kod];
|
||||
if (towar != null)
|
||||
{
|
||||
towar.Delete();
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
bledy.Add($"{kod}: {ex.Message}");
|
||||
// Kontynuuj z następnym elementem
|
||||
}
|
||||
}
|
||||
|
||||
session.Save(); // Zapisz udane operacje
|
||||
|
||||
if (bledy.Any())
|
||||
{
|
||||
Console.WriteLine("Błędy: " + string.Join(", ", bledy));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,356 @@
|
||||
# Session, Login, Database, BusApplication
|
||||
|
||||
Dokumentacja klas zarządzających połączeniem z bazą danych i sesjami w platformie enova365/Soneta.
|
||||
|
||||
## Hierarchia obiektów
|
||||
|
||||
```
|
||||
BusApplication.Instance (Singleton)
|
||||
└── Database[] (kolekcja baz danych)
|
||||
└── Login (uwierzytelniony użytkownik)
|
||||
└── Session[] (sesje robocze)
|
||||
└── Module → Table → Row
|
||||
```
|
||||
|
||||
## Klasa BusApplication
|
||||
|
||||
Singleton reprezentujący instancję aplikacji ERP. Tworzony podczas inicjalizacji systemu.
|
||||
|
||||
### Dostęp do instancji
|
||||
|
||||
```csharp
|
||||
// Singleton
|
||||
BusApplication app = BusApplication.Instance;
|
||||
|
||||
// Dostęp do bazy danych po nazwie
|
||||
Database db = BusApplication.Instance["BazaDemo"];
|
||||
|
||||
// Iteracja po wszystkich bazach
|
||||
foreach (Database db in BusApplication.Instance)
|
||||
{
|
||||
Console.WriteLine(db.Name);
|
||||
}
|
||||
```
|
||||
|
||||
### Właściwości
|
||||
|
||||
| Właściwość | Typ | Opis |
|
||||
|------------|-----|------|
|
||||
| `Instance` | `BusApplication` | Statyczny singleton |
|
||||
| `Is365` | `bool` | `true` = wersja HTML, `false` = wersja okienkowa |
|
||||
| `this[string]` | `Database` | Indeksator - baza po nazwie |
|
||||
|
||||
## Klasa Database
|
||||
|
||||
Abstrakcyjna klasa reprezentująca bazę danych. Konkretne implementacje dla wspieranych silników:
|
||||
|
||||
```
|
||||
Database (abstrakcyjna)
|
||||
└── SqlDatabase (abstrakcyjna)
|
||||
├── MsSqlDatabase (SQL Server)
|
||||
└── AzureDatabase (Azure SQL)
|
||||
```
|
||||
|
||||
**Uwaga:** MySqlDatabase oraz OracleDatabase nie są już obsługiwane.
|
||||
|
||||
### Konfiguracja
|
||||
|
||||
Obiekty Database są deserializowane z pliku `Lista baz danych.xml`.
|
||||
|
||||
### Właściwości
|
||||
|
||||
| Właściwość | Typ | Opis |
|
||||
|------------|-----|------|
|
||||
| `Name` | `string` | Nazwa bazy danych |
|
||||
| `DefaultDatabase` | `bool` | Czy baza domyślna |
|
||||
| `DatabaseName` | `string` | Nazwa bazy w silniku SQL (SqlDatabase) |
|
||||
|
||||
### Logowanie do bazy
|
||||
|
||||
```csharp
|
||||
Database db = BusApplication.Instance["BazaDemo"];
|
||||
|
||||
// ZALECANE: Logowanie z LoginParameters
|
||||
Login login = db.Login(new LoginParameters
|
||||
{
|
||||
UserName = "Administrator",
|
||||
UserPassword = "password"
|
||||
});
|
||||
|
||||
// Logowanie zintegrowane Windows
|
||||
Login login = db.Login(new LoginParameters
|
||||
{
|
||||
UserName = LoginParameters.WindowsAuthenticationUser
|
||||
});
|
||||
```
|
||||
|
||||
## Klasa Login
|
||||
|
||||
Obiekt reprezentujący zalogowanego użytkownika. Zarządza sesjami. IDisposable.
|
||||
|
||||
### Tworzenie
|
||||
|
||||
```csharp
|
||||
Database db = BusApplication.Instance["BazaDemo"];
|
||||
Login login = db.Login(new LoginParameters
|
||||
{
|
||||
UserName = "Administrator",
|
||||
UserPassword = "password"
|
||||
});
|
||||
```
|
||||
|
||||
### Dostęp do informacji o operatorze
|
||||
|
||||
**WAŻNE:** Właściwości `Operator` i `Entitle` w klasie `Login` nie należy stosować, ponieważ używają obiektu z `ConfigSession` w sposób niekontrolowany.
|
||||
|
||||
```csharp
|
||||
// NIE ZALECANE:
|
||||
// var op = login.Operator; // unikać!
|
||||
// var ent = login.Entitle; // unikać!
|
||||
|
||||
// ZALECANE: Użyj w konkretnej sesji
|
||||
using (var session = login.CreateSession(readOnly: true, config: false, name: "Odczyt"))
|
||||
{
|
||||
var op = session.AuthorizationInfo.Operator;
|
||||
Console.WriteLine($"Zalogowany: {op.Name} - {op.FullName}");
|
||||
}
|
||||
```
|
||||
|
||||
### Właściwości informacyjne
|
||||
|
||||
| Właściwość | Typ | Opis |
|
||||
|------------|-----|------|
|
||||
| `IsWebUser` | `bool` | Czy operator pulpitu (web) |
|
||||
| `Licencje` | `IEnumerable` | Pobrane licencje |
|
||||
| `Database` | `Database` | Baza danych logowania |
|
||||
|
||||
### Tworzenie sesji
|
||||
|
||||
```csharp
|
||||
// ZALECANA sygnatura (czytelność + debugowanie)
|
||||
Session session = login.CreateSession(
|
||||
readOnly: false, // true = tylko odczyt
|
||||
config: false, // true = dane konfiguracyjne
|
||||
name: "MojaSesja" // nazwa sesji - pomocna przy debugowaniu
|
||||
);
|
||||
```
|
||||
|
||||
**Zawsze używaj wersji z parametrem `name`** - ułatwia debugowanie i identyfikację sesji.
|
||||
|
||||
### Dostęp do danych konfiguracyjnych
|
||||
|
||||
```csharp
|
||||
// ZALECANE: ExecuteConfig z lambdą (zwraca wartość prostą)
|
||||
var opisDlaSzt = login.ExecuteConfig(configSession =>
|
||||
configSession.GetTowary().Jednostki.WgKodu["szt"]?.Opis);
|
||||
|
||||
// NIE ZALECANE: login.ConfigSession
|
||||
// var configSession = login.ConfigSession; // unikać!
|
||||
```
|
||||
|
||||
**WAŻNE:** Używając `ExecuteConfig()` nie można zwracać obiektów sesyjnych z sesji konfiguracyjnej, ponieważ może być używana w innym wątku do innych celów. Zwracaj tylko wartości proste lub kopie danych.
|
||||
|
||||
### Sygnatury CreateSession
|
||||
|
||||
```csharp
|
||||
public Session CreateSession(bool readOnly, bool config, string name) // rekomendowana
|
||||
public Session CreateSession(bool readOnly, bool config)
|
||||
public Session CreateSession() // readOnly=false, config=false
|
||||
```
|
||||
|
||||
## Klasa Session
|
||||
|
||||
Fundamentalna klasa do zarządzania danymi. **Każda operacja na danych wymaga sesji.** IDisposable.
|
||||
|
||||
### Tworzenie
|
||||
|
||||
```csharp
|
||||
// ZAWSZE używaj using lub wywołuj Dispose()
|
||||
using (var session = login.CreateSession(readOnly: false, config: false, name: "MojaSesja"))
|
||||
{
|
||||
// operacje na danych
|
||||
session.Save();
|
||||
}
|
||||
```
|
||||
|
||||
### Typy sesji - macierz
|
||||
|
||||
| ReadOnly | Config | Zastosowanie |
|
||||
|----------|--------|--------------|
|
||||
| `false` | `false` | **Edycja operacyjna** - dokumenty, kartoteki |
|
||||
| `true` | `false` | **Odczyt operacyjny** - raporty, wyświetlanie |
|
||||
| `false` | `true` | **Edycja konfiguracyjna** - ustawienia systemu |
|
||||
| `true` | `true` | **Odczyt konfiguracyjny** - odczyt słowników |
|
||||
|
||||
**WAŻNE:** W sesji operacyjnej (`config: false`) nie można modyfikować obiektów konfiguracyjnych. Do modyfikacji konfiguracji wymagana jest sesja konfiguracyjna (`config: true`).
|
||||
|
||||
### Właściwości
|
||||
|
||||
| Właściwość | Typ | Opis |
|
||||
|------------|-----|------|
|
||||
| `ReadOnly` | `bool` | Czy sesja tylko do odczytu |
|
||||
| `IsConfig` | `bool` | Czy sesja konfiguracyjna |
|
||||
| `Name` | `string` | Nazwa sesji |
|
||||
| `Login` | `Login` | Obiekt logowania |
|
||||
| `Database` | `Database` | Baza danych |
|
||||
|
||||
### Metody zarządzania danymi
|
||||
|
||||
```csharp
|
||||
// Zapisanie zmian do bazy
|
||||
session.Save();
|
||||
```
|
||||
|
||||
### Dane operacyjne vs konfiguracyjne
|
||||
|
||||
**Dane operacyjne (`config=false`):**
|
||||
- Dokumenty, kartoteki, transakcje
|
||||
- Zawsze odczytywane z bazy (aktualność)
|
||||
- Modyfikacje zapisywane natychmiast przy `Save()`
|
||||
|
||||
**Dane konfiguracyjne (`config=true`):**
|
||||
- Słowniki, definicje, ustawienia
|
||||
- Buforowane w cache (optymalizacja)
|
||||
- Modyfikowane rzadko, odczytywane często
|
||||
- Określane atrybutem `config="true"` w business.xml
|
||||
|
||||
### Wiele sesji jednocześnie
|
||||
|
||||
```csharp
|
||||
// Normalne zjawisko - wiele sesji może współistnieć
|
||||
using (var session1 = login.CreateSession(readOnly: true, config: false, name: "Lista1"))
|
||||
using (var session2 = login.CreateSession(readOnly: true, config: false, name: "Lista2"))
|
||||
{
|
||||
// Obie sesje mogą odczytywać te same dane
|
||||
var tm1 = session1.GetTowary();
|
||||
var tm2 = session2.GetTowary();
|
||||
}
|
||||
```
|
||||
|
||||
### Transakcje biznesowe - Session.Logout()
|
||||
|
||||
**WAŻNE:** Każda zmiana obiektu biznesowego MUSI być w transakcji!
|
||||
|
||||
```csharp
|
||||
using (var session = login.CreateSession(readOnly: false, config: false, name: "Edycja"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
var towar = tm.Towary.WgKodu["KOD001"];
|
||||
|
||||
// Logout(editMode: true) - transakcja EDYCYJNA (można modyfikować)
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
towar.Nazwa = "Nowa nazwa";
|
||||
var cena = towar.Ceny["Hurtowa"];
|
||||
cena.Netto = new DoubleCy(100.00m);
|
||||
transaction.Commit(); // Zatwierdza zmiany w sesji
|
||||
}
|
||||
// Brak Commit() = rollback (przywrócenie stanu sprzed Logout)
|
||||
|
||||
session.Save(); // Zapisuje do bazy danych
|
||||
}
|
||||
```
|
||||
|
||||
### Logout(editMode: false) - transakcja tylko do odczytu
|
||||
|
||||
```csharp
|
||||
using (var session = login.CreateSession(readOnly: false, config: false, name: "Przeglad"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
var towar = tm.Towary.WgKodu["KOD001"];
|
||||
|
||||
// Transakcja tylko do odczytu
|
||||
using (var readTrans = session.Logout(editMode: false))
|
||||
{
|
||||
// Tutaj NIE można modyfikować
|
||||
|
||||
// Ale można otworzyć zagnieżdżoną transakcję edycyjną
|
||||
using (var editTrans = session.Logout(editMode: true))
|
||||
{
|
||||
var cena = towar.Ceny["Hurtowa"];
|
||||
cena.Netto = new DoubleCy(200m);
|
||||
editTrans.Commit();
|
||||
}
|
||||
|
||||
// WAŻNE: Commit wymagany też dla transakcji readonly!
|
||||
// Inaczej zmiany z zagnieżdżonych transakcji edycyjnych przepadną
|
||||
readTrans.Commit();
|
||||
}
|
||||
session.Save();
|
||||
}
|
||||
```
|
||||
|
||||
| Metoda | Opis |
|
||||
|--------|------|
|
||||
| `session.Logout(editMode: true)` | Transakcja edycyjna - można modyfikować |
|
||||
| `session.Logout(editMode: false)` | Transakcja tylko do odczytu |
|
||||
| `transaction.Commit()` | Zatwierdza zmiany (wymagane dla obu typów!) |
|
||||
| `transaction.CommitUI()` | Zatwierdza + odświeża UI |
|
||||
| `transaction.Dispose()` | Bez Commit = rollback (także zagnieżdżonych zmian) |
|
||||
|
||||
## Kompletny przykład
|
||||
|
||||
```csharp
|
||||
// 1. Pobranie instancji aplikacji
|
||||
BusApplication app = BusApplication.Instance;
|
||||
|
||||
// 2. Pobranie bazy danych
|
||||
Database db = app["MojaBaza"];
|
||||
|
||||
// 3. Logowanie
|
||||
Login login = db.Login(new LoginParameters
|
||||
{
|
||||
UserName = "admin",
|
||||
UserPassword = "haslo"
|
||||
});
|
||||
|
||||
// 4. Utworzenie sesji (zawsze z nazwą!)
|
||||
using (var session = login.CreateSession(readOnly: false, config: false, name: "Import"))
|
||||
{
|
||||
// 5. Pobranie modułu (extension method)
|
||||
var tm = session.GetTowary();
|
||||
|
||||
// 6. Transakcja biznesowa - WYMAGANA dla zmian!
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
var nowyTowar = new Towar();
|
||||
tm.Towary.AddRow(nowyTowar);
|
||||
nowyTowar.Kod = "IMPORT001";
|
||||
nowyTowar.Nazwa = "Zaimportowany towar";
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
// 7. Zapis do bazy
|
||||
session.Save();
|
||||
}
|
||||
// 8. Session automatycznie Dispose() przez using
|
||||
```
|
||||
|
||||
## Thread-safety
|
||||
|
||||
### Obiekty multi-threaded (można współdzielić między wątkami)
|
||||
- `BusApplication`
|
||||
- `Database`
|
||||
- `Login`
|
||||
|
||||
### Obiekty single-threaded (NIE współdzielić)
|
||||
- `Session`, `Module`, `Table`, `Row`, `Context`
|
||||
|
||||
```csharp
|
||||
// DOBRZE - Login można współdzielić, każdy wątek tworzy własną sesję
|
||||
Login sharedLogin = db.Login(new LoginParameters { UserName = "admin", UserPassword = "haslo" });
|
||||
|
||||
Parallel.ForEach(items, item => {
|
||||
using (var session = sharedLogin.CreateSession(readOnly: false, config: false, name: "Watek"))
|
||||
{
|
||||
var tm = session.GetTowary();
|
||||
using (var transaction = session.Logout(editMode: true))
|
||||
{
|
||||
// operacje...
|
||||
transaction.Commit();
|
||||
}
|
||||
session.Save();
|
||||
}
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user