Bezpośrednia komunikacja z bazą danych w metodach logiki biznesowej¶
Szybie uruchomienie kodu SQL¶
Do tego celu służy RunSQL. Sam możesz złożyć wyrażenie, które zostanie wykonane. Aby je zbudować należy użyć metod przekształcających, których nazwy zaczynają się od SQL. Na przykład:
string sql = "update TABELA set POLETEXT = " + this.POLETEXT.SQLString() + ", POLENUM = " + this.POLENUM.SQLNumber();
sql += " where REF = " + this.REF.SQLInteger();
CORE.RunSQL(sql);
Metody te są na tyle inteligentne, że np. SQLString, SQLDate, SQLTimestamp, SQLTime sama doklejają apostrofy ' ', a pierwsza z wymienionych poprawnie eskejpuje apostrofy dublując je w treści. Powyższe funkcje zamieniają puste wartości na wartością domyślną, czyli pustym string lub zero. Ale posiadają parametr pozwalający zastąpić je ciągiem znaków "NULL".
W ten sposób możesz zlecić jakąś operację BD (najczęściej będą to szybkie, bezpośrednie update, ale może to być też na przykład "execute procedure X()", albo "drop table"), ale nie otrzymasz żadnego wyniku zwrotnego.
Wywołanie procedur SQL, zwracających co najwyżej pojedyncze parametry wyjściowe¶
Są to takie procedury w których nie jest konieczne używanie słowa kluczowego suspend. Aby móc wykonywać procedury składowane i przekazywać/pobierać z nich parametry użyj metody RunProcedure w połączeniu z obiektem Contexts. Poniżej podstawowy przykład wykonania.
{
Contexts c = new Contexts();
c.Add("NAZWA","Nowy klient");
c.Add("NIP","1234567890");
CORE.RunProcedure("DODAJKLIENTA",c);
}
Uwaga!
Kolejność podawanych parametrów ma znaczenie, deklaruj je w kolejności takiej samej jak w definicji procedury[1].
Wynika to z faktu, że podajesz tylko nazwę procedury, a sam firebird nie posiada wsparcia do kojarzenia parametrów po nazwie. Jeżeli chcesz uniezależnić się od kolejności parametrów musisz używać pełnego wywołania zgodnie z pełną składnią:
{
Contexts c = new Contexts();
c.Add("NAZWA","Nowy klient");
c.Add("NIP","1234567890");//odwrócone
CORE.RunProcedure("execute procedure DODAJKLIENTA(@NIP,@NAZWA) ",c);
}
Nie używaj metod SQL tylko ToSQL¶
Jeżeli już potrzebujesz dokonać konwersji typów w kontekście przed wywołaniem procedury składowanej, nie używaj do tego dla pól modelu danych metod SQL tylko ToSQL, ponieważ za przekazywanie parametrów do procedury odpowiada driver firebirda i rozpoznaje on typ podawanych wartości. Będzie to powodować problemy.
this.POLE = "napis";//pole obiektu biznesowego
Contexts c = new Contexts();
c.Add("PARAM", POLE.SQLString());//przekazujemy wartość SQLString, czyli napis 'napis' typu string
RunProcedure("PROC",c); //driver rozpozna że c["PARAM"] jest stringiem apostrof-n-a-p-i-s-apostrof, więc przekaże ''napis''
//jeżeli użyjemy
c.Add("PARAM", POLE.ToSQLString());//przekazujemy wartość SQLString, czyli napis n-a-p-i-s typu string
Zamiana na stringa powoduje też problemy przy konwersji liczb. "5,5" może zostać przez firebirda zinterpretowane jako 55. Zależy to od ustawień regionalnych serwera, klienta i samej bazy danych.
Pobieranie wartości parametrów wyjściowych¶
Jeżeli potrzebujesz pobrać wartości parametrów po wywołaniu procedury, zadeklaruj je jako wyjściowe
{
Contexts c = new Contexts();
c.Add("NAZWA","Nowy klient");
c.Add("NIP",NIP.ToSQLString());
c.Add("WAGA",WAGA.ToSQLNumber());
c.Add("OUTREF","",NValueRole.OutputValue);
CORE.RunProcedure("","DODAJKLIENTA",c);
int ref = c["OUTREF"].AsInteger;
}
Przekazywanie do bazy NULLi¶
Do tej pory jedyną możliwością przekazania nulli do bazy było ich jawne podanie w następujący sposób:
c.Add("PROJECT",
String.IsNullOrEmpty(_myproject.ToString()) ? null : _myproject.SQLInteger(),NValueRole.InputValue);
Dało się to ograniczyć do
c.Add("PROJECT",
_myproject.Empty ? null : _myproject.SQLInteger());
Dalej jednak nie jest wygodne. Dodatkowo ponieważ wszystkie wartości pól modelu danych w Neosie są wewnętrznie przekazywane jako stringi, trzeba się decydować czy pusty string idzie jako pusty string, czy jako NULL, nie da się obsługiwać tych opcji jednocześnie.
Pobieranie z bazy NULLi¶
Jeżeli pole zwróci NULL a użyjemy metody AsInteger otrzymamy 0. Jeżeli AsString dostaniemy pustego stringa. Rozpoznać że zwrócony został NULL można w następujący sposób:
bool isnull = c["OUTPARAM"].Value == DBNull.Value;
Nowości¶
Interfejs ten nie był zbyt wygodny, dlatego zostały wprowadzone nowe możliwości.
Od wersji 1.2.RC1¶
Nie musimy już na polach modelu danych wywoływać przy ustawianiu kontekstu żadnych dodatkowych metod, Neos automatycznie przeprowadzi konwersję do takiego typu jaki zadeklarowaliśmy w Neosie przy tworzeniu pola, bądź też jakiego typu jest podawana wartość.
{
Contexts c = new Contexts();
c.Add("NAZWA","Nowy klient");
c.Add("NIP",NIP);//pole VARCHAR(20), albo domena STRING100 -> neos sam zrobi ToSQLString()
c.Add("WAGA",WAGA.ToSQLNumber());//pole DOUBLE PRECISION albo domena -> ToSQLNumber()
c.Add("NOWY",1);//1 jest typu int -> ToSQLInteger()
double xfactor = 1.93;
c.Add("XFACTOR",xfactor); //typu double więc ->ToSQLNumber()
c.Add("OUTREF","",NValueRole.OutputValue);
if(RunProcedure("","DODAJKLIENTA",c)) {
int ref = c["OUTREF"].AsInteger;
} else {
API.ShowError("Błąd","","");
}
}
Note
Jeżeli mamy pole modelu danych lub parametr o typie innym niż łańcuch (czyli int,date,time itp) a jego wartość jest pusta, to do bazy danych zostanie przesłany NULL.
Wywołania metod Add można łączyć stosując tak zwany chaining.
{
Contexts c = new Contexts()
.Add("NAZWA","Nowy klient")
.Add("NIP",NIP)
.Add("WAGA",WAGA.ToSQLNumber())
.Add("OUTREF","",NValueRole.OutputValue);
if(RunProcedure("","DODAJKLIENTA",c)) {
int ref = c["OUTREF"].AsInteger;
} else {
API.ShowError("Błąd","","");
}
}
Metody SQL*() przyjmują dodatkowy parametr emptyAsNull, który dla pustych wartości zwraca string "null".
string sql = "update TABELA set POLETEXT = " + this.POLETEXT.SQLString(emptyAsNull:true) + ", POLENUM = " + this.POLENUM.SQLNumber(true);//użycie true i emptyAsNull:true jest równoznaczne, ale które jest bardziej czytelne?
sql += " where REF = " + this.REF.SQLInteger();
RunSQL(sql);
Od wersji 4.0.5.2¶
Mamy skrótowe wersje metody Add do deklarowania parametrów wyjściowych i wejściowych
Contexts c = new Contexts()
.In("REF",5)
.Out("OUTREF");//Nie trzeba podawać NValueRole ani domyślnej wartości parametru
Procedury z rodziny RunSQL i RunProcedure doczekały się wersji bez konieczności podawania (w 90% przypadków) pustego aliasu bazy danych.
{
Contexts c = new Contexts()
.Add("NAZWA","Nowy klient");
if(RunProcedure("DODAJKLIENTA",c)) {
}
}
Gdy chcesz sprawdzić, czy wartość parametru wyjściowego nie jest NULLem możesz napisać
c["OUTREF"].IsNull
Metody z rodziny ToSQL() dostały również parametr emptyAsNull, który działa analogicznie jak w metodach SQL(), gdy chcemy mieć trochę większą kontrolę nad tym, kiedy przesyłamy NULLa. I tak przekazywanie nulla do bazy wygląda teraz tak:
{
this.TEKST = "";//typu VARCHAR(255)
this.LICZBA = "";//typ INTEGER
this.DATA = "";//typ DATE
Contexts c = new Contexts()
.Add("P1",TEKST) //pójdzie pusty string tak jak TEKST.ToSQLString()
.Add("P2",LICZBA)//pójdzie NULL tak jak LICZBA.ToSQLString(true)
.Add("P3",DATA)//pójdzie NULL tak jak DATA.ToSQLDate(true)
.Add("P4",TEKST.ToSQLString(true)) //pójdzie NULL
.Add("P5",LICZBA.ToSQLInteger())//pójdzie 0
.Add("P6",DATA.ToSQLDate())//pójdzie pusty string
}
Od wersji 5.4¶
W obiektach z podzieloną logiką biznesową można korzystać z generycznych metod RunProcedure, pozwala to na automatyczne utworzenie kontekstu wywołania RunProcedure z przekazanej struktury danych.
var input = new InputClass();
input.Field = "value";
OutputClass result = CORE.RunProcedure<InputClass, OutputClass>("esystem", "PROCEDURA", input);
//lub bez zwracania wyniku
CORE.RunProcedure<InputClass>("esystem", "PROCEDURA", input);
Wywołanie kodu SQL, zwracających więcej niż jeden wiersz danych¶
Note
Od wersji neosa 4.7 zostały dodane do API oraz obiektu this metody QuerySQL, które ułatwiają iterowanie po danych wielowierszowych.
foreach(var res in API.QuerySQL("select POLEINT, POLESTRING from TABELA")) {
var str = res["POLESTRING"];
var liczba = res["POLEINT"].AsInteger;
//...inne operacje
}
Wiersze doczytywane są leniwie, chyba że poprosimy o utworzenie listy.
var listawierszy = this.QuerySQL("select POLEINT, POLESTRING from TABELA")).ToList();
//do skonsumowania potem
W kodzie metod można programowo otwierać źródła danych oparte na tabelach lub zapytaniach SQL. Uważaj jednak aby mieć już dosyć dobrze zawężoną dziedzinę danych, gdyż chodzenie po dużej dziedzinie jest wolne. Przykład oparty o zapytanie SQL:
using(NDataView dv = DB.OpenTable("","","select * from STANYIL where ...", NFillMode.Default, "", "*")) {
if(dv!=null) {
if(dv.FirstRecord()) {
do {
var item = new STANYILInfo();
item.KTM = dv["KTM"];
item.MAGAZYN = dv["MAGAZYN"];
...
} while(dv.NextRecord());
} else //blad albo brak rekordow
} else //blad
}
Przykład oparty o zapytanie tabelę:
using(NDataView dv = DB.OpenTable("","STANYIL","", NFillMode.Default, "", "*")) {
if(dv!=null) {
//teraz zawężamy dziedzinę danych
dv.FilterAndSort("WERSJAREF="+wersja.ToString(),"MAGAZYN");
//dopiero teraz pobieramy dane
if(dv.FirstRecord()) {
do {
var item = new STANYILInfo();
item.KTM = dv["KTM"];
item.MAGAZYN = dv["MAGAZYN"];
...
} while(dv.NextRecord());
} else //blad albo brak rekordow
} else //blad
}
Uwaga!
Nie zaleca się używania takich konstrukcji w metodach na widoczność, redagowalność, gdyż takie metody serwer Neos wywołuje sam, robi to często i odwołania do bazy danych w nich bardzo spowalniają GUI aplikacji biznesowej.
Uruchomienie procedury SQL w bazie danych¶
static public string Add(string oddzial, string typ, string tbdate, string contractorname, string contractoraddress, string tbvalue,string tbweight, string payment, string comments, string shipmentnumber, string charge, string createheader, string crmtrbookprefin)
{
string pozref = "0";
var poz = new SENTE.CRMTRBOOKP();
Contexts spar = new Contexts();
spar.Add("ODDZIAL", oddzial, NValueRole.InputValue);
spar.Add("TYP", typ, NValueRole.InputValue);
spar.Add("TBDATE", tbdate, NValueRole.InputValue);
spar.Add("CONTRACTORNAME", contractorname, NValueRole.InputValue);
spar.Add("CONTRACTORADDRESS", contractoraddress, NValueRole.InputValue);
spar.Add("TBVALUE", tbvalue, NValueRole.InputValue);
spar.Add("TBWEIGHT", tbweight, NValueRole.InputValue);
spar.Add("PAYMENT", payment, NValueRole.InputValue);
spar.Add("COMMENTS", comments, NValueRole.InputValue);
spar.Add("SHIPMENTNUMBER", shipmentnumber, NValueRole.InputValue);
spar.Add("CHARGE", charge, NValueRole.InputValue);
spar.Add("CREATEHEADER", createheader, NValueRole.InputValue);
spar.Add("CRMTRBOOKPREFIN", crmtrbookprefin.IsEmpty() ? null : crmtrbookprefin, NValueRole.InputValue);//
spar.Add("CRMTRBOOKPREF", "0", NValueRole.OutputValue);
if(poz.RunProcedure("","CRMTRBOOKP_ADD", spar))
{
pozref = spar["CRMTRBOOKPREF"].ToString();
}
return pozref;
}
ExecuteDDLScript(string alias, string ddl)¶
Umożliwia wykonanie skryptu zmieniającego strukturę bazy danych. Za pomocą tej funkcji można np. dodać tabelę albo zmienić proceduję w bazie danych.
Funkcja przyjmuje dwa parametry:
- Alias bazy danych na której ma być wykonany skrypt (jeśli pusty to skrypt wykona się w domyślnej bazie)
- treść skryptu do wykonania
Uwaga!
W treści można użyć SET TERM, ale tylko raz na początku skryptu. Czyli na początku skryptu ustawiamy np. SET TERM ^ ; natomiast na końcu nie powracamy do znaku ;
Przykład
string ddl = @"SET TERM ^ ;
create or alter procedure TEST
as
declare variable T1 integer;
declare variable T2 varchar(20);
begin
for
select null, 'fsdg3543'
from rdb$database
into :t1, :t2
do begin
--nothing
end
end^";
API.ExecuteDDLScript("", ddl);
Dostęp do domyślnej bazy danych projektu¶
Aby uzyskać dostęp do aliasu domyślnej bazy projektu należy skorzystać z statycznej klasy ProjectInfo, która znajduje się w kodzie każdego stworzonego projektu. Zawiera ona pole DefaultDatabase, które zwraca nam alias domyślnej bazy.
public static void ShowDefaultDatabase()
{
DEBUG.Log("DefaultDB: " + TESTING.ProjectInfo.DefaultDatabaseAlias);
}