[Delphi]IBQuery uitvoeren/Thread safe data-aware components*

Pagina: 1
Acties:
  • 260 views sinds 30-01-2008
  • Reageer

  • Armageddon_2k
  • Registratie: September 2002
  • Laatst online: 30-04 15:11

Armageddon_2k

Trotse eigenaar: Yamaha R6

Topicstarter
Hey mede tweakers,

Ik heb een connectie opgezet tussen een Interbase server en Delphi.
Query's uitvoeren gaat in principe prima, maar als ik een Insert Into wil uitvoeren krijg ik een probleem:
code:
1
2
 insert into langeafstand (Schaatser,Jaar,Soort,LangeAfstand)
 values('Ids Postma', 2003, 'EK','Y')


nou zou zoon query toch geen probleem moeten opleveren dacht ik zo.
rechtstreeks met de IBconsole werkt de query.
ik ben al bezig geweest met de query om te bouwen, maar het lukt me maar niet dat het werkt.
geprobeerd:
-de singel quotes ' naar double omzetten "
-dubbelepunt voor de quotes (stond ergens op internet)
-dubbelepunt tussen de quotes
-query herschrijven ed.

resultaat:
Steeds een error met een kolom die niet bestaat (hij probeerde 'Ids Postma' als kolom te doen) met die dubbelepunt is die error weg maar nu krijg ik de erorr dat ik een een **NULL** probeer te schrijven, maar het lijkt me toch echt dat ik er een waarde heenschrijf?

  • whoami
  • Registratie: December 2000
  • Laatst online: 06-05 15:36
Je moet de query niet uitvoeren via de Open() method oid, maar via een ExecuteCommand of ExecSQL() oid method.

De Delphi help zegt er wel meer over; bekijk eens de methods die beschikbaar zijn voor het object dat je gebruikt om die query uit te voeren.

https://fgheysels.github.io/


  • Armageddon_2k
  • Registratie: September 2002
  • Laatst online: 30-04 15:11

Armageddon_2k

Trotse eigenaar: Yamaha R6

Topicstarter
Ik krijg nu nog steeds de error dat ik een ***NULL*** probeer weg te schrijven. maar hoe moet ik mn query nu opbouwen met de quotes en dubbelepuntjes?
ik heb nu:
code:
1
2
3
4
5
6
7
  IBQuery.Close;
  IBQuery.SQL.Clear;

  IBQuery.SQL.Text := ('INSERT INTO Langeafstand (Schaatser,Jaar,Soort,LangeAfstand)' +
                                   'VALUES(:"Ids Postma", :2003,:"EK",:"Y")');

  IBQuery.ExecSQL;


Edit:
Probleem een opgelost, zoals je dus al zei moest het met een ExecSQL maar daarna moet de IBTransaction dus ook nog eens Committen om de data daadwerkelijk te versturen.
Bedankt voor de hulp. nu kan ik weer verder

[ Voor 27% gewijzigd door Armageddon_2k op 07-06-2005 15:25 ]


  • whoami
  • Registratie: December 2000
  • Laatst online: 06-05 15:36
:?
Wat doen die dubbele puntjes daar ?

Als ik me nog goed herinner, dan zet je dubbele punten in een query om parameters aan te geven; echter bij jou zijn die waardes hard gecodeerd, dus zijn het geen parameters.

https://fgheysels.github.io/


  • Armageddon_2k
  • Registratie: September 2002
  • Laatst online: 30-04 15:11

Armageddon_2k

Trotse eigenaar: Yamaha R6

Topicstarter
ik wil wel gebruik maken van variablen die mijn functie binnenkomen, en daar begint de query raar te doen. ik heb het voor elkaar met harde waarden maar ik snap nog niet helemaal hoe ik het met variabelen voor erlkaar moet krijgen maar ik probeer eerst nog even verders.

Ik blijf steeds rare fouten krijgen, want ik kan dus niet die dubbele singel-quotes gebruiken ' ' die je standaard voor de "harde" query gebruik. dan krijg ik mijn variabele namenlijk in een String te staan en kan ik er nix mee. ik krijg het gewoon niet voor elkaar. het idee is:

code:
1
2
3
4
5
6
7
8
procedure  TDatabase.PutSchaatKampio(const db_schaatser, db_soort:String;
                                  db_Jaar: Integer; db_Lange: Boolean);
begin
  IBQuery.Close;
  IBQuery.SQL.Clear;

  IBQuery.SQL.text :=('INSERT INTO Langeafstand (Schaatser,Jaar,Soort,LangeAfstand)' +
                      'VALUES(:db_Schaatser,:db_Jaar,:db_Soort,:db_LangeAfstand)');


waarbij die Values de functie binnenkomen.
Ik heb even de namen wat aangepast om het overzichtelijker te maken de db_waarde komen dus de functie binnen

[ Voor 74% gewijzigd door Armageddon_2k op 07-06-2005 15:45 ]


  • whoami
  • Registratie: December 2000
  • Laatst online: 06-05 15:36
Je moet dus parameters gebruiken. Kijk eens in de help van delphi hoe je dat moet doen mbt de componenten die je gebruikt.

Met de BDE componten ging het bv als volgt (semi-pseudo code, want Delphi is al een tijd geleden)

code:
1
2
3
4
5
myQuery.SQL.Text = 'INSERT INTO tabel ( naam ) VALUES ( :naam )';

myQuery.Parameters('naam').AsString := TextBox1.Text;

myQuery.ExecSQL();


Nu, het kan zijn dat ik hier en daar iets mis, want dit is ff uit de losse pols gedaan. Ik heb hier geen Delphi staan atm, en 't is al een paar jaar geleden dat ik er nog wat mee gedaan heb.
Daarnaast zal het bij u nog wel iets anders zijn, aangezien je andere componten gebruikt.

https://fgheysels.github.io/


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 19:34

Tomatoman

Fulltime prutser

Zoek in de Delphi helpfiles eens naar het onderwerp 'Using parameters in queries' (in de index heet het onderwerp parameters, queries), daar staat het uitgebreid uitgelegd. Vergeet niet dat een INSERT commando geen result set oplevert, zodat je de query moet uitvoeren met ExecSQL en niet met Open of Active := True.

Een goede grap mag vrienden kosten.


  • BoomSmurf
  • Registratie: Maart 2003
  • Laatst online: 13-06-2025

BoomSmurf

Am-Ende!

IBQuery.SQL.Clear is totaal overbodig. SQL is van de klasse TStrings, waarvan je de waarde in kan stellen met de Text property. Of SQL daarvoor een waarde had is onbelangrijk. Volgens mij (weet ik niet 100% zeker) wordt een TIBQuery component ook automatisch gesloten als je SQL.Text een waarde toekent, dus deze is misschien ook overbodig (maar wel netjes)

Om de parameters te gebruiken:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure  TDatabase.PutSchaatKampio(const db_schaatser, db_soort:String;
                                  db_Jaar: Integer; db_Lange: Boolean);
begin
  if IBQuery.Active then
    IBQuery.Close;

  IBQuery.SQL.Text := 'INSERT INTO Langeafstand (Schaatser,Jaar,Soort,LangeAfstand)' +
                      'VALUES(:db_Schaatser,:db_Jaar,:db_Soort,:db_LangeAfstand)';

  IBQuery.ParamByName('db_Schaatser').AsString := db_schaatser;
  IBQuery.ParamByName('db_Jaar').AsInteger := db_jaar;
  IBQuery.ParamByName('db_Soort').AsString := db_soort;
  if db_Lange then
    IBQuery.ParamByName('db_Lange').AsString := 'Y'
  else
    IBQuery.ParamByName('db_Lange').AsString := 'N';
// Aan je vorige code te zien is 'LangeAfstand' niet echt een boolean veld maar een char veld, vandaar dat ik niet .AsBoolean gebruik


SQL variabelen in je SQL statement (SQL.Text) hebben geen relatie tot parameters van je functie!

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 19:34

Tomatoman

Fulltime prutser

* Tomatoman constateert een aardig gevalletje suboptimalisatie :). Je gebruikt een enterprise-class database server, dan moet je ook enterprise-class code schrijven. Dynamisch query's bouwen mag voor een desktop databaseje dan aardig werken, het is funest voor de performance van een 'echte' database server, zeker als die remote draait. Dat komt doordat een database server een query eerst prepareert voordat hij hem uitvoert. Dat prepareren kost eenmalig wat tijd, maar zorgt ervoor dat de query voortaan heel efficiënt wordt uitgevoerd.

Door telkens opnieuw de SQL-code van de query te veranderen doe je dit performancevoordeel teniet. Daar komt nog bij dat je de design-time ondersteuning van Delphi overboord zet, terwijl die er niet voor niets is. Een betere aanpak is een combinatie van wat design-time werk en wat code schrijven.

1. Stel in design-time de SQL property van de query in, waarbij je gebruik maakt van parameters.
SQL:
1
2
INSERT INTO Langeafstand (Schaatser, Jaar, Soort, LangeAfstand)
VALUES (:Schaatser, :Jaar, :Soort, :LangeAfstand)
2. Delphi vult nu automatisch de Parameters property voor je.

3. Via de Parameters property van de query kun je desgewenst in design-time de parameters nog wat tweaken. Met name de DataType property van iedere parameter is interessant.

4. Schrijf wat code voor de databaseverbinding.
Delphi:
1
2
3
4
5
procedure TMijnForm.IBConnectionAfterConnect(Sender: TObject);
begin
  { Deze code wordt eenmalig uitgevoerd }
  IBQuery.Prepare; // niet verplicht, wel ten zeerste aan te raden
end;


5. Schrijf code om de geparametriseerde query uit de voeren.
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
procedure TMijnForm.Button1Click(Sender: TObject);
begin
  { Deze code wordt uitgevoerd telkens als je op Button1 klikt }
  with IBQuery do
  begin
    Params.ParamByName('Schaatser').AsString:= EditSchaatser.Text;
    Params.ParamByName('Jaar').Value:= EditJaar.Text; // Value is een Variant
    Params.ParamByName('Soort').AsString:= Soort.Text;
    Params.ParamByName('LangeAfstand').AsString:= 'Y';
    ExecSQL;
  end;
end;

Je ziet dat er nergens aan de SQL property van de query wordt gemorreld. Wil je verschillende query's draaien, gebruik dan gewoon verschillende query-componenten. Hergebruik lijkt op het eerste gezicht handig, maar levert alleen maar problemen op. En wat je niet ziet is dat het een onnodige belasting op de database server oplevert, vandaar dat ik begon over suboptimalisatie :).

Kleine aanvulling: je gebruikt een tabelnaam langeafstand met in die tabel een veld LangeAfstand - afgezien van de hoofdletters precies dezelfde namen. Dat nodigt uit tot zeer lastig op te sporen fouten, die misschien niet eens door jouw eigen code veroorzaakt worden, maar wellicht door een database driver die over zijn nek gaat. Algemene regel: gebruik in een database unieke namen voor tabellen, query's, velden, enzovoort.

[ Voor 15% gewijzigd door Tomatoman op 07-06-2005 22:47 ]

Een goede grap mag vrienden kosten.


  • BoomSmurf
  • Registratie: Maart 2003
  • Laatst online: 13-06-2025

BoomSmurf

Am-Ende!

tomatoman schreef op dinsdag 07 juni 2005 @ 22:21:
4. Schrijf wat code voor de databaseverbinding.
Delphi:
1
2
3
4
5
procedure TMijnForm.IBConnectionAfterConnect(Sender: TObject);
begin
  { Deze code wordt eenmalig uitgevoerd }
  IBQuery.Prepare; // niet verplicht, wel ten zeerste aan te raden
end;
-1 overbodig :P

Alhoewel het natuurlijk per geval discutabel is of deze designtime features handig zijn of niet geef ik je in dit geval 100% gelijk (aangezien het zo wel lijkt alsof die query vaker uitgevoerd gaat worden). In sommige gevallen is hergebruik echter wel degelijk handig en dat hoeft totaal geen problemen te veroorzaken. Maar dat is even beside the point.

To the point dan maar. Delphi (7+ altijd, 5 en 6 met IBX upgrade) prepared een parameterized query automatisch als deze voor het eerst uitgevoerd wordt. Zolang het SQL statement niet veranderd wordt blijft deze prepared (de automatisch unprepare vindt - zonodig - pas plaats bij de destructie van het object). De gequote code hierboven kan dus weggelaten worden. Dit heeft het voordeel dat de query pas prepared wordt als hij voor het eerst gebruikt wordt en het nadeel dat de query pas prepared wordt als hij voor het eerst gebruikt wordt :+ Voordeel omdat het opstarttijd scheelt (zeker als je er 100 hebt), nadeel omdat er een ietsiepietsie 'lag' optreedt bij het voor het eerst uitvoeren van de query (mierenneukerij natuurlijk maar goed). Als dit niet zo was zou ik altijd nog deze code gebruiken ipv die jij voorstelt:

Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure TMijnForm.Button1Click(Sender: TObject);
begin
  { Deze code wordt uitgevoerd telkens als je op Button1 klikt }
  with IBQuery do
  begin
    { begin invoegsel }
    if not Prepared then
     Prepare;
    { eind invoegsel }

    Parameters.ParamByName('Schaatser').Value := EditSchaatser.Text;
    Parameters.ParamByName('Jaar').Value := IntToStr(EditJaar.Text);
    Parameters.ParamByName('Soort').Value := Soort.Text;
    Parameters.ParamByName('LangeAfstand').Value := 'Y';
    ExecSQL;
  end;
end;


Ook weer om dezelfde reden dat een hele stapel queries preparen tijdens het opstarten ook zo 'lelijk' is. Maar dat is meer een kwestie van smaak en applicatie denk ik :)

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 19:34

Tomatoman

Fulltime prutser

Daar valt veel voor te zeggen, maar evenveel tegenin te brengen. Het hangt er een beetje vanaf wat voor soort applicatie je schrijft en hoeveel tijd het preparen van de query's neemt, maar mijn ervaring is dat gebruikers het niet erg vinden om een paar seconden langer te wachten bij het starten terwijl ze wachten middenin het programma nogal irritant vinden.

Misschien dan maar de query's na het verbinden preparen in Application.OnIdle met jouw constructie als vangnet? :P
(Is Prepare eigenlijk thread-safe?)

Een goede grap mag vrienden kosten.


  • BoomSmurf
  • Registratie: Maart 2003
  • Laatst online: 13-06-2025

BoomSmurf

Am-Ende!

Leuke query als IB er een seconde over doet om te preparen, maar goed zoals we al overeengekomen waren, "it just depends!" ;) De communicatie IBX <-> IB is niet threadsafe dus Prepare logischerwijs ook niet (elke thread moet dus ook zn eigen TIBDatabase, TIBTransaction, TIBQuery, etc hebben om met de database te praten, en alleen de main thread mag een lokale connection string gebruiken, de rest moet via netwerk)

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 19:34

Tomatoman

Fulltime prutser

Dan maar een compromis:

Delphi:
1
2
3
4
5
procedure TMijnForm.IBConnectionAfterConnect(Sender: TObject); 
begin
  if Random > 0.5 then
    IBQuery.Prepare;
end;
:P

Dat verhaal over thread-safe gedrag klinkt aannemelijk. Ik begrijp alleen niet helemaal hoe het zich verhoudt tot wat de helpfiles zeggen.
Data access components are thread-safe as follows: For BDE-enabled datasets, each thread must have its own database session component. The one exception to this is when you are using Microsoft Access drivers, which are built using a Microsoft library that is not thread-safe. For dbExpress, as long as the vendor client library is thread-safe, the dbExpress components will be thread-safe. ADO and InterBaseExpress components are thread-safe.

When using data access components, you must still wrap all calls that involve data-aware controls in the Synchronize method. Thus, for example, you need to synchronize calls that link a data control to a dataset by setting the DataSet property of the data source object, but you don't need to synchronize to access the data in a field of the dataset.

[...]

Controls are not thread-safe.
Ik meen me ook te herinneren dat je best een databaseverbinding mag opzetten in een thread, zolang je het verbinden met data-aware controls maar doet in de context van de main thread. Sterker nog: met ADO in combinatie met Access heeft dit bij mij altijd prima gewerkt.

Het is inmiddels redelijk offtopic, maar is er iemand die weet hoe het precies zit met thread-safe gedrag van data-aware componenten (uitgezonderd data-aware controls)?

Een goede grap mag vrienden kosten.


  • BoomSmurf
  • Registratie: Maart 2003
  • Laatst online: 13-06-2025

BoomSmurf

Am-Ende!

offtopic:
8)

Mijn ervaring en de documentatie schijnen elkaar inderdaad tegen te spreken. Misschien dat het iets is van het D5 tijdperk (toen ik met IBX heb leren werken) wat in D7 of zo niet meer van toepassing is.

Volgens de docs zouden de IBX componenten thread-safe zijn, volgens mijn ervaring zijn ze dat niet. Volgens de docs zouden de DA controls niet thread-safe zijn, volgens jouw ervaring zijn ze dat wel.

Mooi geregeld dus :Y)

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 19:34

Tomatoman

Fulltime prutser

Nu de topictitel is aangepast is de discussie over thread-safe data-aware componenten volledig on-topic 8). Is er iemand die nader kan uitleggen welke database-acties in een aparte thread kunnen worden uitgevoerd?

• Kun je een query vanuit een aparte thread preparen?
• Kun je een query vanuit een aparte thread openen?
• Hoe kun je een vanuit een aparte thread veilig een databaseverbinding opbouwen?

Kan de goeroe zich hier melden? :)

Een goede grap mag vrienden kosten.

Pagina: 1