Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi Parameter unter Firedac (https://www.delphipraxis.net/188447-parameter-unter-firedac.html)

Delbor 4. Mär 2016 12:47

Datenbank: MySQL • Version: 5.9xxx • Zugriff über: Firedac

Parameter unter Firedac
 
Hi zusammen

Das hier ist eine der Proceduren, die ablaufen, wenn ein Bild in meine DB aufgenommen werden soll. Eingeführt wurde sie unter DBExpress; Jetzt, unter Firedac, hab ich bis auf das beteiligte Query nichts geändert. Auch in den andern Proceduren nicht. Und es tut genau, was es soll.
In den letzten Tagen habe ich wohl beinahe "jeden Stein umgedreht" nach der Suche, wie Firedac mit Abfrageparametern umgeht. Die entsprechenden Codebeispiele von Embarcadero arbeiten alle mit "ParamsByName.(..).add." ohne die Parameter (oder wenigstens die Parameter-Collection) vorher zu erzeugen.
Auch in den Sourcen habe ich keinen Hinweis darauf gefunden, dass und wo die Params-Collection erzeugt werden. Und da selbst Delphi es nicht schafft, mit nichterzeugten Objekten zu arbeiten ( :wink:):
  • Wieso das untenstehende funktioniert, kannn ich mir mit gutem Willen ja noch vorstellen
  • Wenn unter Firedac die Parameter-Items (CollectionItems) nur hinzugefügt werden müssen,
  • warum funktioniert meinn Zeugs, obwohl ich 3 zusätzliche Parameter erzeuge?

Delphi-Quellcode:
procedure TFDMySQLDml.kategorien_bildDescribeTabelleInsert(BildDescribeLastID: Integer;bildtabelle_idbild: Integer);
  var kategorien_tabelle_Kath_ID, BildDescribeTabelle_BilddesribeID,
      BildDescribeTabelle_Bildtabelle_idBild: Integer;
      SQLString: String;
begin
  kategorien_tabelle_Kath_ID := ApplicationManager.CategoryKey;
  BildDescribeTabelle_BilddesribeID := BildDescribeLastID;
  BildDescribeTabelle_Bildtabelle_idBild := bildtabelle_idbild;
  SQLString := 'Insert Into kategorien_tabelle_has_bildDescribeTabelle'+
                            '(kategorien_tabelle_Kath_ID,'+
                            'BildDescribeTabelle_BilddesribeID,' +
                            'BildDescribeTabelle_Bildtabelle_idBild) ' +
                            'Values (:kategorien_tabelle_Kath_ID,' +
                                    ':BildDescribeTabelle_BilddesribeID,' +
                                    ':BildDescribeTabelle_Bildtabelle_idBild)';
  if (FDQueryMain.Active) then
    FDQueryMain.Active := False;
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Params.CreateParam(ftinteger, 'kategorien_tabelle_Kath_ID', ptInput);
  FDQueryMain.Params.CreateParam(ftInteger, 'BildDescribeTabelle_BilddesribeID', ptInput);
  FDQueryMain.Params.CreateParam(ftInteger, 'BildDescribeTabelle_Bildtabelle_idBild', ptInput);
  FDQueryMain.Params[0].AsInteger := kategorien_tabelle_Kath_ID;
  FDQueryMain.Params[1].AsInteger := BildDescribeTabelle_BilddesribeID;
  FDQueryMain.Params[2].AsInteger := BildDescribeTabelle_Bildtabelle_idBild;
  FDQueryMain.ExecSQL(false);
  FReportList.Add('  -TPicAdmin.kategorien_bildDescribeTabelleInsert');
end;
Gruss
Delbor

Sir Rufo 4. Mär 2016 13:02

AW: Parameter unter Firedac
 
Die Parameter werden erzeugt in
Delphi-Quellcode:
FireDAC.Phys.SQLPreprocessor.TFDPhysPreprocessor.Execute

himitsu 4. Mär 2016 13:12

AW: Parameter unter Firedac
 
Fast alle DBZugriffskomponenten sollten die Parameter selber ertstellen, anhand des übergebenen SQL.
Außer man hat diese Funktion deaktiviert, wo die Komponente das SQL parst, nachdem es zugewiesen/geändert wurde.

Delphi-Quellcode:
FDQueryMain.SQL.Text := SQLString;
FDQueryMain.Params[0].AsInteger := kategorien_tabelle_Kath_ID;
FDQueryMain.Params[1].AsInteger := BildDescribeTabelle_BilddesribeID;
FDQueryMain.Params[2].AsInteger := BildDescribeTabelle_Bildtabelle_idBild;
FDQueryMain.ExecSQL(false);

// oder
FDQueryMain.SQL.Text := SQLString;
FDQueryMain.ParamByName('kategorien_tabelle_Kath_ID').AsInteger := kategorien_tabelle_Kath_ID;
FDQueryMain.ParamByName('BildDescribeTabelle_BilddesribeID').AsInteger := BildDescribeTabelle_BilddesribeID;
FDQueryMain.ParamByName('BildDescribeTabelle_Bildtabelle_idBild').AsInteger := BildDescribeTabelle_Bildtabelle_idBild;
FDQueryMain.ExecSQL(false);

DeddyH 4. Mär 2016 13:18

AW: Parameter unter Firedac
 
Und speziell FireDAC hat da schöne Überladungen, so dass man auch mit ganz wenig Code auskommt.
Delphi-Quellcode:
Query.ExecSQL('INSERT INTO Tabelle(Feld1, Feld2, Feld3) VALUES(:Wert1, :Wert2, :Wert3)', [1, 2, 3], [ftInteger, ftInteger, ftInteger]);

Delbor 4. Mär 2016 14:06

AW: Parameter unter Firedac
 
Hi zusammen

Vielen Dank für eure aufschlussreichen Antworten!

Zitat:

Die Parameter werden erzeugt in FireDAC.Phys.SQLPreprocessor.TFDPhysPreprocessor.E xecute
Danke,Sir Rufo! Schau ich mir mal an. Kein Wunder, dass ich unter
Delphi-Quellcode:
C:\Program Files (x86)\Embarcadero\Studio\16.0\source\data\firedac\stan\param
nichts gefunden habe...
@DeddyH:
Zitat:

Query.ExecSQL('INSERT INTO Tabelle(Feld1, Feld2, Feld3) VALUES(:Wert1, :Wert2, :Wert3)', [1, 2, 3], [ftInteger, ftInteger, ftInteger]);
Das wirkt jetzt auf den ersten Blick etwas gewöhnungsbedürftig. Aber soweit ich sehe, ist das mit meinem Monster identisch.
Und wenn ich mehrere Zeilen draus mache, gehts übersichtlicher wirklich nicht mehr, ist aber immer noch viel kleiner:
Delphi-Quellcode:
Query.ExecSQL('INSERT INTO Tabelle(Feld1, Feld2, Feld3),'+
                            ' VALUES(:Wert1, :Wert2, :Wert3)',
                                    [1, 2, 3],
                                    [ftInteger, ftInteger, ftInteger]);
Wobei Gruppe 3 offenbar die Indexes enthält. Ich werde das gleich hier mal umsetzen und auch die andern Prozeduren anpassen. Auch wenn das Ding genau(?) das getan hat, was es soll: die "Createparams" von DBExpress sind nicht nur überflüssig, sondern auch zuviel des Guten.

Zitat:

Fast alle DBZugriffskomponenten sollten die Parameter selber ertstellen, anhand des übergebenen SQL.
Außer man hat diese Funktion deaktiviert, wo die Komponente das SQL parst, nachdem es zugewiesen/geändert wurde.
@ himitsu:
Das FDQuery (Unter anderen) hat ein entsprechendes Boolean-Property - dessen Name hab ich inzwischen vergessen. Aber irgendwie ist es mir och lieber, ich übergebe die Parameter selber. Ist vielleicht etwas mehr Typarbeit, dafür ist klar, was abläuft.


Gruss
Delbor

himitsu 4. Mär 2016 14:18

AW: Parameter unter Firedac
 
Zitat:

Zitat von Delbor (Beitrag 1332084)
Wobei Gruppe 3 offenbar die Indexes enthält.

Nee, das sind einfach "irgendwelche" Werte. :zwinker:

statt
Delphi-Quellcode:
[1, 2, 3]
ist natürlich
Delphi-Quellcode:
[kategorien_tabelle_Kath_ID, BildDescribeTabelle_BilddesribeID, BildDescribeTabelle_Bildtabelle_idBild]
gemeint.

Zitat:

Zitat von Delbor (Beitrag 1332084)
dafür ist klar, was abläuft.

Sooooooo unklar ist hier doch nicht, was passiert?
Delphi-Quellcode:
FDQueryMain.SQL.Text := SQLString;
FDQueryMain.ParamByName('kategorien_tabelle_Kath_ID').AsInteger := kategorien_tabelle_Kath_ID;
FDQueryMain.ParamByName('BildDescribeTabelle_BilddesribeID').AsInteger := BildDescribeTabelle_BilddesribeID;
FDQueryMain.ParamByName('BildDescribeTabelle_Bildtabelle_idBild').AsInteger := BildDescribeTabelle_Bildtabelle_idBild;
FDQueryMain.ExecSQL(false);
Gut, FireDAC wird bei falschen Typen nicht meckern können, aber spätestens das DBMS wird es dann tun.

DeddyH 4. Mär 2016 14:21

AW: Parameter unter Firedac
 
Aus der Hilfe:
Zitat:

Delphi-Quellcode:
function ExecSQL(const ASQL: String; const AParams: array of Variant; const ATypes: array of TFieldType): LongInt; overload;
...

Die vierte überladene Methode führt die in ASQL angegebene SQL-Anweisung oder die Anweisung, die der Eigenschaft SQL zugewiesen ist, aus, wenn ASQL leer ist. Sie gibt die Anzahl der aktualisierten Zeilen zurück. AParams repräsentiert ein offenes Array von Parameterwerten.
ATypes repräsentiert ein offenes Array von Parameterdatentypen, die der Abfrage zugewiesen werden.
Delphi-Quellcode:
FDQuery1.ExecSQL('insert into mytab (f1, f2) values (:f1, :f2)',
  [100, 'qweqwe'], [ftInteger, ftWideString]);


Delbor 4. Mär 2016 21:25

AW: Parameter unter Firedac
 
Hi zusammen

Sorry, wenn ich erst jetzt antworte - ich bin im Moment sozusagen im "MultitaskModus". Im Schweizer Fernsehen läuft geradwe eine Sendung, in der Leute aus Stein im Kanton Appenzell Ferien in der Wohnung eines deutschen Ehepaars in Stein an der Nordsee machen und umgekehrt...
@Himitsu:
Unter der automatischen Parametererstellung wusste ich erstmal nicht, was ich darunter verstehen sollte. Die Variablen müssen ja auch so in der bekannten Form im SQL-String angegeben werden (:xxx).
Zitat:

Sooooooo unklar ist hier doch nicht, was passiert?
Die zitierten Zeilen brauchts ja auch nicht, wenn TFDResourceOptions.ParamCreate true ist. Aus:
Delphi-Quellcode:
SQLString := 'Insert Into Bildtabelle(Thumbnail) Values (:LThumbMemory)';
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Params.CreateParam(ftBlob, 'LThumbMemory', ptInput);
  FDQueryMain.Params[0].LoadFromStream(LThumbMemory, ftgraphic);
  FDQueryMain.ExecSQL(false);
würde, wenn ich das jetzt nicht gründlich missverstanden habe:
Delphi-Quellcode:
SQLString := 'Insert Into Bildtabelle(Thumbnail) Values (:LThumbMemory)';
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.ExecSQL(false);
Da wird zwar die Variable übergeben. Aber allein aus diesen Zeilen geht nicht hervor, ob die auch wirklich parametrisiert werden.


Gruss
Delbor

himitsu 4. Mär 2016 22:08

AW: Parameter unter Firedac
 
Es bedeutet ganz einfach, dass FireDAC nach Zuweisen des SQL-Strings nach Parametern sucht, welche als
Delphi-Quellcode:
:ParamName
deklariert sind und dann macht FireDAC das Params.CreateParam, für die gefundenen Parameter (nur halt Typlos, da es den DataTyp ja nicht kennt)

Delphi-Quellcode:
SQLString := 'Insert Into Bildtabelle(Thumbnail) Values (:LThumbMemory)';
FDQueryMain.Options.ParamCreate := False; // oder irgendwie so
FDQueryMain.SQL.Text := SQLString;
FDQueryMain.Params.CreateParam(ftBlob, 'LThumbMemory', ptInput);
FDQueryMain.ParamByName('LThumbMemory').irgendwas...; // oder FDQueryMain.Params[0].irgendwas...;
FDQueryMain.ExecSQL(false);
Delphi-Quellcode:
SQLString := 'Insert Into Bildtabelle(Thumbnail) Values (:LThumbMemory)';
FDQueryMain.Options.ParamCreate := True; // oder irgendwie so
FDQueryMain.SQL.Text := SQLString;
//FDQueryMain.Params.CreateParam(ftBlob, 'LThumbMemory', ptInput); // das macht FireDAC für dich
FDQueryMain.ParamByName('LThumbMemory').irgendwas...; // oder FDQueryMain.Params[0].irgendwas...;
FDQueryMain.ExecSQL(false);

Delbor 5. Mär 2016 06:04

AW: Parameter unter Firedac
 
Hi himitsu

Vielen Dank! Das macht die Sache klarer! Ich dachte wirklich, der sucht das ':' und setzt den Parameter dann auch gleich ein!

Auffallend ist/finde ich, dass in allen Beispielen ParambyName verwendet wird und nicht der Feldindex, wobei sich Embarcadero da ja eindeutig ausspricht: treten zwei gleiche Parameternamen auf, wird der erste verwendet und der zweite fallengelassen. Dabei galt schon unter der altehrwürdigen BDE der Weg über die FeldIndexes als der Sicherere.

Gruss
Delbor

himitsu 5. Mär 2016 08:17

AW: Parameter unter Firedac
 
Der Index geht zwar schneller, aber wenn du z.B. mal am SQL was veränderst, dann ändert sich auch der Index, aber über den Namen gibt es niemals Probleme und im Code siehst auch schneller wo auf was zugegriffen wird.

Man nimmt ja auch meistens FieldByName und nicht Fields, wenn man auf die Felder zugreift.

Fallen gelassen wird nichts.
Der Parser geht von vorne nach hinden durch den String und die Parameter werden in der Reihenfolge in die Liste eingetragen, wie sie gefunden werden.
Kommt ein Parameter mehrmals im String vor, dann steht natürlich nur der "erste" Fund an seiner Stelle in der Liste und die nachfolgenden Funde werden nicht nochmal angehängt, da der Parameter dann ja schon existiert. :zwinker:

SQL-Code:
SELECT :a, :b, :a, :c
Delphi-Quellcode:
Params[0] = ParamByName('a')
Params[1] = ParamByName('b')
Params[2] = ParamByName('c')
SQL-Code:
SELECT :c, :b, :a, :a
Delphi-Quellcode:
Params[2] = ParamByName('a')
Params[1] = ParamByName('b')
Params[0] = ParamByName('c')

Delbor 5. Mär 2016 10:42

AW: Parameter unter Firedac
 
Liste der Anhänge anzeigen (Anzahl: 4)
Hi zusammen
Zitat von Sir Rufo:
Zitat:

Die Parameter werden erzeugt in FireDAC.Phys.SQLPreprocessor.TFDPhysPreprocessor.E xecute
Zumindest wer mit Delphi XE8 arbeite, wird da nichts finden - es ist wohl nicht das erste Mal, dass sich in Firedac einniges ändert.
Ich häng hier einige jpegs an, die hoffentlich meine Suche etwas dokumentieren können.

Gruss
Delbor

PS: Da muss ich wohl ziemlich geschlafen haben:
Zitat:

C:\Program Files (x86)\Embarcadero\Studio\16.0\source\data\firedac\ stan\param nichts gefunden habe...

Sir Rufo 5. Mär 2016 11:42

AW: Parameter unter Firedac
 
Diese Klasse/Unit ist auch in Seattle nicht dokumentiert.

Nimm doch einfach den Debugger und stepp durch den Code. Wenn dir das zu langweilig ist, dann suche die Parameter-Klasse und setze einen Breakpoint im
Delphi-Quellcode:
Create
. Wenn dann dort angehalten wird, gehst du einfach durch den Callstack durch und findest die Stellen, die das aufrufen.

So mache ich das wenigtens und das führt eigentlich immer zum Erfolg.

Delbor 7. Mär 2016 13:59

AW: Parameter unter Firedac
 
Hi zusammen
Zitat:

---------------------------
Im Projekt ContentMasterDXE8.exe ist eine Exception der Klasse EFDException mit der Meldung '[FireDAC][Phys][MySQL]-335. Datentyp des Parameters [LFOLDERID] ist unbekannt. Hinweis: Geben Sie TFDParam.DataType an, oder weisen Sie den TFDParam-Wert vor dem "Prepare"/"Execute"-Aufruf zu' aufgetreten.
---------------------------
Das ist die Fehlermeldung, die ich gerade eben erhalten habe. Und so versuche ich, das Ergebnis aus GuidToString in die DB zu bekommen:
Delphi-Quellcode:
function TFDMySQLDml.BildInsertQuery(LThumbMemory: TMemoryStream; LFolderID: TMemoryStream): integer;
  var SQLString: string; AUser,APass :string;
begin
  SQLString := 'Insert Into Bildtabelle(Thumbnail, FolderID) Values (:LThumbMemory, :LFolderID)';
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Params[0].LoadFromStream(LThumbMemory, ftgraphic);
  FDQueryMain.Params[0].LoadFromStream(LFolderId, ftstring); //<==
  FDQueryMain.ExecSQL(false);
...
Ursprünglich hatte ich den Datentyp für das Feld mit ftguid angegeben, aber da meckerte Delphi einige seltsame (unbekannte) Zeichen an.
Erzeugt wird die Guid so:
Delphi-Quellcode:
    ....
    if not FileExists(LPath+'IdentFile.cgf') then
    begin   // ist in dem Ordner kein IdentifikationsFile
      FolderID := CreateIDFile(LPath);
      Self.FIDFile := FolderID;                                                  /// wird eines angelegt
    end;

function TOpenFileFrame.CreateIDFile(LPath : String): String;
  var IdFile : TFileStream; ID_GUID: TGUID;
      UniqueName: String; Len: Longint;
begin
  if CreateGUID(ID_GUID) <> 0 then
     Result := 'Creating GUID failed!'
  else
  begin
     UniqueName := GUIDToString(ID_GUID);
     IdFile := TFileStream.Create(LPath,fmCreate);
     try
       Len := Length(UniqueName);
       IdFile.Write(Len, SizeOf(Len));
       IdFile.Write(PChar(UniqueName)^, Len);
     finally
      IdFile.Free;
     end;
     Result := 'IdentFile.cgf';
  end;
end;
Was mache ich falsch?

Gruss
Delbor

himitsu 7. Mär 2016 14:05

AW: Parameter unter Firedac
 
LFolderId = IdentFile.cgf ?
Binärdaten in einen String laden?

Delphi-Quellcode:
Params[0]
Params[0]
:roll:

Delphi-Quellcode:
FDQueryMain.Params[1].AsAString := TFile.ReadAllText(LFolderId); // natürlich nur, wenn da wirklich nur "Text" in der Datei ist

Delbor 9. Mär 2016 16:40

AW: Parameter unter Firedac
 
Hi Himitsu

Danke für den Tip! Entsprechend habe ich das wie folgt abgeändert
Delphi-Quellcode:
function TFDMySQLDml.BildInsertQuery(LThumbMemory: TMemoryStream; FolderId: string): integer;
  var SQLString: string; AUser,APass :string;
begin
  SQLString := 'Insert Into Bildtabelle(Thumbnail, FolderID) Values (:LThumbMemory, :FolderID)';
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Params[0].AsStream := LThumbMemory;
  FDQueryMain.Params[1].AsString := FolderID;
  FDQueryMain.ExecSQL(false);
....
und
Delphi-Quellcode:
function TOpenFileFrame.CreateIDFile(LPath : String): String;
  var IdFile : TFileStream; ID_GUID: TGUID;
      UniqueName: String; Len: Longint;
begin
  if CreateGUID(ID_GUID) <> 0 then
     Result := 'Creating GUID failed!'
  else
  begin
     UniqueName := GUIDToString(ID_GUID);
     IdFile := TFileStream.Create(LPath,fmCreate);
     try
       Len := Length(UniqueName);
       IdFile.Write(Len, SizeOf(Len));
       IdFile.Write(PChar(UniqueName)^, Len);
     finally
      IdFile.Free;
     end;
     Result := UniqueName;
  end;
end;
Meine letzte Fehlermeldung:
Zitat:

Benachrichtigung über Debugger-Exception
---------------------------
Im Projekt ContentMasterDXE8.exe ist eine Exception der Klasse EMySQLNativeException mit der Meldung '[FireDAC][Phys][MySQL] Data too long for column 'FolderID' at row 1' aufgetreten.
---------------------------
Anhalten Fortsetzen Hilfe
Das Feld in der DB ist jetzt ein Varchar(80). Max_allowed_packet (Maximale Packetgrösse) hat jetzt den Wert 16M. Beide Werte sollten nicht zu klein sein, um den Guidstring zu speichern.

Gruss
Delbor

himitsu 9. Mär 2016 17:51

AW: Parameter unter Firedac
 
hmmmmmmmmm :gruebel:

Etwas in dieser Richtung geht auch nicht?
Delphi-Quellcode:
FDQueryMain.SQL.Text := 'SELECT Thumbnail, FolderID FROM Bildtabelle WHERE 0'; // LIMIT 0
FDQueryMain.Open;
FDQueryMain.Insert;
FDQueryMain.FieldByName('Thumbnail').AsStream := LThumbMemory;
FDQueryMain.FieldByName('FolderID').AsString := FolderID;
FDQueryMain.Post;

Delbor 9. Mär 2016 20:42

AW: Parameter unter Firedac
 
Hi Himitsu

Ich bin überzeugt, dass es mit der SQL-Syntax nichts zu tun hat. Es hat ja nicht nur unter DBExpress, sondern auch unter FireDac funktioniert, bis ich der Tabelle das zusätzliche Feld verpasst habe.
Beim letzten Versuch habe ich mal ein Showmessage eingebaut. Das Resultat:
[Window Title]
Zitat:

Contentmasterdxe8

[Content]
Size of FolderId := 4

[OK]
Hmm... Variablen sind ja eigentlich Zeiger, und die sind 4 Bytes gross...

Gruss
Delbor

himitsu 10. Mär 2016 10:04

AW: Parameter unter Firedac
 
Wenn da 4 bei raus kommt, dann ist irgendwas "kaputt".

TField.Size = bei VARCHAR(MaxLength) sollte MaxLength raus kommen
TField.DataSize = bei VARCHAR(MaxLength) als WideStringField sollte 2+MaxLength*2 raus kommen

Sir Rufo 10. Mär 2016 10:24

AW: Parameter unter Firedac
 
Zitat:

Zitat von Delbor (Beitrag 1332471)
Delphi-Quellcode:
function TOpenFileFrame.CreateIDFile(LPath : String): String;
  var IdFile : TFileStream; ID_GUID: TGUID;
      UniqueName: String; Len: Longint;
begin
  if CreateGUID(ID_GUID) <> 0 then
     Result := 'Creating GUID failed!'
  else
  begin
     UniqueName := GUIDToString(ID_GUID);
     IdFile := TFileStream.Create(LPath,fmCreate);
     try
       Len := Length(UniqueName);
       IdFile.Write(Len, SizeOf(Len));
       IdFile.Write(PChar(UniqueName)^, Len);
     finally
      IdFile.Free;
     end;
     Result := UniqueName;
  end;
end;

Könntest du mal erklären wozu du das benötigst?

Du schreibst da einen GUID-String in eine Datei ... aber wozu? Um diesen String als Stream zu haben?

Nicht wirklich, oder etwa doch? :gruebel:

Delbor 10. Mär 2016 13:27

AW: Parameter unter Firedac
 
Hi zusammen

@Sir Rufo

Zitat:

Nicht wirklich, oder etwa doch?
Diese Datei soll den Ordner, der die Rohdaten der importierten Bilder enthält, kennzeichnen. Der Tip mit dieser Guid stammt übrigens von dir.
Gleichzeitig soll der vergebene GUID in der DB gespeichert werden.

Allerdings ist mir ein Designfehler unterlaufen. Ich habe das Feld für den GUID der Bildtabelle verpasst, aber eigentlich gehört das Ding in die Kategorientabelle - die enthält "Kategorien" (diesen Namen habe ich seinerzeit falsch gewählt), die jedoch eigentlich den Ordnernamen auf der Festplatte entsprechen. Diese Ordner enthalten meist Fotos, die bei einem bestimmten Ereignis entstanden sind, können aber auch solche enthalten, die sich auf mehrere Ereignisse beziehen.

@Himitsu:
Zitat:

Wenn da 4 bei raus kommt, dann ist irgendwas "kaputt".
Kaputt wohl nicht - ich habs wohl einfach falsch gemacht...
Delphi-Quellcode:
Showmessage('Size of UniqueNameUniqueName := ' + IntToStr(SizeOf(UniqueName)));
Da ich die Bytes wissen wollte, habe ich Size verwendet. Wobei mir das Leerzeichen nach Size erst jetzt auffällt...

Gruss
Delbor

PS: Dacht ichs doch: Wenn ich SizeOf durch Length ersetze, siehts ganzanders us:
[Window Title]
Zitat:

Contentmasterdxe8

[Content]
Size of UniqueName := 38
[OK]

Delbor 10. Mär 2016 14:08

AW: Parameter unter Firedac
 
Hi zusammen
Im Moment siehts ganz danach aus, als ob die DB den String/Stream akzeptiert:
Delphi-Quellcode:
procedure TOpenFileFrame.Btn_OkClick(Sender: TObject);
  var AUser,APass, Bildpfad, LPath : String;
      FolderID : TStringstream; LIdFolder: String; // <=Da Fettdarstellung innerhalb der Delphi-Tags nicht funktioniert -  die Änderungen
      LPathCount, i: integer;  
begin
  LPathCount := Self.FPathlist.Count;
  CM_First.CreateProgressbarDlg(LPathCount);     // Create den ProgressbarDialog und zeigt ihn an // Dazu braucht das System etwas Zeit
  try
    CM_First.ProgressbarDlg.Top := 20;
    CM_First.ProgressbarDlg.Left := (CM_First.ProgressbarDlg.Monitor.Width - CM_First.ProgressbarDlg.Width) div 2;
    Application.ProcessMessages;
    FDMySQLDml.Pathlist.AddStrings(FPathlist);
    CM_First.Listbox1.Items.AddStrings(FPathlist);
    Bildpfad := FPathList[0];
    LPath := ExtractFilePath(Bildpfad)+'IdentFile.cgf';
    if not FileExists(LPath+'IdentFile.cgf') then
    begin   // ist in dem Ordner kein IdentifikationsFile
      FolderID := TStringstream.Create;
      LIdFolder := CreateIDFile(LPath);
      FolderID.WriteString(LIdFolder);                                                /// wird eines angelegt
    end;
    if FDMySQLDml.DefineContentmasterConnection then
    begin
      try
        FDMySQLDml.FDConnectionMySql.StartTransaction;
        FDMySQLDml.BildTabelleInsert2(100, FolderID);                     // Startet die Insert-Prozeduren
        FDMySQLDml.FDConnectionMySql.Commit;
      except
        FDMySQLDml.FDConnectionMySql.Rollback;
      end;
    end;
  finally
      CM_First.ProgressbarDlg.FormStyle := fsnormal;
      CM_First.ProgressbarDlg.Close;
      FreeAndNil(CM_First.ProgressbarDlg);
      if assigned(FolderID) then
        FolderID.Free;
  end;
end;
Für einmal die komplette Prozedure, welche die Inserts startet. Die Änderungen sind fett dargestellt.
Delphi-Quellcode:
procedure TFDMySQLDml.BildTabelleInsert2(Seitenlaenge: Integer; FolderID: TStringstream);
  var BMap: TBitmap; BJpeg: TJPEGImage; SQLString,Bildname, Bildpfad: String;
       BildTabelleLastId,IDBild, i,inserted : Integer; LPicture : TPicture;
       LThumbMemory : TMemoryStream;
begin
  LThumbMemory := TMemoryStream.Create;
  LPicture := TPicture.Create;
  BMap := TBitmap.Create;      //Lokal
  BJpeg := TJpegImage.Create;  //Lokal
  BJpeg.CompressionQuality := 100;
  BJpeg.PixelFormat := jf24Bit;
 try
    FTotal := FPathList.Count;
    for i := 0 to FPathList.Count - 1 do
    begin                                
      FRest := FPathList.Count - i;      
      if FDQueryMain.Active then
        FDQueryMain.Active := False;
      Bildname := ExtractFileName(FPathList[i]);      
      Bildpfad := FPathList[i];
      Delete(BildName,Length(BildName)-3,4);          
        LThumbMemory.Clear;                             //<= Beim 2. durchlauf gibts hier eine AV
        if ExtractFileExt(FPathList[i]) = '.NEF' then  
        begin                                          
          LPicture.LoadFromFile(FPathList[i]);
          BMap.Assign(LPicture.Graphic);
          FModifyBitmap.ScaleBitmaps(BMap, Seitenlaenge);
          BJpeg.Assign(BMap);
          BJpeg.SaveToStream(LThumbMemory);
          BildTabelleLastId := BildInsertQuery(LThumbMemory, FolderId);
          FReportList.Add('  '+ IntToStr(i)+'-TPicAdmin.BildTabelleInsert2');
          FReportList.Add(' ');
          BildDescribeTabelleInsert(BildTabelleLastId,BildName,Bildpfad);   //Ab hier werden nacheinander die Insert-Prozeduren
          inserted := 1;                                                    //aufgerufen
          if Assigned(FOnInsertedRecord) then           //<== Hier wird das Event gefeuert; zu dem Zeitpunkt wurde
            FOnInsertedRecord(Self,inserted);         //<== ein Datensatz über die verschiedenen Tabellen eingefügt
        end;
    end;                                            
  finally
    LThumbMemory.Free;                      
    FreeAndNil(BJpeg);
    FreeAndNil(BMap);
    FreeAndNil(LPicture);
  end;
end;
In der 2. Prozedur wird die Iteration durch die Pfadliste begonnen, wobei beim 2. Durchlauf eine AV ausgelöst wird, wenn LThumbMemory geleert werden soll. Wie aber ersichtlich ist, wiird LThumbMemory erst nach getaner Arbeit freigegeben.
Da ich LThumbMemory als Parameter übergebe, kamm mir die Idee, Firedac gäbe den Stream nach getaner Arbeit frei. Das aber kannnicht sein, da ein Probelauf schonmal funktioniert hatte.

Gruss
Delbor

Sir Rufo 10. Mär 2016 15:05

AW: Parameter unter Firedac
 
Ersichtlich ist da gar nichts, denn wir können nicht sehen, was in
Delphi-Quellcode:
BildInsertQuery
mit dem Stream passiert.

BTW
  • Diese Kopierorgie mit Picture, Bitmap, JpegImage, Stream ist schon sehr abstrus und könnte bequem auf ein Picture und JpegImage reduziert werden
  • Eine GUID als StringStream ist auch sehr merkwürdig

Delbor 10. Mär 2016 15:50

AW: Parameter unter Firedac
 
Hi Sir Rufo
Zitat:

Ersichtlich ist da gar nichts, denn wir können nicht sehen, was in BildInsertQuery mit dem Stream passiert.
Delphi-Quellcode:
function TFDMySQLDml.BildInsertQuery(LThumbMemory: TMemoryStream; FolderId: TStringstream): integer;
  var SQLString: string;
begin
  SQLString := 'Insert Into Bildtabelle(Thumbnail, FolderID) Values (:LThumbMemory, :FolderID)';
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Params[0].AsStream := LThumbMemory;
  FDQueryMain.Params[1].AsStream := FolderId;
  FDQueryMain.ExecSQL(false);
  if FDQueryMain.Active then
     FDQueryMain.Close;
  SQLString := 'Select Last_Insert_ID()AS LastID';
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Open;
  if not FDQueryMain.IsEmpty then
    result := FDQueryMain.FieldByName('LastID').AsInteger;     //
end;
Wie gesagt: Im ersten Durchlauf scheint der Stream FolderId akzeptiert zu werden - ExecSQL läuft scheinbar(?) problemlos ab.
Soll dann im zweiten Durchlauf LThumbmemory neu befüllt werden, muss es erstmal geleert werden. Ich könnte wohl auch die Position zurücksetzen, dass hätte aber zur Folge, dass Reste des vorigen Bildes angezeigt werden, wenn das aktuelle Bild kleiner ist.
Zitat:

Diese Kopierorgie mit Picture, Bitmap, JpegImage, Stream ist schon sehr abstrus und könnte bequem auf ein Picture und JpegImage reduziert werden
Diese Kopierorgie macht durchaus Sinn - das Jpeg(LThumbnail) dient der Navigation in der Datenbank. Diese Dinger bringen es auf gerade mal 1.2 KB. Grössere Bilder würden die Navigation deutlich verlangsamen. Die Bitmap brauche zum einen, um ein Thumbnail im BMP-Format herzustellen. Ausserdem wollte ich sie ursprünglich auch in der DB speichern, tue dies aber aktuell noch nicht, da die Dinger gut dreimal grösser sind. Benötigt werden die Bitmaps für die Bearbeitung der Bilder. Und zu guter letzt werden damit Jpegs in "normaler" Grösse hergestellt.
Zitat:

Eine GUID als StringStream ist auch sehr merkwürdig
Da ich bei der Erzeugung der GUID diese in eine Stringvariable speichere (Embarcadero-Vorschlag), schien mir dies naheliegend.
Embarcadero äussert sich nicht darüber (oder ich habs gründlich überlesen), wie ein GUID binär gespeichert werden kann.
Einzig TPictur ist eventuell überflüssig; bei der Erstellung der Procedur schien es mir aber der einfachste weg zu sein, ein Bild zu laden. Das wäre allenfalls zu optimieren...

Gruss
Delbor

PS:
Delphi-Quellcode:
LPicture.LoadFromFile(FPathList[i]);
Das lädt erstmal ein NEF-Bild. Und davon brauche ich eine Bitmap und ein Jpeg. Nef-Bilder könnten auch mit WICImage geladen werden, aber das hab ich wieder verworfen. Soweit ich mmich erinnere, sind die WicImage-Bilder nicht mehr im Original vorhanden, wenn sie erstmal, in was auch immer, umgewamdelt wurden.

Sir Rufo 10. Mär 2016 16:00

AW: Parameter unter Firedac
 
Siehe englische Hilfeseite zu
Delphi-Quellcode:
TFDParam.AsStream

Zitat:

The assigned
Delphi-Quellcode:
TStream
object will be owned by this
Delphi-Quellcode:
TFDParam
.
Das herunterskalieren auf ein JPEG ist ja ok, aber diese wilde hin- und herkopiere ist zum großen Teil überflüssig.

Zumal ein
Delphi-Quellcode:
FDQueryMain.Params[0].Assign( MyImage );
das auch erledigt. Also ist der Stream schonmal komplett überflüssig.

Eine GUID ist auch nur eine Bytefolge und kann mit
Delphi-Quellcode:
System.Sysutils.TGUIDHelper
ganz gemütlich in ein ByteArray geschoben werden.

Delbor 10. Mär 2016 16:32

AW: Parameter unter Firedac
 
Hi SirRufo
Zitat:

Eine GUID ist auch nur eine Bytefolge und kann mit System.Sysutils.TGUIDHelper ganz gemütlich in ein ByteArray geschoben werden.
Danke für die Info!! Auf jeden Fall auf den von mir besuchten Seiten habe ich nichts über TGUIDHelper entdecken können. Wobei natürlich wie immer gilt: wegen der Fülle von Infos kann auch mal was überlesen werden.
Zitat:

Zumal ein FDQueryMain.Params[0].Assign( MyImage ); das auch erledigt. Also ist der Stream schonmal komplett überflüssig.
Eventuell dämliche Nachfrage: Was erledigt das auch? Das Einfügen des Bildes in einer für die DB akzeptablen Grösse?
Wenn ich eine Bitmap erstelle und diese bearbeite, will ich sie so, wie ich sie bearbeitet habe, in der DB. Ansonsten müsste ich ein Bild jedesmal neu als Bitmap erstellen und neu bearbeiten, wenn Bedarf besteht.

Gruss
Delbor

Sir Rufo 10. Mär 2016 16:40

AW: Parameter unter Firedac
 
Statt Grafik in Stream und Stream an Parameter einfach nur Grafik per Assign an den Parameter.

Delbor 10. Mär 2016 16:49

AW: Parameter unter Firedac
 
Hi Sir Rufo
Erstmal danke für den Tip:
Zitat:

Statt Grafik in Stream und Stream an Parameter einfach nur Grafik per Assign an den Parameter.
Und nun lass mich raten: da Grafiken binäre Daten sind, hätte das auch unter DBExpress funktioniert?

Gruss
Delbor

Sir Rufo 10. Mär 2016 16:51

AW: Parameter unter Firedac
 
Das funktioniert mit allen Klassen, die von
Delphi-Quellcode:
TPersistent
abgeleitet sind und
Delphi-Quellcode:
IStreamPersist
implementieren.

Und
Delphi-Quellcode:
TGraphic
erfüllt dieses Kriterium - siehe: Delphi-Referenz durchsuchenTGraphic

Korrektur:
Delphi-Quellcode:
TStrings
wird beim Assign gesondert behandelt und nicht über
Delphi-Quellcode:
IStreamPersist

Delbor 11. Mär 2016 13:14

AW: Parameter unter Firedac
 
Hi zusammen

In Bezug auf den GUID tut sich noch ein Problemm auf. Um mich selbst zu zitieren:
Zitat:

Allerdings ist mir ein Designfehler unterlaufen. Ich habe das Feld für den GUID der Bildtabelle verpasst, aber eigentlich gehört das Ding in die Kategorientabelle - die enthält "Kategorien" (diesen Namen habe ich seinerzeit falsch gewählt), die jedoch eigentlich den Ordnernamen auf der Festplatte entsprechen. Diese Ordner enthalten meist Fotos, die bei einem bestimmten Ereignis entstanden sind, können aber auch solche enthalten, die sich auf mehrere Ereignisse beziehen.
  • Grundsätzlich kann ich über ein Eingabefeld eine neue Kategorie erzeugen. Die muss dann ichts mit einem Ordnernamen gemein haben.
  • Die Kategorien können in der DB jederzeit geändert werden. Das wird vor allem der Fall sein, wenn sich die Rohdaten im Quellordner auf mehrere Ereignisse beziehen.
  • Auf diese Weise haben dann Bilder in der DB plötzlich keinen zugehörigen Rohdaten-Ordner mehr auf der Festplatte
Das bedeutet aber, dass ich so vorgehen muss, wie ich es bisher umgesetzt habe - die eigentliche Bildtabelle enthält neben den 3 Blobfeldern auch ein Feld für den GUID.
Und das wiederum widerspricht eigentlich den Normalisierungsregeln: Ich hab jetzt nur mal testweise Bilder aus einem Ordner in die DB aufgenommen - 444 Stück, und jedem wird der selbe GUID zugeordnet.

Wie liesse sich das lösen?

Gruss
Delbor

Sir Rufo 11. Mär 2016 14:01

AW: Parameter unter Firedac
 
Was entspricht nicht der Normalisierung? Die GUID an dem Bild zu speichern?

Wie kommst du da drauf? Den eindeutigen Pfad zu der Bilddatei bekommst du doch nur über die GUID des Ordners und dem relativen Pfad zu der Datei. Als gehören diese Informationen auch zu dem Datensatz.

Delbor 11. Mär 2016 15:06

AW: Parameter unter Firedac
 
Hi Sir Rufo

Zum einen schreien 444 Datensaetze mit einem Feld, das überall den gleichen Wert hat, nach einer eigenen Tabelle.

Eine Adresstabelle, enthält ja in der Regel aauch eine eigene Strassentabelle, in der jede Strasse einmal aufgeführt ist, aber mit beliebig vielen Personen verknüpft ist. Dabei gibts hier noch die Besonderheit, dass in vielen Orten je eine =Post- und eine Bahnhofstrasse vorhanden ist. Genau genomen müssten solche Strassen (auch Marktstrasse uund -Gasse etc) nur gerade in einer Strassentabelle vorhanden sein, könnten aber mit beliebigen Personen und fast beliebigen Orten verknüpft werden.

Klar, diese 200KB sind nicht viel und das schreit von daher seinerseits nach Brechen der Normalisierung - was ja je nach dem auch mal sinnvoll sein kann.
Zitat:

Den eindeutigen Pfad zu der Bilddatei bekommst du doch nur über die GUID des Ordners und dem relativen Pfad zu der Datei.
Vorerst ist die DB darauf ausgelegt, den vollen Pfad zu speichern. Okay - Hier nur den relativen Pfad - Ordner- und Dateinamen - zu speichern und den Rest des Pfades Aufgrund der GUID zu ermitteln würde den Speicherbedarf aufgrund des nur relativen Pfades wieder etwas verringern.

Ich hab in den letzten Tagen mal ein kleines Beispielprogramm mit TClientdataset nachgebaut, einfach, um zu sehen, was dabei wirklich raussschaut. Der Nachteil ist, dass die Fotos im Explorer nicht mehr als solche erkennbar sind. Der Vorteil wohl, dass sie gerade dadurch wohl etwas besser vor versehentlichem löschen geschützt sind.

Gruss
Delbor

DeddyH 11. Mär 2016 20:46

AW: Parameter unter Firedac
 
Die GUID ist doch als Schlüssel gedacht, von daher kann sie natürlich beliebig oft vorkommen. Wozu also noch eine weitere Tabelle?

Delbor 11. Mär 2016 21:28

AW: Parameter unter Firedac
 
Hi zusammen

Zitat:

Die GUID ist doch als Schlüssel gedacht, von daher kann sie natürlich beliebig oft vorkommen. Wozu also noch eine weitere Tabelle?
Stimmt auch wieder - der Datensatz bräuchte ein Schlüsselfeld zur Guidtabelle und diese einen PrimärIndex. Damit wäre bei einer Abfrage ein zusätzliches Schlüsselpaar zu verarbeiten.

Gruss
Delbor


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:35 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz