Zobrazují se příspěvky se štítkemFireDAC. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemFireDAC. Zobrazit všechny příspěvky

úterý 17. října 2017

Podnikové konektory společnosti CData

S aplikacemi, které se obejdou bez jakékoliv integrace, se již dnes často nesetkáme. Zajištění propojení s externími systémy či službami se tak stává pro vývojáře a programátory jednou z největších výzev. Znamená to opustit důvěrně známé vody vlastního kódu a spoléhat se na nabízená rozhraní a k nim dostupnou dokumentaci. Jedná se tak zpravidla o časově náročnou záležitost a čas jsou peníze.

To si uvědomila i společnost CData a přišla s řešením, které je jednoduché, a především rychle aplikovatelné. Myšlenku, na které je technologie konektorů založena, nejlépe charakterizuje slogan, který má společnost CData na svých webových stránkách: "Dívejte se na svět jako na databázi".
CData vytvořila mezivrstvu, která využívá nativní API integrovaných systémů, aby ji následně uživatelům zpřístupnila, jako by se jednalo o standardní relační databázi, se kterou lze komunikovat s využitím standardního jazyka SQL. Tím odpadá nutnost studovat nové protokoly či API, které se navíc neustále vyvíjí a mění. Speciálně pro vývojová prostředí Delphi a C++Builder pak vznikla (a je dále rozšiřována) sada ovladačů pro použití s populární technologií FireDAC.

RAD Studio a konektory CData

Použití v aplikaci

Instalace jednotlivých konektorů jsou dostupné přímo z prostředí Delphi, C++Builderu nebo RAD Studia. Stačí spustit "GetIt Package Manager", (Hlavní nabídka prostředí -> Tools -> GetIt) a pro rychlejší nalezení požadovaného ovladače použít filtrování (je zde přímo kategorie "Connectors") nebo vyhledávání. Následně již jen spustíte instalaci zvoleného ovladače kliknutím na tlačítko "INSTALL".
GetIt - Instalace zvoleného konektoru

Po dokončení instalace nalezneme vybrané konektory v paletě nástrojů, v sekci "FireDAC Links".

Zobrazení ovladačů v paletě komponent

Práci s konektory si můžeme ukázat na propojení s emailovou službou Seznam. Založíme novou aplikaci VCL a pro uživatelské rozhraní použijeme komponenty "FDConnecion", "FDTable", "DBMemo", "ComboBox", "FDGUIxWaitCursor", "DataSource" a "DBGrid".

Pro vlastní připojení k serverům společnosti Seznam použijeme komponentu TFDConnection, kterou si pojmenujeme například "EmailCnn":

procedure TwMain.btnConnectClick(Sender: TObject);
begin
  EmailCnn.Connected := False;
  EmailCnn.Params.Clear;
  EmailCnn.Params.Add('DriverID=CData.Email');
  EmailCnn.Params.Add('User=adresa@email.cz');
  EmailCnn.Params.Add('Password=heslo');
  EmailCnn.Params.Add('Server=imap.seznam.cz');
  EmailCnn.Params.Add('SMTPServer=77.75.76.48');
  EmailCnn.Params.Add('Port=993');
  EmailCnn.Params.Add('MaxItems=10');
  EmailCnn.Params.Add('SMTPPort=465');
  EmailCnn.Params.Add('IncludeMessage=True');
  EmailCnn.Params.Add('IsHTML=False');
  EmailCnn.Connected := True;
  FDTable.Connection := EmailCnn;
end;

Po úspěšném připojení je třeba zvolit, která data má aplikace zobrazovat. V praxi to znamená vybrat si z tabulek, které jsou pro daný systém (v našem případě email) definovány. Zde se buď musíme podívat do dokumentace, zvolit tabulku dle názvu v okně ObjectInspector (musí být vybrána komponenta "FDTable" a připojena k aktivní "FDConnection"), nebo můžeme využít vlastností technologie FireDAC, načíst dostupné tabulky z „metadat“ a nechat výběr na uživateli.

procedure TwMain.ComboBox1Click(Sender: TObject);
var
  tbName: String;
  i: Integer;
begin
  tbName := ComboBox1.Text;
  FDQuery1.Active := False;
  FDQuery1.SQL.Clear;
  if tbName.Length > 0 then
    FDQuery1.SQL.Add('select * from ' + tbName);
  FDQuery1.Open();
  DataSource1.DataSet := FDTable;
  DBGrid.DataSource := DataSource1;
  DBMemo1.DataSource := DataSource1;
  DBMemo1.DataField := 'MessageBody';
end;

Protože tabulky, se kterými pracujeme jsou pouze virtuální a neříkají nic o skutečné struktuře systému, není zpravidla možné jednoduše vložit data do některé z tabulek. Může zde být řada vazeb a integritních omezení.
Pokud se budeme držet příkladu s e-mailovým serverem, je celkem zřejmé, že vložením záznamu do tabulky odeslané pošty k odeslání zprávy nedojde. I toto však CData řeší databázovými prostředky. Podobně jako virtuální tabulky, vytváří ovladač také sadu uložených procedur. Do projektu tak stačí přidat komponentu "FDStoredProcedure" a napojit ji na dříve definovanou "FDConnection". Pro požadovanou operace pak již jen stačí zavolat příslušnou proceduru.

procedure TwMain.btnSendEmailClick(Sender: TObject);
var
  result: Integer;
begin
  FDStoredProc1.Close;
  FDStoredProc1.FetchOptions.Items := FDStoredProc1.FetchOptions.Items - [fiMeta];
  FDStoredProc1.StoredProcName := 'CData.SendMailMessage';
  FDStoredProc1.Prepare;
  FDStoredProc1.Params.ParamByName('To').AsString := 'adresa@embt.cz';
  FDStoredProc1.Params.ParamByName('Subject').AsString := 'Předmět zprávy';
  FDStoredProc1.Params.ParamByName('From').AsString := 'adresa@email.cz';
  FDStoredProc1.Params.ParamByName('MessageBody').AsString := 'Tělo zprávy';
  FDStoredProc1.Params.ParamByName('Attachment#').AsString := 'C:\TMP\Priloha.txt';
  FDStoredProc1.Params.ParamByName('IsHTML').AsString := 'True';
  FDStoredProc1.Open;
  result := FDStoredProc1.Params.ParamByName('MessageId').AsInteger;
  if result > 0 then
    ShowMessage('ID odeslané zprávy: ' + IntToStr(result));
end;

Aktuálně je k dispozici 83 ovladačů pro populární podnikové systémy z oblastí ekonomiky, účetnictví, řízení vztahů se zákazníky (CRM) nebo řízení podnikových zdrojů (ERP). Dále jsou zde obsaženy konektory pro cloudové služby, platební systémy, datové formáty či sociální sítě.
Konektory nejsou nabízeny jednotlivě, ale v sadách. Vybrat si můžete variantu Enterprise nebo více vybavenou Enterprise+. Sada Enterprise obsahuje:

Sada Enterprise

V sadě Enterprise+ naleznete navíc ještě tyto konektory:

Sada Enterprise Plus



čtvrtek 31. srpna 2017

Vývoj databázových aplikací VII

Metadata

Ne vždy je možné programovat komunikaci s databázovým strojem "natvrdo". Důvodem může být, že chceme navrhnout aplikaci tak, aby se byla schopna vypořádat s případnými změnami ve struktuře databáze, nebo chceme poskytnout uživateli možnost sestavovat některé dotazy takzvaně "Ad-Hoc" (tedy v daném okamžiku).
Aby bylo možné sestavit funkční SQL příkaz, musíme mít přístup k "metadatům", které popisují strukturu a vlastnosti jednotlivých objektů databáze. Tyto informace můžeme zjistit hned několika způsoby.

Systémové tabulky

Každý databázový stroj si udržuje informace o objektech, které spravuje. Tyto informace jsou dostupné v závislosti na typu databáze prostřednictvím systémových tabulek, pohledů a procedur. Například seznam existujících tabulek tak lze zjistit spuštěním standardního SQL dotazu:

Oracle:
SELECT owner, table_name FROM dba_tables;
MSSQL:
SELECT * FROM INFORMATION_SCHEMA.TABLES;
Sybase:
SELECT * FROM sys.objects WHERE type = 'U';
InterBase / Firebird:
select rdb$relation_name from rdb$relations
where rdb$view_blr is null and (rdb$system_flag is null or rdb$system_flag = 0);

Na první pohled je zřejmé, že tento přístup má dvě úskalí. Abychom mohli získat informace o existujících objektech (které neznáme), musíme znát strukturu systémových tabulek. A co je horší, řešení založené na systémových tabulkách je "svázáno" s konkrétním databázovým strojem. Z hlediska přenositelnosti tedy nic moc.

Komponenty FireDAC

FireDAC se snaží na rozdíl od systémových tabulek nabídnout obecný koncept, co nejvíce nezávislý na databázové platformě. Protože databázové stroje mohou mít různou architekturu a jednotná není ani terminologie dodavatelů, vychází FireDAC z obecného uspořádání:

Obecná struktura databázového serveru


Komponenty FireDAC nabízí hned několik způsobů, jak je možné
informace o struktuře databáze získat.

1) Na úrovni připojení

Pokud potřebujeme informace o aktuálním připojení (k jaké databázi je aplikace připojena, pod jakým účtem apod.), stačí zavolat funkci "GetInfoReport". Ta vrací seznam hodnot, kterou zapíše do určené proměnné typu "TStrings". Pokud tedy jako parametr této funkce použijeme odkaz na řádky komponenty "TMemo", dojde k zobrazení všech požadovaných informací.

Příklad 1: načtení informací z "GetInfoReport"

procedure TForm1.btnGetCnnInfoClick(Sender: TObject);
begin
  FDCnn.GetInfoReport(mCnnInfo.Lines);
end;

Výpis vlastností DB připojení

 Pro zjištění informací o jednotlivých objektech pak můžeme využít konkrétní funkce:

GetCatalogNames - pro přehled instancí, katalogů nebo databází, které server spravuje.
GetSchemaNames - pro seznam schemat, jmenných prostorů nebo vlastníků objektů ve vybraném katalogu.
GetTableNames - pro seznam tabulek ve vybraném katalogu a schematu.
GetFieldNames - pro seznam sloupců určené tabulky.
GetKeyFieldNames - pro seznam sloupců obsažených v primárním klíči.
GetIndexNames - pro seznam indexů určené tabulky.
GetPackageNames - pro seznam balíčků ve vybraném katalogu a schematu.
GetStoredProcNames - pro přehled uložených procedur obsažených ve zvoleném balíčku.
GetGeneratorNames - vrací přehled generátorů primárních klíčů.
GetLastAutoGenValue - vrací poslední hodnotu generátoru, sekvence nebo autoikrement určeného sloupce.

Vstupní a výstupní parametry:
Všechny uvedené funkce předávají seznam názvů objektů do určené proměnné typu "TStrings".

TFDPhysObjectScopes
osMy - zahrne do seznamu objekty přihlášeného uživatele
osSystem - zahrne do seznamu systémové objekty
osOther - zahrne do seznamu ostatní objekty

TFDPhysTableKinds
tkSynonym - zahrne do seznamu synonyma
tkTable - zahrne do seznamu tabulky
tkView - zahrne do seznamu tabulkové pohledy

Pattern
Umožňuje specifikovat název objektu za pomoci textového řetězce. Syntaxe je shodná se standardním SQL operátorem "Like", kde '%' nahrazuje libovolný počet znaků a '_' právě jeden libovolný znak.

Příklad 2: Získání přehledu všech tabulek v DB InteBase:

FDCnn.GetTableNames('', '', 'FI%', ListBox.Items, [osMy, osSystem], [tkSynonym, tkTable, tkView], True);

Pozn.: InteBase nevyužívá členění do Katalogů a Schemat. V takovém případě se jako parametr použije prázdný řetězec.

2) Komponenta "FDMetaInfoQuery"

Dalším způsobem, jak za běhu zjišťovat informace o databázových objektech je použití komponenty "FDMetaInfoQuery". Jedná se o standardní "DataSet", jehož obsah lze snadno konfigurovat za pomoci vstupních parametrů.
Typ objektu, jehož metadata potřebujeme získat, určuje parametr "MetaInfoKind". Jeho hodnota může být mkNone, mkCatalogs, mkSchemas, mkTables, mkTableFields, mkIndexes, mkIndexFields, mkPrimaryKey, mkPrimaryKeyFields, mkForeignKeys, mkForeignKeyFields, mkPackages, mkProcs, mkProcArgs, mkGenerators, mkResultSetFields nebo mkTableTypeFields.

Příklad 3: Zjištění informací o sloupcích tabulky FIRMA

procedure TForm1.btnFindClick(Sender: TObject);
begin
  FDMetaInfoQuery1.Active := False;
  FDMetaInfoQuery1.MetaInfoKind := mkTableFields;
  FDMetaInfoQuery1.ObjectName := 'FIRMA';
  FDMetaInfoQuery1.Wildcard := '';
  FDMetaInfoQuery1.Active := True;
end;

Zobrazená datová sada

3) Objekty "FDTable", "FDQuery" nebo "FDStoredProc"

Pokud známe jméno tabulky nebo uložené procedury, můžeme získat podrobné informace přímo ze získané datové sady.

Příklad 4: Zjištění názvů sloupců z komponenty "FDQuery"

procedure TfrmMain.ButtonGetFields(Sender: TObject);
var
  AList: TStringList;
  i: Integer;
begin
  AList := TStringList.Create;
  FDQuery1.GetFieldNames(AList);
end;

Příklad 5: Zjištění vstupních parametrů uložené procedury:

procedure TfrmMain.ButtonGetSPParams(Sender: TObject);
var
  i, n: Integer;
  pname: String;
  pType: TParamType;
begin
  n := FDStoredProc1.ParamCount;
  for i := 0 to n - 1 do
    begin
      pname := FDStoredProc1.Params[i].Name;
      pType := FDStoredProc1.Params[i].ParamType;
      if pType = TParamType.ptInput then
         Memo1.Lines.Add(pname);
    end;
end;

Pokud potřebujete navrhnout aplikaci tak, aby se dokázala pružně přizpůsobit případným změnám ve struktuře databáze, nabízí Vám RAD Studio a FireDAC víc než dostatečné možnosti, jak toho dosáhnout.

úterý 30. září 2014

Optimalizace komunikace s DB - Kurzory

Komunikaci mezi aplikací a databázovým serverem je možné do značné míry optimalizovat. Technologie FireDAC zpřístupňuje množství parametrů, které upravují způsob, jakým se požadavek zaslaný databázovému serveru bude zpracovávat.
Z hlediska odezvy jsou velmi důležitá nastavení týkající se vytvoření datové sady na serveru a její následný přenos na klientskou stanici.

Volba typu DB kurzoru

FireDAC umožňuje volbu typu kurzoru prostřednictvím parametru "CursorKind".
Parametr "CursorKind" má dopad na:
Čas potřebný pro načtení prvního záznamu výsledkové sady
Čas potřebný pro načtení kompletní výsledkové sady
Možnost současného otevření více kurzorů
Stabilitu (neměnnost) kurzoru
Nároky na systémové prostředky databázového stroje

FireDAC FetchOptions

Dostupné možnosti nastavení jsou:

  • ckAutomatic - Typ kurzoru je vybrán automaticky na základě nastavení ostatních parametrů. 
  • ckDefault - Obsahuje záznamy, které odpovídaly dotazu v okamžiku jeho spuštění. Načtení prvního záznamu může být pomalejší, protože se na klienta přesouvá celá výsledková sada. Celkově je ale rychlejší. 
  • ckDynamic - Dynamický serverový kursor reflektuje změny způsobené aktualizacemi, které proběhly po dobu kdy je kurzor aktivní. Načtení prvního záznamu je rychlé, získání celé výsledkové sady může být pomalejší. 
  • ckStatic - Statický serverový kurzor obsahuje záznamy, které odpovídaly dotazu v okamžiku jeho spuštění.
  • ckForwardOnly - Jednosměrný serverový kurzor. Uvolňuje již použité záznamy, takže šetří paměť. Parametr "Unidirectional" musí být "True".



Příklad Delphi

procedure TForm1.Button1Click(Sender: TObject);
begin
  // Nastavení typu kurzoru na úrovni připojení
  FDConnection.Connected := False;
  FDConnection.FetchOptions.CursorKind := ckStatic;
  FDConnection.Connected := True;
end;

Příklad C++ Builder

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  // Nastavení typu kurzoru na úrovni komponenty FDQuery
  FDQuery->Active = False;
  FDQuery->FetchOptions->CursorKind = ckAutomatic;
  FDQuery->Active = True;
}

Způsob práce s kurzorem

Parametr "Unidirectional" určuje, zda se lze v kurzoru pohybovat oběma směry, tedy dopředu i zpět. Standardně je má parametr nastavenu hodnotu "False", kdy je povolen i zpětný pohyb. Pokud je nastaven na "True", šetří se systémové zdroje (již zpracované záznamy jsou uvolněny z paměti), ale například při použití komponenty "DBGrid" vyvolá přesun na předchozí záznam chybové hlášení.

Chyba kurzoru

Uvolnění kurzoru

Databázový stroj udržuje kurzor dokud není datová sada uzavřena, nebo není uvolněn objekt, který je jejím správcem. Parametr "AutoClose", pokud je nastaven na "True", uvolní kurzor ihned po načtení posledního záznamu.

Pozor! Jestliže příkaz vrací více datových sad, musí být tento parametr nastaven na "False", jinak dojde k uzavření kurzoru po načtení všech záznamů první datové sady a další sady již načteny nebudou!

čtvrtek 8. května 2014

Vývoj databázových aplikací VI

Práce s DataSety na straně klienta

Výsledková sada přenesená na klienta může být v řadě případů příliš rozsáhlá, aby se v ní uživatel jednoduše orientoval. FireDAC podobně jako DB Express nebo jeho předchůdce BDE nabízí funkce, které umožňují data třídit, filtrovat je dle určených kritérií nebo v nich vyhledávat.

FireDAC nabízí dvě varianty, jak výše uvedené funkce implementovat. Programátor může využít přímo metody třídy DataSet (či jejích potomků), nebo se i na klientské straně spolehnout na jazyk SQL.

DataSet - Třídění záznamů

Záznamy lze třídit podle jednoho nebo více sloupců, které byly přidány do pojmenovaného "Indexu". Indexů může být definováno více a následně mohou být aktivovány dle potřeby. Záznamy jsou setříděny podle určených sloupců. Pokud není uvedeno jinak, jsou záznamy třízeny vzestupně s ohledem na malá a velká písmena.


Příklad Delphi - Třídění dle indexu

procedure TForm1.SortClick(Sender: TObject);
begin
  // Definování indexu
  FDTable1.Indexes.Clear;
  FDTable1.Indexes.Add();
  FDTable1.Indexes.Items[0].Name := 'idxPrijmeni';
  // Záznamy budou setříděny nejprve podle příjmení a
  // potom podle jména
  FDTable1.Indexes.Items[0].Fields := 'PRIJMENI; JMENO';
  // Sloupec jména bude se bude třídit sestupně
  FDTable1.Indexes.Items[0].DescFields := 'JMENO';
  // Sloupec příjmení se bude třídit bez ohledu na velká
  // a malá písmena
  FDTable1.Indexes.Items[0].CaseInsFields := 'PRIJMENI';
  FDTable1.Indexes.Items[0].Active := True;
  FDTable1.Indexes.Items[0].Selected := True;
end;


Příklad C++ Builder

void __fastcall TForm1::SortClick(TObject *Sender)
{
  FDQuery1->Indexes->Clear();
  FDQuery1->Indexes->Add();
  FDQuery1->Indexes->Items[0]->Name = "idxPrijmeni";
  FDQuery1->Indexes->Items[0]->Fields = "PRIJMENI; JMENO";
  FDQuery1->Indexes->Items[0]->DescFields = "JMENO";
  FDQuery1->Indexes->Items[0]->CaseInsFields = "PRIJMENI";
  FDQuery1->Indexes->Items[0]->Active = True;
  FDQuery1->Indexes->Items[0]->Selected = True;
}

DataSet - Filtrování záznamů

Množinu zobrazovaných záznamů lze omezit použitím vhodného filtru. Filtr může obsahovat logické operátory, operátory LIKE, IN nebo zástupné znaky.


Příklady Delphi

// Osoby s příjmením začínajícím na 'Fa'
procedure TForm1.Filter1Click(Sender: TObject);
begin
  FDTable1.Filtered := False;
  FDTable1.Filter := 'PRIJMENI LIKE ' + QuotedStr('Fa%');
  FDTable1.Filtered := True;
end;

// Výběr výčtem
procedure TForm1.Filter2Click(Sender: TObject);
begin
  FDTable1.Filtered := False;
  FDTable1.Filter := 'OSOBA_ID < 10 AND OSOBA_ID NOT IN (5, 6, 8)';
  FDTable1.Filtered := True;
end;


Příklad C++ Builder

// Filtrování podle rozpětí (osoba_id od 5 do 12)
void __fastcall TForm1::Filter1Click(TObject *Sender)
{
  FDQuery1->IndexFieldNames = "osoba_id";
  FDQuery1->SetRangeStart();
  FDQuery1->KeyExclusive = False;
  FDQuery1->KeyFieldCount = 1;
  FDQuery1->FieldByName("osoba_id")->AsInteger = 5;
  FDQuery1->SetRangeEnd();
  FDQuery1->KeyExclusive = False;
  FDQuery1->KeyFieldCount = 1;
  FDQuery1->FieldByName("osoba_id")->AsInteger = 12;
  FDQuery1->ApplyRange();
}

DataSet -Vyhledání záznamu

Pro vyhledávání v datové sadě je možné použít více přístupů. Nejvíce možností však nabízí funkce "LocateEx". Pokud jsou pro datovou sadu definovány indexy, budou pro vyhledávání použity.


Příklad Delphi

// Vyhledání záznamu pomocí LocateEx
procedure TForm1.FindClick(Sender: TObject);
var
  lxo: TFDDataSetLocateOptions;
begin
  lxo := [lxoPartialKey,lxoCaseInsensitive, lxoFromCurrent];
  //Nalezne postupně první a následující výskyty
  if not FDTable1.LocateEx('PRIJMENI', 'mal', lxo) then
    ShowMessage('Hledaný záznam nebyl nalezen.');
end;



Příklad C++ Builder

void __fastcall TForm1::FindClick(TObject *Sender)
{
  TFDDataSetLocateOptions lxo;
  lxo << lxoPartialKey;
  lxo << lxoCaseInsensitive;
  lxo << lxoFromCurrent;
  if (FDQuery1->LocateEx("Prijmeni", Edit1->Text, lxo) == False)
 ShowMessage("Záznam nenalezen.");
}

Pro Search Options lze použít přepínače:
lxoCaseInsensitive - Hodnoty jsou vyhledány bez ohledu na použití malých nebo velkých písmen
lxoPartialKey - Vyhledávací podmínce budou odpovídat záznamy obsahující hledaný klíč
lxoFromCurrent - Další vyhledávání bude pokračovat od aktuálního záznamu
lxoCheckOnly - Je pouze ověřena existence záznamu splňujícího vyhledávací podmínky. Nedojde ke změně aktuálního záznamu ani spuštění s ní spojených událostí. DataSet zůstává v edit modu
lxoBackward - Vyhledávání bude probíhat od posledního záznamu
lxoNoFetchAll - Pokud je načtena pouze část záznamů, proběhne vyhledání pouze v nich. Nedojde k vynucenému načtení všech záznamů.

Lokální SQL

FireDAC podporuje používání jazyka SQL při práci s datovými sadami na klientské straně. Tuto vlastnost zajišťuje komponenta "FDLocalSQL", která jako databázový engine využívá SQLite. Kromě třídění, filtrování či vyhledávání dat lze za pomoci lokálních SQL dotazů řešit například také požadavky na:  

Heterogenní dotazy - Dotazy, které získávají data z různých databází
In-memory zpracování - Jako datové sady lze použít "TFDMemTable"
Offline zpracování dat - Aplikace pracuje s lokální kopií dat
Migrace dat - Přenesení dat mezi různými databázovými stroji


Příklad Delphi - Filtrování a setřídění záznamů


Vytvoříme aplikaci s komponentami "FDConnection1" připojenou na zvolenou DB a "FDTable1", která získá ze serveru požadovaná data. Do projektu přidáme komponenty "FDConnection2", "FDQuery1, "FDLocalSQL1", "DBGrid1" a "DataSource1".
procedure TForm1.Button1Click(Sender: TObject);
begin
  // Získání DataSetu z DB stroje
  FDConnection1.ConnectionDefName := 'FBDEMODB';
  FDTable1.Connection := FDConnection1;
  FDTable1.TableName := 'OSOBA';
  // Nastavení LocalSQL
  FDConnection2.DriverName := 'SQLite';
  FDLocalSQL1.Connection := FDConnection2;
  FDLocalSQL1.SchemaName := 'Local';
  // DataSety lze přidávat dle potřeby, přičemž mohou být
  // získány z různých DB strojů
  FDLocalSQL1.DataSets.Add(FDTable1, 'Local', 'Osoba');
  FDLocalSQL1.Active := True;
  // Realizace dotazu nad lokálním DataSetem
  FDQuery1.Connection := FDConnection2;
  FDQuery1.Open('select prijmeni, jmeno from osoba where jmeno like' +
  QuotedStr('Fra%') + 'order by prijmeni');
  // Zobrazení dat
  DataSource1.DataSet := FDQuery1;
  DBGrid1.DataSource := DataSource1;
end;



Příklad C++ Builder - heterogenní dotaz



Nastavení komponenty FDLocalSQL

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  // DataSet MS SQL Server
  FDConnection1->ConnectionDefName = "MSDEMODB";
  FDTable1->Connection = FDConnection1;
  FDTable1->TableName = "OSOBA";
  // DataSet Firebird
  FDConnection2->ConnectionDefName = "FBDEMODB";
  FDTable2->Connection = FDConnection2;
  FDTable2->TableName = "FIRMA";
  // Nastavení FDLocalSQL
  FDConnection3->DriverName = "SQLite";
  FDLocalSQL1->Connection = FDConnection3;
  FDLocalSQL1->SchemaName = "Local";
  FDLocalSQL1->DataSets->Add(FDTable1, "Local", "Osoba");
  FDLocalSQL1->DataSets->Add(FDTable2, "Local", "Firma");
  // Realizace dotazu nad heterogenními daty
  FDLocalSQL1->Active = True;
  FDQuery1->Connection = FDConnection3;
  FDQuery1->Open("select f.nazev, o.jmeno, o.prijmeni from Osoba o, Firma f where o.firma_id = f.firma_id");
  DataSource1->DataSet = FDQuery1;
  DBGrid1->DataSource = DataSource1;
}

Pro každý databázový stroj včetně SQLite musí aplikace obsahovat příslušnou knihovnu ovladače. V našem případě tedy třídy TFDPhysMSSQLDriverLink, TFDPhysFBDriverLink a TFDPhysSQLiteDriverLink.

středa 23. dubna 2014

Vývoj databázových aplikací V

Uložené procedury

Uložená procedura (Stored Procedure) je podobně jako tabulka nebo pohled databázový objekt, definovaný uvnitř databáze. Místo vlastních dat však obsahují kód (aplikační logiku). Uložené procedury mohou používat téměř libovolné dotazy (DQL) nebo příkazy pro manipulaci s daty (DML). Jejich použití je vhodné zvláště tam, kde by realizace úlohy na klientské straně znamenala zbytečný přenos většího objemu dat ze serveru a následně zpět na server.

Na rozdíl od příkazů DDL pro návrh struktury dat, jejichž převod lze snadno provést pomocí CASE nástrojů, jsou uložené procedury podobně jako spouště (triggery), funkce nebo balíčky obtížněji přenositelné. Dodavatelé aplikací, kteří musí podporovat databázové servery různých dodavatelů, se tak použití uložených procedur často vyhýbají. Bohužel se tak zároveň vzdávají řady výhod, které použití uložených procedur přináší:

Zpřehlednění systému

Uložené procedury zjednodušují a zpřehledňují architekturu systému.
- Změny lze provádět na serveru, aniž by tím byla dotčena aplikace
- Aplikační logiku lze konzistentně sdílet více moduly nebo aplikacemi
- Uložené procedury jsou validovány serverem a mají transparentní vazby
- Uložené procedury se snadno dokumentují
- Návrh uložených procedur lze do velké míry automatizovat  

Sdílení aplikační logiky

Zlepšení odezvy aplikace

Uložené procedury jsou předkompilované a typicky načtené v cache paměti DB serveru. V porovnání se standardním příkazem tak již není nutné ověřovat jejich syntaktickou správnost, provádět kontrolu existence dotčených databázových objektů nebo validovat správnost vazeb. Rovněž odpadá proces optimalizace, kdy se databázový server na základě statistických informací o jednotlivých objektech snaží navrhnout ideální exekuční plán a samozřejmě také vlastní kompilace. Provedení uložené procedury je tak zpravidla výrazně rychlejší než přímé spuštění identické sady příkazů.

Zpracování uložené procedury


Při větších změnách v DB, které by mohli mít dopad na stanovení optimálního exekučního plánu je doporučeno aktualizovat statistiky a provést rekompilaci všech uložených procedur.


Bezpečnost

Použití uložených procedur je mnohem bezpečnější než přímý přístup k datům. Pro každou proceduru je možné přesně stanovit, který uživatel nebo která role má právo ji spustit. Uložená procedura chrání systém před útoky typu SQL injection.

Řízení oprávnění


Uložené procedury je také možné využít ke skrytí aplikační logiky. Po zkompilování uložené procedury lze ze serveru odstranit zdrojový kód (typicky uložený v systémových tabulkách v textové podobě).

Produktivita

V současné době je pro většinu databázových serverů k dispozici široká nabídka nástrojů a utilit, které návrh uložených procedur zjednodušují a automatizují.

Generování uložených procedur

Pro volání uložených procedur databázových strojů Interbase a Firebird, které vracející datovou sadu nelze použít příkaz ExecProc. Místo toho stačí uloženou proceduru nastavit na "Active". Alternativně lze získat data z procedury s využitím FDQuery. Ve vlastnosti "SQL.text" pak provedeme standardní "select", například 'select * from SEL_TEST'.


Příklady Delphi (InterBase/Firebird)

// Načtení záznamů
procedure TForm1.selClick(Sender: TObject);
begin
  FDStoredProc1.Active := False;
  FDStoredProc1.StoredProcName := 'SEL_TEST';
  FDStoredProc1.Prepare();
  FDStoredProc1.Active := True;
end;

// Vložení nového záznamu
procedure TForm1.insClick(Sender: TObject);
begin
  FDStoredProc1.Active := False;
  FDStoredProc1.StoredProcName := 'INS_TEST';
  FDStoredProc1.Prepare();
  FDStoredProc1.ParamByName('IN_TXT').AsString := Edit1.Text;
  // Zobrazení výstupního parametru
  // v tomto případě ID vloženého záznamu
  ShowMessage(FDStoredProc1.ParamByName('RET').AsString);
  FDStoredProc1.ExecProc();
end;

// Aktualizace záznamu
procedure TForm1.updClick(Sender: TObject);
begin
  FDStoredProc1.Active := False;
  FDStoredProc1.StoredProcName := 'UPD_TEST';
  FDStoredProc1.Prepare();
  FDStoredProc1.ParamByName('IN_TXT').AsString := Edit1.Text;
  FDStoredProc1.ParamByName('IN_ID').AsInteger := Edit2.Text;
  FDStoredProc1.ExecProc();
end;

// Odebrání záznamu
procedure TForm1.delClick(Sender: TObject);
begin
  FDStoredProc1.Active := False;
  FDStoredProc1.StoredProcName := 'DEL_TEST';
  FDStoredProc1.Prepare();
  FDStoredProc1.Params.ParamByPosition(1).AsInteger := StrToInt(Edit2.Text);
  FDStoredProc1.ExecProc();
end;


Příklady C++ Builder (Microsoft SQL Server)

// Načtení záznamů
void __fastcall TForm1::selClick(TObject *Sender)
{
  FDStoredProc1->Active = False;
  FDStoredProc1->StoredProcName = "dbo.up_sel_edice";
  FDStoredProc1->Prepare();
  FDStoredProc1->ParamByName("@eid")->AsInteger = StrToInt(Edit1->Text);
  FDStoredProc1->Active = True;
}

// Vložení nového záznamu
void __fastcall TForm1::insClick(TObject *Sender)
{
  FDStoredProc1->Active = False;
  FDStoredProc1->StoredProcName = "dbo.up_ins_edice";
  FDStoredProc1->Prepare();
  FDStoredProc1->ParamByName("@edice")->AsString = Edit2->Text;
  FDStoredProc1->ExecProc();
}

// Aktualizace záznamu
void __fastcall TForm1::updClick(TObject *Sender)
{
  FDStoredProc1->Active = False;
  FDStoredProc1->StoredProcName = "dbo.up_upd_edice";
  FDStoredProc1->Prepare();
  FDStoredProc1->ParamByName("@eid")->AsInteger = StrToInt(Edit1->Text);
  FDStoredProc1->ParamByName("@edice")->AsString = Edit2->Text;
  FDStoredProc1->ExecProc();
}

// Odebrání záznamu
void __fastcall TForm1::delClick(TObject *Sender)
{
  FDStoredProc1->Active = False;
  FDStoredProc1->StoredProcName = "dbo.up_del_edice";
  FDStoredProc1->Prepare();
  FDStoredProc1->Params->ParamByPosition(2)->AsInteger = StrToInt(Edit1->Text);
  FDStoredProc1->ExecProc();
}

Uložené procedury (a další SQL příkazy) lze spouštět na základě událostí na straně serveru, nebo v požadovaných časech. Vhodné např. pro reporting nebo validaci a čištění dat.

sobota 29. března 2014

Vývoj databázových aplikací IV

Práce s datovými sadami na straně klienta

Dosud jsme se zabývali především příkazy, které aplikaci nevracely žádná nebo jen agregovaná data. Častější však bývá situace, kdy je na základě dotazu na klienta přenesena požadovaná množina dat, a uživatelé s nimi provádí další operace. Pro definování datových sad je k dispozici trojice komponent "FDTable", "FDQuery" a "FDStoredProc".

  • FDTable - Vytváří datovou sadu naplněnou záznamy z určené tabulky nebo pohledu
  • FDQuery - Datová sada je definována na základě SQL dotazu
  • FDStoredProc - Datová sada je získána spuštěním uložené procedury

Použití komponenty "FDTable"

Jak již bylo řečeno, datová sada je tvořena množinou záznamů získaných z databáze. Pokud potřebujeme pracovat výhradně se záznamy konkrétní databázové tabulky, je nejsnazší cestou použití komponenty "FDTable". Minimální nastavení vyžaduje specifikovat v inspektorovi objektů nebo v kódu vlastnosti:

  • Connection - určuje DB připojení, které má být použito pro komunikaci s databázovým strojem
  • TableName - Jméno tabulky, která bude zdrojem dat pro naplnění datové sady

V závislosti na typu DB stroje může být nutné zvolit před volbou tabulky odpovídající "Catalog" nebo "Schema", v kterém se požadovaná tabulka nachází.

Nastavení vlastností komponenty FDTable

Parametr názvu připojení pro "FDTable" je doplněn automaticky prostředím. Na to je třeba dát pozor, pokud je na formuláři umístěna více než jedna komponenta "FDConnection".

Použití komponenty "FDQuery"

Pro definování složitějších datových sad slouží komponenta "ADQuery".
Pro pohodlnější zadávání příkazů SQL, případně jejich ověření, nabízí FireDAC jednoduchý QueryEditor. Okno Query Editoru otevřeme tak, že na formulář umístíme komponentu "FDQuery" a klikneme na ni pravým tlačítkem myši. V kontextovém menu pak vybereme volbu "Query Editor...".

Vyvolání Query Editoru

Příkazy se vkládají do dialogu v záložce "SQL Command". Zadat lze i více příkazů současně, je však třeba použít v závislosti na databázovém stroji správný oddělovač příkazů. Jedná-li se o příkazy, které nevracejí jako výsledek sadu záznamů, stačí následně kliknout na tlačítko "Execute" a příkazy budou spuštěny jako dávka. Pokud je mezi zadanými příkazy více než jeden příkaz vracející výsledkovou sadu, zobrazí se pouze první sada záznamů. Pro zobrazení dalších je třeba použít tlačítko "Next RecordSet".

Query Editor

Je-li příkazem vrácena sada záznamů, lze si v záložce "Structure" ve spodní polovině obrazovky zobrazit její vlastnosti (informace o datových typech a atributech jednotlivých sloupců). V záložce "Messages" pak můžeme najít chybová nebo informační hlášení databáze.

Zobrazení informací o datové sadě

Parametrické dotazy
Kromě dávek podporuje FireDAC Query Editor také parametrické dotazy. Do dialogu "SQL Command" vložíme příkaz s požadovanými parametry, například:

select * from osoba where osoba_id = :oid;

Parametrický dotaz

Následně přejdeme do záložky "Parameters", kam Query Editor automaticky doplní jméno použitých parametrů. V dialogu můžeme specifikovat typ parametru, jeho datový typ a případně defaultní hodnotu. Datovým typem může být i pole hodnot. Po zadání hodnoty (nebo indexu pole hodnot) a kliknutí na tlačítko "Execute" se zobrazí výsledek dotazu.

Specifikace a otestování parametrického dotazu

Parametr můžeme následně používat v kódu aplikace, typicky pro zobrazení dat z podřízené tabulky v pohledech "Master/Detail".


Delphi

procedure TForm1.Button1Click(Sender: TObject);
begin
  FDQuery1.Active := False;
  FDQuery1.ParamByName('oid').AsInteger := StrToInt(edit2.Text);
  FDQuery1.Active := True;
end;


C++ Builder

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  FDQuery1->Active = False;
  FDQuery1->ParamByName("oid")->AsInteger = StrToInt(edit2->Text);
  FDQuery1->Active = True;
}

Použití Maker
Query Editor kromě spouštění parametrických dotazů dovoluje testovat i použití maker, nebo makra přímo používat pro modifikaci příkazů. Pokud například chceme spustit příkaz "select" proti několika různým tabulkám, můžeme využít možnost substituce názvů objektů. Do Query Editoru zadáme příkaz v následujícím tvaru:

SELECT &column FROM &table;

V záložce "Macros" jsou automaticky vytvořeny použité proměnné (v tomto případě "column" a "table") a lze jim přiřazovat požadované hodnoty. Po zadání hodnot a kliknutí na tlačítko "Execute" jsou proměnné nahrazeny zadanými hodnotami, příkaz je spuštěn a zobrazena výsledková sada.

Nastavení a otestování makra


Delphi


procedure TForm1.MakroClick(Sender: TObject);
begin
  FDQuery1.Active := false;
  FDQuery1.MacroByName('TABLE').AsRaw := edit2.Text;
  FDQuery1.Active := true;
end;


C++ Builder

void __fastcall TForm1::MakroClick(TObject *Sender)
{
  FDQuery1->Active = False;
  FDQuery1->MacroByName("TABLE").AsRaw = edit2->Text;
  FDQuery1->Active = True;
}

Makra nám mohou pomoci například při vývoji v multiplatformním prostředí, kde se vyskytují databázové stroje různých dodavatelů. Makra obsahují aparát pro podmíněné volání příkazů. FireDAC disponuje vestavěným textovým preprocesorem, který umožňuje používat při sestavování SQL příkazu makra a překlenout tak rozdíly v syntaxi používané různými dodavateli. Příkladem může být rozšíření příkazu "select" pro omezení počtu vrácených záznamů.

Zatímco pro databázový stroj Microsoft SQL je syntaxe následující:

SELECT TOP 5 * FROM firma;

pro dosažení stejného výsledku musíme v DB Interbase použít:

SELECT FIRST 5 * FROM firma;

S použitím FireDAC maker lze tento příkaz zapsat:

SELECT {IF MSSQL} TOP {fi}{IF INTRBASE} FIRST {fi} 5 * FROM firma;

Podpora parametrů a maker musí být aktivní, to znamená příslušné parametry "Command Text Processing" musí být nastaveny na "True".

Nastavení Command Text Processing Options