|
![]() |
|
Registriert seit: 13. Mär 2008 156 Beiträge Delphi 10.2 Tokyo Professional |
#1
Oder den Bug bei den ADS-Leuten melden und auf 'nen Bugfix warten.
![]()
Ronald
|
![]() |
Registriert seit: 11. Feb 2016 24 Beiträge |
#2
Hallo zusammen, hallo @haentschman,
ich habe mir nochmal Gedanken gemacht zum Vorschlag, die SQL-Abfragen nicht in der Komponente zu speichern. Dazu stellen sich mir folgende Fragen: - Würdet Ihr von einer zentralen Stelle mit den SQL-Queries gleich eine Query-Komponente zurückgeben oder nur einen SQL-String für eine Komponente? Der Vorteil wäre, dass ich beim Wechsel der Datenbank nur an zentraler Stelle die Komponenten-Erzeugung ändern muss und der Kompatibilität wegen aber immer ein TDataSet zurückgeben kann. Nachteil wäre, dass ich (in der IDE) keine persistenten Felder mehr nutzen kann, an die ich z.B. Standard-AufbereitungsRoutinen (in OnGetText) hängen kann. Die Aufbereitung von Feldern z.B. für ein TDBGrid müsste dann auch im Grid erfolgen. Ebenso müsste ich auf Lookup-Felder verzichten, aber das kann mit einem Join in SQL ja auch elegant gelöst werden. Nur bei berechneten Feldern sähe es schlecht aus. - Auf dem Formular würde ich dann auch nur eine DataSource-Komponente anlegen und zur Laufzeit (z.B. im Create) von der zentralen Stelle eine Query-Komponente anfordern und mit einer DataSource verknüpfen? - Wie würdet Ihr es mit Parametern für die Anfrage machen? Diese gleich in den SQL-String mit aufnehmen oder weiterhin als Parameter über ParamByValue und Co. definieren? Hat das Einfluss auf die Performance der Abfrage, wenn sich die Werte zur Laufzeit ändern können? Wenn weiterhin Parameter genutzt werden, würdet Ihr diese direkt vom zentralen Querybuilder bestücken lassen (Updates würden dann auch hierüber laufen) oder dies dem Formular überlassen? - Wenn man den Parameter-Gedanken weiterspinnt, dann wäre es doch sinnvoll, für jede Abfrage eine eigene Routine zu machen, die ein Query liefert und konkret benannte und typisierte Werte entgegen nimmt. Andernfalls müsste ich immer die Parameter-Namen bzw. -Positionen nachschlagen Sind meine Gedanken so nachvollziehbar? Oder bin ich da eher auf dem Holzweg? Viele Grüße Sneak-L8 |
![]() |
Registriert seit: 24. Okt 2006 Ort: Seifhennersdorf / Sachsen 5.448 Beiträge Delphi 12 Athens |
#3
Hallöle...
![]() Deine Gedanken sind richtig. Wie leicht kannst du noch einen Schritt weiter machen... ![]() ![]() Würdet Ihr von einer zentralen Stelle mit den SQL-Queries gleich eine Query-Komponente zurückgeben
Delphi-Quellcode:
function TDatabase.CreateQuery: TUniQuery;
begin Result := TUniQuery.Create(nil); Result.Connection := FMeineConnection; end; ... procedure TDatabase.GetDatabaseVersion; var Query: TUniQuery; begin Query := CreateQuery; try Query.SQL.Text := 'SELECT F_VERSION FROM T_VERSION'; Query.Open; FDatabaseVersion := Query.FieldByName('F_VERSION').AsInteger; finally Query.Free; end; end; ... procedure TDatabase.FillQuery(Query: TDataSet); Query: TUniQuery; begin Query := CreateQuery; Query.SQL.Text := 'SELECT F_BlBB FROM T_BLA'; Query.Open; end; ![]() oder nur einen SQL-String für eine Komponente?
![]() Eine andere Möglichkeit ist die Statements extern zu verwalten in Ressourcen. Wenn du es wissen willst kann ich nochmal darauf eingehen. ![]() ![]() Nachteil wäre, dass ich (in der IDE) keine persistenten Felder mehr nutzen kann, an die ich z.B. Standard-AufbereitungsRoutinen (in OnGetText) hängen kann.
Die Aufbereitung von Feldern z.B. für ein TDBGrid müsste dann auch im Grid erfolgen. Ebenso müsste ich auf Lookup-Felder verzichten ![]() ![]() Die Aufbereitung von Feldern z.B. für ein TDBGrid müsste dann auch im Grid erfolgen.
![]() Wenn man den Parameter-Gedanken weiterspinnt, dann wäre es doch sinnvoll, für jede Abfrage eine eigene Routine zu machen, die ein Query liefert und konkret benannte und typisierte Werte entgegen nimmt. Andernfalls müsste ich immer die Parameter-Namen bzw. -Positionen nachschlagen
![]() Wenn man weiterspinnt...
Delphi-Quellcode:
...das Objekt kennt seinen Status. Dann speichere mal mein Blubb Objekt mit DatabaseInstanz.Save(Blubb)
...fertsch.
function TDatabase.Save(Blubb: TBlubbClass): Integer;
var Qry: TUniQuery; begin Result := -1; Qry := CreateQuery; try case Blubb.State of ddsNew: begin Qry.SQL.Text := GetSQLByName('SER_INSERT_BLUBB'); // aus Ressource Qry.ParamByName('UID').AsInteger := Blubb.UserID; Qry.ParamByName('GRO').AsString := Blubb.Group; Qry.ParamByName('DEF').AsInteger := 0; Qry.ExecSQL; Blubb.ID := Qry.ParamByName('RET_ID').AsInteger; // neue ID direkt im Objekt ... geht bei Firebird Result := Blubb.ID; end; ddsEdit: begin Qry.SQL.Text := GetSQLByName('SER_EDIT_BLUBB'); Qry.ParamByName('GRO').AsString := Blubb.Group; Qry.ParamByName('ID').AsInteger := Blubb.ID; Qry.ExecSQL; Blubb.State := ddsNormal; Result := Blubb.ID; end; ddsDeleted: begin Qry.SQL.Text := GetSQLByName('SER_DELETE_BLUBB'); Qry.ParamByName('ID').AsInteger := Blubb.ID; Qry.ExecSQL; Result := Blubb.ID; end; end; finally Qry.Free; end; end; ![]() Weiter so... ![]() Geändert von haentschman (18. Mär 2017 um 16:35 Uhr) |
![]() |
Registriert seit: 28. Apr 2008 Ort: Stolberg (Rhl) 6.659 Beiträge FreePascal / Lazarus |
#4
Die Anwendung kennt kein DBMS. Sie arbeitet auf Dataset Ebene oder mit Listen.
![]() Spätestens wenn man die Routinen eines "alten" Programms für eine neue Aufgabenstellung verwenden will, weiß man, daß sich etwas Mehrabeit gelohnt hat. Gruß K-H
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector |
![]() |
Registriert seit: 11. Feb 2016 24 Beiträge |
#5
Puh, ok ...
Erstmal vielen Dank für Deine ausführliche Antwort. Die muss ich erstmal verarbeiten. Hier ein erster Versuch einer Erwiderung... 1. Zu Deinem ersten Beispiel: die FillQuery-Methode hat Query als Var. und Param. Da ist mir die Funktionsweise nicht ganz klar. Soll der Parameter mit einem Query versorgt werden (falls er bei Übergabe nil ist)? Dann müsste er als var übergeben werden. Wenn er gefüllt ist, müsste man erst einen Close machen, falls er noch aktiv ist:
Code:
2. Wenn ich komplett auf Komponenten verzichte, dann müsste ich ja "einfache" TLabel, TEdit, ... einfügen und mit DataToForm- bzw. FormToData-Methoden die Felder befüllen (KundenNrLabel.Caption := Query.FieldByName('KUNDENNR').Text). Das wäre jetzt (solange man auf TDataSet als Basis setzt) datenbankunabhängig. Würden diese Zuweisungen dann in meiner Zugriffsklasse in einer entsprechenden Methode passieren, in der auch der Query gebildet wird? Dann wäre QueryString und Zugriff auf Felder/Feldnamen beisammen. Auf externe Ressourcen gehe ich mal noch nicht ein, das überfordert mein Hirn gerade. Eins nach dem anderen
if Assigned(Query) then begin
if Query.Active then Query.Close; end else Query := CreateQuery; ![]() Würde man das dann in eine Methode "KundeToForm(const Vorname, Nachname, Strasse, Ort : TControl" packen, der ich diverse Controls übergeben kann (TLabel, TEdit, ...) und die Werte dann zugewiesen werden, wenn ich ein Control übergeben habe (if Vorname is TLabel then ... else if Vorname is TEdit then ...)? 3. TGrid: Würdest Du dann ein CustomGrid machen und die Verbindung zu Query selbst bauen? Oder ein datensensitives TDBGrid nehmen, das dann alle gelieferten Felder anzeigt, wenn ich es zur Laufzeit mit einem TDataSet verbinde? (Dritt-Komponenten mal außen vor) 4. Dein Beispiel mit den Save-/Load-Methoden: die TBlubbclass würde nun meine fachlichen Daten enthalten. Aber in der Save-Methode von TDatabase wird dann doch "Wissen" der Tabelle benötigt, da Du ja die Felder UID und GRO explizit ansprichst. Oder sollten die grundsätzlich immer bei allen Tabellen da sein? Oder Wird TDatabase für jede Tabelle abgeleitet und Load/Save entsprechend auscodiert? 5. An den Aufruf "GetSQLByName" hatte ich auch schon gedacht. Aber dann muss der Aufrufer ja doch wieder wissen, wie die Parameter und die gelesenen Felder heißen von dem, was GetSQLByName liefert. Da finde ich es praktischer, hier gleich den SQL-String stehen zu haben, dann sehe ich beim auscodieren der FieldByName-Aufrufe gleich wie meine Felder heißen. Verstehst Du, was ich meine? 6. Noch ein anderer Gedanke zum "individuellen QueryBuilder": wenn ich z.B. die Daten eines Kunden auf dem Dialog im Kunden-Stamm, auf dem Lieferschein und auf der Rechnung anzeigen will, mache ich dann unterschiedliche Queries zw. Query-Methoden, da ich beim KundenStam mehr Daten lesen will als für den Lieferschein/die Rechnung? Oder versuche ich die anzahl der verschiedenen Queries klein zu halten und baue nur ein Query, der immer alle Felder liest und ich verwende dann nur die, die ich im Kontakt gerade brauche? Viele Grüße Sneak-L8 |
![]() |
Registriert seit: 24. Okt 2006 Ort: Seifhennersdorf / Sachsen 5.448 Beiträge Delphi 12 Athens |
#6
Moin...
![]() ![]() Zu Deinem ersten Beispiel: die FillQuery-Methode hat Query als Var. und Param. Da ist mir die Funktionsweise nicht ganz klar. Soll der Parameter mit einem Query versorgt werden (falls er bei Übergabe nil ist)? Dann müsste er als var übergeben werden. Wenn er gefüllt ist, müsste man erst einen Close machen, falls er noch aktiv ist:
Delphi-Quellcode:
function TDatabase.CreateMeinDataSet(Parameter: TMeineParameter): TDataSet;
Query: TUniQuery; begin Query := CreateQuery; // hier wir die Instanz erzeugt Query.SQL.Text := 'SELECT F_BLUBB FROM T_BLA'; Query.Open; end; . procedure TDatabase.FillQuery(Query: TDataSet); Query: TUniQuery; begin Query.SQL.Text := 'SELECT F_BLUBB FROM T_BLA'; Query.Open; end; ![]() Wenn ich komplett auf Komponenten verzichte, dann müsste ich ja "einfache" TLabel, TEdit, ... einfügen und mit DataToForm- bzw. FormToData-Methoden die Felder befüllen (KundenNrLabel.Caption := Query.FieldByName('KUNDENNR').Text). Das wäre jetzt (solange man auf TDataSet als Basis setzt) datenbankunabhängig.
![]() ![]() ![]() ![]() Würde man das dann in eine Methode "KundeToForm(const Vorname, Nachname, Strasse, Ort : TControl" packen, der ich diverse Controls übergeben kann (TLabel, TEdit, ...) und die Werte dann zugewiesen werden, wenn ich ein Control übergeben habe (if Vorname is TLabel then ... else if Vorname is TEdit then ...)?
![]()
Delphi-Quellcode:
...mit Objekten brauchst du die FeldNamen nicht kennen.
TfoBlubb = class(TForm)
edtName: TEdit; edtCaption: TEdit; strict private FBlubbDaSet: TDataSet; public property BlubbDataSet: TBlubbDataSet read FBlubbDaSet write FBlubbDaSet; end; ... edtName.Text := FBubbDataSet.FieldByName('NAME').AsString; ... // Alternativ mit Objekten wenn man eines übergibt edtName.Text := FBlubb.Name; ![]() TGrid: Würdest Du dann ein CustomGrid machen und die Verbindung zu Query selbst bauen? Oder ein datensensitives TDBGrid nehmen, das dann alle gelieferten Felder anzeigt, wenn ich es zur Laufzeit mit einem TDataSet verbinde? (Dritt-Komponenten mal außen vor)
![]() An den Aufruf "GetSQLByName" hatte ich auch schon gedacht. Aber dann muss der Aufrufer ja doch wieder wissen, wie die Parameter und die gelesenen Felder heißen von dem, was GetSQLByName liefert.
WERBUNG ![]() ![]() ![]() Da finde ich es praktischer, hier gleich den SQL-String stehen zu haben, dann sehe ich beim auscodieren der FieldByName-Aufrufe gleich wie meine Felder heißen. Verstehst Du, was ich meine?
![]() ![]() Aber in der Save-Methode von TDatabase wird dann doch "Wissen" der Tabelle benötigt, da Du ja die Felder UID und GRO explizit ansprichst. Oder sollten die grundsätzlich immer bei allen Tabellen da sein? Oder Wird TDatabase für jede Tabelle abgeleitet und Load/Save entsprechend auscodiert
Beispiel: stark gekürzt!
Delphi-Quellcode:
IdVA_Database = interface(IdDatabase)
TDatabaseIB = class(TInterfacedObject, IdDatabase) //in der Anwendung existiert nur das Interface für alle gleich. strict private FConnection: TUniConnection; FConnected: Boolean; function GetConnected: Boolean; public constructor Create; destructor Destroy; override; property Connected: Boolean read GetConnected; function Connect: Boolean; procedure Disconnect; procedure StartTransaction; procedure Commit; procedure Rollback; procedure CreateDatabase; function CreateQuery: TUniQuery; function CreateStoredProc: TUniStoredProc; function GetSQLByName(SQLName: string): string; procedure ExecuteScript(SQL: TStrings); // Beispiele function Save(UserRole: TdUserRole): Integer; overload; function Save(User: TdUser): Integer; overload; procedure SaveList(aList: TAlarmStateList); overload; procedure SaveList(aList: TDeviceList); overload; procedure Get(User: TdUser; UserName: string); overload; procedure Get(Device: TDevice; ID: Integer); overload; procedure FillList(aList: TMasterDeviceList); overload; procedure FillList(List: TdUserList; UserID: Integer = -1); overload; end; ![]() Noch ein anderer Gedanke zum "individuellen QueryBuilder": wenn ich z.B. die Daten eines Kunden auf dem Dialog im Kunden-Stamm, auf dem Lieferschein und auf der Rechnung anzeigen will, mache ich dann unterschiedliche Queries zw. Query-Methoden, da ich beim KundenStam mehr Daten lesen will als für den Lieferschein/die Rechnung? Oder versuche ich die anzahl der verschiedenen Queries klein zu halten und baue nur ein Query, der immer alle Felder liest und ich verwende dann nur die, die ich im Kontakt gerade brauche
![]() ![]() Du bist an dem Punkt wo du entscheiden mußt welche Technik du benutzen willst. Letztendlich muß es zu dir passen... ![]() ![]() Aus der Erfahrung heraus: Ich bin für Objekte in Listen. Da kann man sich die Daten bliebig zusammensetzen...auch aus verschieden Tabellen. Das Laden/Speichern übernimmt das Database "Modul". Dem Objekt ist die Quelle der Daten egal. Der Anwendung ist es wurscht wie Daten in das Objekt gekommen sind...Datenbank, XML, Omas Küchenschrank. ![]() DBGrid alles schön und gut. Du wirst aber an den Punkt kommen das die Query im Grid angezeigt werden kann, aber weil du JOINS benutzt hast, das DataSet nicht gespeichert werden kann. ![]() Da fängst du dann an umzubauen weil deine Anforderung gestiegen ist. Dadurch entstehen die berühmten Workarounds... ![]() ![]() Geändert von haentschman (19. Mär 2017 um 16:38 Uhr) |
![]() |
Registriert seit: 11. Feb 2016 24 Beiträge |
#7
Hallo zusammen,
und wieder herzlichen Dank an @haentschman für Deine Ausführungen. Ich habe mich jetzt drangemacht und mal alle neuen TAdsQuerys aus den Formularen rausgeworfen und eine QueryBuilder-Klasse gebaut. Ich hab mich jetzt mal für ein Interface (IDataSet) entschieden, das nur ein paar Sachen aus TDataSet enthält und sonst neue für mich praktische Funktionen (z.B. ein Refresh, das sich bei einem statischen Cursor den aktuellen Satz merkt, und nach Close/Open zu diesem zurückkehrt, damit man vom Update nichts sieht, solange die Datenbasis unverändert bleibt) einführt. Auf TDataSource und datensensitive Felder habe ich im ersten Schritt noch nicht verzichtet, um nicht gleich alles bisherige umzuwerfen. Über die Builder-Technik baue ich mir die Daten auf: TMyQueryBuilder.GetQueryKunde(self, Kunden).LinkTo(MyDataSource).LockForEdit; Eigentlich wollte ich dann das zurückgegebe IDataSet auch in einer Variable im Form speichern, damit ich später darauf zugreifen kann. Doch da habe ich die Rechnung ohne IntfClear gemacht und mir nette Speicherzugriffsfehler eingehandelt. Jetzt gebe ich erstmal ein TDataSet zurück. Ganz auch eine Variable im Formular verzichten klappt leider noch nicht, weil ich z.B. noch ein xxx.Post; absetzen will, wenn das Formular zum Ändern von Daten dient und ich (noch) datensensitive Elemente nutze. Aber mich beschäftigt das Interface trotzdem. Nach Studium von Google (bzw. seinen Suchtreffern) scheint es mir so, dass ich irgendwo noch auf die IDataSet-Variable zugreife, nachdem das TDataSet bereits freigegeben wurde. Ich bin aber der Meinung, dass ich die Variablen rechtzeitig vor dem Release des TDataSets freigebe (Zuweisung von nil), also spätestens im Destroy des Formulars (vor dem inherited). Das scheint aber nicht zu klappen. Habt Ihr da vielleicht einen Tipp für mich? Oder passt das alles und ich hab nur irgend eine Variable übersehen? Ansonsten fühlt sich die Sache mit der Trennung der DataSets/Queries und dem Formular gut an. Selbst die Formatierung der Felder (DisplayName, DisplayFormat oder OnGettext/OnSetText) kann ich ja in der GetQuery-Methode nach dem Open mittels FieldByName('xxx').DisplayName := 'xxx' machen. Dadurch ist es nochmal leichter, Formular und Datenkomponente zu trennen. Und ich kann die Aufbereitung a) zentral und b) direkt beim Query-String machen, so ist alles schön bei einander. Wobei ich mir DisplayName auch sparen kann, indem ich im select-Statement gleich ein 'as "xxx"' hinter die zu lesenden Felder packe... Viele Grüße Sneak-L8 |
![]() |
Ansicht |
![]() |
![]() |
![]() |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
![]() |
![]() |