Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   BlobStream in StringGrid speichern (https://www.delphipraxis.net/210528-blobstream-stringgrid-speichern.html)

Smiley 6. Mai 2022 15:56

Datenbank: Absolute DB • Version: 7.10 • Zugriff über: TABSDatabase

BlobStream in StringGrid speichern
 
Hallo Community

Ich überlege was die beste Lösung für mich ist um Daten aus einer Datenbanktabelle in ein StrinGrid zu schreiben.
Die normalen Daten sind kein Problem, ich habe jedoch noch ein Blob Feld in der Tabelle, das ich entweder irgendwie mit ins Grid bekommen muss oder nur einen Index ins Grid schreibe und den BlobFeld-Inhalt in eine Array of TJpegImage speichere.
Im Blob-Feld ist entweder ein TJpegImage gespeichert oder ein Array[0..119] of TJpegImage.
Die Angabe ob es ein Bild oder eine Bildfolge ist habe ich in der Tabelle stehen, als ‚B‘ oder ‚V‘.
Für Bild oder Video.
In der Datenbank sind mehrere Kunden mit ID gespeichert und zu jedem Kunden 1 bis 50 Datensätze mit Daten und Bild/Bildfolge.

Hier der Code wie ich ins Grid schreibe:

Code:
// **************************************************************************
// *             Daten in Grid schreiben                                   *
// **************************************************************************
procedure TDM.WriteDataToGrid(lPatNr: Cardinal; lGrid: TStringGrid);
var
  I, Row, Col: Integer;
  AlteDB: String;
  StopWatch: TStopWatch;
begin
  // Header in Grid schreiben
  lGrid.cells[0, 0] := 'ID';
  lGrid.cells[1, 0] := 'Erst.Datum';
  lGrid.cells[2, 0] := 'Info Datum';
  lGrid.cells[3, 0] := 'Typ';
  lGrid.cells[4, 0] := 'Bilder Anzahl';
  lGrid.cells[5, 0] := 'Info';
  lGrid.cells[6, 0] := 'Bild';
  lGrid.ColWidths[0] := -1;
  lGrid.ColWidths[1] := 65;
  lGrid.ColWidths[2] := 65;
  lGrid.ColWidths[3] := 30;
  lGrid.ColWidths[4] := 70;
  lGrid.ColWidths[5] := 250;
  lGrid.ColWidths[6] := 70;
 
  qData.SQL.Text := 'Select * from Bilder Where PatNr=:PatNr order by CreateDate Desc';
  qData.ParamByName('PatNr').AsInteger := lPatNr;
  qData.Open;
  qData.first;

  // Daten aus der DB lesen und in Grid schreiben
  Row := 1;
  Col := 0;

  for I := 0 to qData.RecordCount - 1 do
  begin
    // ID einlesen und in Grid und AktData schreiben
    lGrid.cells[Col, Row] := AktData.ID.ToString;
    Inc(Col);

    // CreateDate einlesen und in Grid schreiben
    lGrid.cells[Col, Row] := DateToStr(AktData.CreateDate);
    Inc(Col);

    // InfoDate einlesen und in Grid schreiben
    if (qData.FieldByName('InfoDate').AsString = '') then
      lGrid.cells[Col, Row] := ''
    else
      lGrid.cells[Col, Row] := DateToStr(AktData.InfoDate);
    Inc(Col);

    // BildTyp einlesen und in Grid schreiben
    lGrid.cells[Col, Row] := AktData.Typ;
    Inc(Col);

    // BildAnzahl einlesen und in Grid schreiben
    lGrid.cells[Col, Row] := AktData.PicCount.ToString;
    Inc(Col);

    // Infotext einlesen und in Grid schreiben
    lGrid.cells[Col, Row] := AktData.Info;
    Inc(Col);
   
   // Hier sollte das Bild aus der Tabelle in das Grid geschrieben werden
    //lGrid.cells[Row,I+6] := PicData; // Bild in Grid schreiben ???

    // Nächsten Datensatz holen
    qData.Next;
    Inc(Row);
    Col := 0;
  end;

  // Ersten Datensatz mit neuestem Datum auswählen
  // und in AktData Record ablegen
  qData.first;
  AktData.ID := qData.FieldByName('ID').AsInteger;
  AktData.CreateDate := qData.FieldByName('CreateDate').AsDateTime;
  AktData.InfoDate := qData.FieldByName('InfoDate').AsDateTime;
  AktData.Typ := qData.FieldByName('PicTyp').AsString;
  AktData.PicCount := qData.FieldByName('PicCount').AsInteger;
  AktData.Info := qData.FieldByName('Info').AsString;
  // Hier sollte das Bild aus der Tabelle in das Grid geschrieben werden
  // AktData.JPegArray:= PicData ???
  // Query schließen
  qData.Close;
//--------------------------------------------------------------------------
 
  AktData sieht so aus:
  TAktData = class
    PatNr: Cardinal;
    ID: Cardinal;
    CreateDate: TDate;
    Typ: String;
    InfoDate: TDate;
    PicCount: Byte;
    Info: String;
    DBName: string;
    JpegArray: Array [0 .. 119] of TjpegImage;
.......

constructor TAktData.Create;
Var
  i: Integer;
Begin
  ID := 0;
  PatNr := 0;
  CreateDate := 0;
  InfoDate := 0;
  Typ := 'B';
  PicCount := 1;
  Info := '';
  For i := 0 to 119 Do
  begin
    JpegArray[i] := TjpegImage.Create;
  end;
End;
Frage 1:
Kann ich das BlobFeld einfach in das StringGrid bekommen und wenn ja wie ?

Frage 2:
Wenn ich ein Array[0..119] of TJpegImage habe in dem nur einBild in Array[0] enthalten ist, wieviel wird dann im Blobfeld von den nicht benutzten Arrayfeldern an Platz beansprucht und wieviel im RAM für das Array ?

tewes 9. Mai 2022 12:35

AW: BlobStream in StringGrid speichern
 
Für solche Fälle hat TStringGrid noch "Objects":
lGrid.Objects[Col, Row] := PicData;

MyRealName 9. Mai 2022 13:16

AW: BlobStream in StringGrid speichern
 
Warum eigentlich ein Stringgrid und nicht einfach ein TDBGrid ?

Smiley 9. Mai 2022 17:09

AW: BlobStream in StringGrid speichern
 
Wenn ich ein DBGrid nehme muss ich die Datenbank ja immer offen halten, wenn ich mit den Daten arbeiten will.
Das Ziel ist aber die Datenbank nach dem lesen der Daten so schnell wie möglich wieder zu schließen.
Wenn alle Datensätze gelesen sind kann ich ohne DBGrid die Verbindung lösen und lokal im Grid arbeiten bis mal etwas geändert wird, was selten vorkommt.
Was nicht verbunden ist, kann nicht kaputt gehen. (Single user DB).

Smiley 9. Mai 2022 19:15

AW: BlobStream in StringGrid speichern
 
@tewes
Das speichern ins Grid habe ich jetzt so gemacht:
Code:
lGrid.Objects[Col, Row] := AktData.JpegArray[0];
Wenn ich es aber wieder aus dem Grid rausholen will und in das JPegArray[0] schreiben will muss ich noch den ObjectTyp casten
Das hier geht nicht:
Code:
JpegArray[0].Assign(lGrid.Objects[5,lGrid.Row]);
Inkompatible Typen TPersistent und Object

Wie schreibe ich das richtig?

tewes 10. Mai 2022 08:54

AW: BlobStream in StringGrid speichern
 
JpegArray[0].Assign(TjpegImage(lGrid.Objects[5,lGrid.Row]));

MyRealName 10. Mai 2022 10:16

AW: BlobStream in StringGrid speichern
 
Zitat:

Zitat von Smiley (Beitrag 1505622)
Wenn ich ein DBGrid nehme muss ich die Datenbank ja immer offen halten, wenn ich mit den Daten arbeiten will.
Das Ziel ist aber die Datenbank nach dem lesen der Daten so schnell wie möglich wieder zu schließen.
Wenn alle Datensätze gelesen sind kann ich ohne DBGrid die Verbindung lösen und lokal im Grid arbeiten bis mal etwas geändert wird, was selten vorkommt.
Was nicht verbunden ist, kann nicht kaputt gehen. (Single user DB).

MemoryDatasets wie TFDMemTable (FireDac Memtable).. da liest Du die ganzen records rein und funktioniert wie das Query selbst, aber ohne DB-Anbindung.
Das geht dann auch mit nur 1 Zeile Code, in dem Fall

Code:
FDMemtable1.CopyDataset(FDQuery1, [coAppend,coRestart,coStructure]);
Wobei die coXXXX halt sehen musst, was Du da brauchst. UniDAC etc. haben solche MemoryDataSets auch, falls Du kein FireDAC hast

Jumpy 10. Mai 2022 10:17

AW: BlobStream in StringGrid speichern
 
Zitat:

Zitat von Smiley (Beitrag 1505622)
Wenn ich ein DBGrid nehme muss ich die Datenbank ja immer offen halten, wenn ich mit den Daten arbeiten will.
Das Ziel ist aber die Datenbank nach dem lesen der Daten so schnell wie möglich wieder zu schließen.
Wenn alle Datensätze gelesen sind kann ich ohne DBGrid die Verbindung lösen und lokal im Grid arbeiten bis mal etwas geändert wird, was selten vorkommt.
Was nicht verbunden ist, kann nicht kaputt gehen. (Single user DB).

Ich will das jetzt nicht empfehlen, in Zeiten von ORM und Co. ist ja das Arbeiten mit den DB-Controls etwas "out", aber das Obige wäre genau das Szenario, wofür das TClientDataset gedacht ist.

Smiley 10. Mai 2022 12:23

AW: BlobStream in StringGrid speichern
 
@tewes
Danke für die Syntax.

@MyRealName
Richtig FdMemtable wäre eine gute Idee, daran habe ich gar nicht mehr gedacht.
Ich habe FireDAC,UniDAC und TMS.
Was nehmen wir denn Heute?

@Jumpy
Deine Antwort ist für mich nicht ganz eindeutig, was du aussagen möchtest.
"Ich will das jetzt nicht empfehlen" meintest du meine Idee das mit einem Stringgrid zu machen ?
"in Zeiten von ORM und Co. ist ja das Arbeiten mit den DB-Controls etwas out" kannst du das näher erläutern ?

Mit den DB-Controls hatte ich im letzten Projekt einige Schwierigkeiten, da ich im Event DataSetChange versucht habe dort die FormularAktualisierung aufzurufen und dabei einige Probleme hatte, da er dort nicht nur beim wechseln des Datensatzes dort reinspringt.
Hab ich da eventuell den falschen Event benutzt?

MyRealName 10. Mai 2022 12:38

AW: BlobStream in StringGrid speichern
 
Zitat:

Zitat von Smiley (Beitrag 1505651)
@MyRealName
Richtig FdMemtable wäre eine gute Idee, daran habe ich gar nicht mehr gedacht.
Ich habe FireDAC,UniDAC und TMS.
Was nehmen wir denn Heute?

UniDAC mit VirtualTable. Warum ? Weil Du VirtualQueries drüber machen kannst mit JOINS auf andere Datasets :) Um es jugendfei zu halten will ich gar nicht sagen, wie geil das ist :lol:

Smiley 10. Mai 2022 13:02

AW: BlobStream in StringGrid speichern
 
Zu VirtualQueries habe ich auf die schnelle keine Beispiele gefunden wofür man das einsetzen kann.
Hast du da etwas BeispielCode um zu sehen was das bringt?

Ist aber für das jetzige Projekt denke ich nicht nötig, da ich nur eine Tabelle habe und 1 bis 100 Datensätze pro ID, die ich dann in ein Memtable einlesen könnte.

Kann ich mein JPegArray[0..119] direkt in ein BlobFeld speichern und wieder rausbekommen by fdMemtable, wie wäre da die Syntax?

MyRealName 10. Mai 2022 13:46

AW: BlobStream in StringGrid speichern
 
Zitat:

Zitat von Smiley (Beitrag 1505656)
Zu VirtualQueries habe ich auf die schnelle keine Beispiele gefunden wofür man das einsetzen kann.
Hast du da etwas BeispielCode um zu sehen was das bringt?

Ist aber für das jetzige Projekt denke ich nicht nötig, da ich nur eine Tabelle habe und 1 bis 100 Datensätze pro ID, die ich dann in ein Memtable einlesen könnte.

Bei einem TVirtualQuery fügt man einfach Datensätze hinzu und gibt ihnen einen Namen und dann macht man in die SQL-Property das SQL was diese Datensatz-Namen als Tabellen-Aliase nutzt.
Zum Bsp.

Query1 Tabelle Kunden -> vQuery Alias Ku
Query2 Tabelle Kunden_Wohnort -> vQuery Alias Ku_wo

SQL.Text := SELECT Ku.*, Ku_wo.Stadt FROM Ku left join Ku_wo on (Ku_wo.Id=Ku.Id)

Das ist jetzt aus dem Gedächtnis, ich hab das bestimmt 3 Jahre nicht mehr gemacht, weil ich in meinem aktuellen Job nur noch mit FireDAC arbeiten (muss).
Die Query1 und Query2 können MemTables sein, oder TTable oder FDQuery, alles was von TDataSet abgeleitet ist. Du kannst da auch frei mixen.
SQL ist Sqlite in memory.

Zitat:

Zitat von Smiley (Beitrag 1505656)
Kann ich mein JPegArray[0..119] direkt in ein BlobFeld speichern und wieder rausbekommen by fdMemtable, wie wäre da die Syntax?

Das geht über die CreateBlobStream funktion
Hier ist das ofizielle Beispiel.

Smiley 19. Mai 2022 10:54

AW: BlobStream in StringGrid speichern
 
Ich komme mit dem Beispiel von Embarcadero einfach nicht weiter.

Hier der BeispielCode:
Code:
{-------------------------------------------------------------------------------}
procedure TfrmBlobStr.btnInsertInternalClick(Sender: TObject);
var
  oFS: TFileStream;
begin
  // All Real BLOB Streaming operations must be performed in a transaction.
  FDConnection1.StartTransaction;
  try
    case FDConnection1.RDBMSKind of
    TFDRDBMSKinds.PostgreSQL:
      qInsert.SQL.Text := 'insert into {id FDQA_LO} (blobdata) values (:blobdata)';
    TFDRDBMSKinds.Oracle:
      qInsert.SQL.Text := 'insert into {id FDQA_Blob} (blobdata) values (EMPTY_BLOB()) returning blobdata into :blobdata';
    else
      qInsert.SQL.Text := 'insert into {id FDQA_Blob} (blobdata) values (:blobdata)';
    end;
    // Set parameter data type ftStream and do not assign stream reference.
    // The internal stream reference will be returned after ExecSQL.
    // The internal stream does not perform any conversion (eg, character set).
    qInsert.Params[0].DataType := ftStream;
    qInsert.Params[0].StreamMode := smOpenWrite;
    qInsert.ExecSQL;

    oFS := TFileStream.Create(C_File, fmOpenRead);
    try
      // Write to internal stream. The stream is available after ExecSQL.
      qInsert.Params[0].AsStream.CopyFrom(oFS, -1);
    finally
      oFS.Free;
    end;
    // Flush / close the streams. Mandatory for ODBC-based, InterBase and
    // Firebird, MySQL drivers. Optional for other drivers (does nothing).
    qInsert.CloseStreams;
    Log('BLOB is written - internal streaming');

    ShowData;
    Log('BLOB is shown');
    FDConnection1.Commit;
  except
    FDConnection1.Rollback;
    raise;
  end;
end;
Was bedeutet das {id FDQA_Blob} im SQL-String und (blobdata), muss da nicht der Name des BlobFeldes rein?
Außerdem wird hier mit FileStream gearbeitet und nicht mit MemStream. Ich schreibe mein JPGArray, das ich im Speicher habe doch nicht erst auf die Platte um es dann in den Datensatz zu bekommen.

Angenommen ich habe eine Firebird Datenbank in der alle Daten liegen und einen MemTable in den ich alle Bilddaten der aktuellen ID halte. Felder ID,JPGPic.
In JPGPic soll mein JPegArray[0..119] rein. In dem ist entweder 1 Bild in JPegArray[0] oder 120 Bilder drin.

Ob ftMemTable geht ist noch fraglich, da hier in den Bemerkungen steht:
// The following drivers support Real BLOB Streaming:
// - SQLite (only external streams)
und fdMemtable ist doch SQLite.
Geht das dann überhaupt?
Oder ist Real Blob Streaming hier nicht nötig?

MyRealName 19. Mai 2022 12:48

AW: BlobStream in StringGrid speichern
 
Der STream ist ja egal, einfach Deinen MemoryStream übergeben an den BlobStream in

Code:
qInsert.Params[0].AsStream.CopyFrom(oFS, -1);

himitsu 19. Mai 2022 15:26

AW: BlobStream in StringGrid speichern
 
Falls der Blob leer bleibt oder der Anfang fehlt:

Delphi-Quellcode:
oFS.Position := 0;
qInsert.Params[0].AsStream.CopyFrom(oFS, -1);
Eventuell auch prüfen, ob man den Blob vorher Clear(en) sollte. (falls vorher mehr Daten drin waren, was neu geschrieben werden)

Smiley 19. Mai 2022 15:41

AW: BlobStream in StringGrid speichern
 
Warum ParamStr[0] und wie sage ich in welches Feld der Stream geschrieben wird.

Wie bekomme ich mein Array[0..119] of TJPEGImage in einen MemoryStream (ofs) um diesen dann so zuweisen zu können.

himitsu 19. Mai 2022 15:53

AW: BlobStream in StringGrid speichern
 
Params ... nicht ParamStr :zwinker:

such dir was aus, je nach dem wo es rein soll:
Params[123]
ParamByName('abc')
Fields[123]
FieldByName('abc')


TJPEGImage sollte doch sowas wie SaveToStream und LoadFromStream haben, wo du einen TMemoryStream nutzen kannst, bzw. vermutlich sogar direkt den Stream, welchen AsStream dir gibt.

Smiley 19. Mai 2022 16:23

AW: BlobStream in StringGrid speichern
 
OK dann also etwa so

qBilder.FieldByName('JPGPic').AsStream.CopyFrom(MS tream, -1);

Jetzt nur noch Das Array in das MStream reinbringen.

MyRealName 19. Mai 2022 17:20

AW: BlobStream in StringGrid speichern
 
Zitat:

Zitat von Smiley (Beitrag 1506093)
OK dann also etwa so

qBilder.FieldByName('JPGPic').AsStream.CopyFrom(MS tream, -1);

Jetzt nur noch Das Array in das MStream reinbringen.

Einfach die TPicture Klasse nutzen mit SaveToStream und dann aber die Stream.Position auf 0 setzen :)

Smiley 23. Mai 2022 10:08

AW: BlobStream in StringGrid speichern
 
Ich habe jetzt etwas gefunden und zusammengebaut das dem entspricht was ich vorhabe.
Ich schreibe und lese mein TJpegImage Array[0..19) direkt in ein BlobFeld und lese es so auch wieder.
Code:
// **************************************************************************
// * Read and Write Blob Stream                                            *
// **************************************************************************
procedure TDM.ReadBlobStream;
var
  BlobStream: TStream;
Begin
  // Bild aus Datenbank lesen und in AktData.JPegArray schreiben
  BlobStream := DM.qData.CreateBlobStream(DM.qData.FieldByName('PicData'), bmRead);
  BlobStream.Position := 0;
  Try
    BlobStream.ReadBuffer(AktData.JpegArray, sizeof(????????));
  Finally
    BlobStream.Free;
  End;
End;

procedure TDM.WriteBlobStream;
var
  BlobStream: TStream;
Begin
  // Bild aus AktData.JPegArray lesen und in Datenbank schreiben
  BlobStream := DM.qData.CreateBlobStream(DM.qData.FieldByName('PicData'), bmWrite);
  BlobStream.Position := 0;
  Try
    BlobStream.WriteBuffer(AktData.JpegArray, sizeof(AktData.JpegArray));
  Finally
    BlobStream.Free;
  End;
End;
Ist das so korrekt ??
Was muss ich bei SizeOf(??????) reinschreiben?

Poelser 23. Mai 2022 10:29

AW: BlobStream in StringGrid speichern
 
Ohne jetzt genauer in die Materie einzutauchen, ist mein erster Gedanke Length(AktData.JpegArray). SizeOf ist doch nur die Größe des Pointers, du brauchst hier aber doch die Länge der Daten.

Smiley 23. Mai 2022 10:43

AW: BlobStream in StringGrid speichern
 
Muss da vielleicht sowas wie
BlobStream.ReadBuffer(AktData.JpegArray,BlobStream .Size);
rein ?

Streams sind mir noch völlig fremd daher so merkwürdige Fragen.

himitsu 23. Mai 2022 11:30

AW: BlobStream in StringGrid speichern
 
Delphi-Quellcode:
BlobStream.WriteBuffer(AktData.JpegArray, SizeOf(AktData.JpegArray));


Aber NEIN.

So wird bloß die Array-Variable gespeichert.

Wäre es ein dynmaisches Array, dann bloß der Array-Zeiger
und bei einem statischen Array die Felder, also bloß die Objekt-Zeiger zu den TJpegImage.



Du willst mußt aber den Inhalt (die Daten) der JpegImage speichern, also jeweils SaveToStream.
Soll das dann noch alles zusammen in einen Stream, mußt du die die größe der einzelnen Streams speichern
und dann beim Laden die Streams aufteilen und jeweils in einen MemoryStream umkopieren.
* speichernd an einen Stream anhängen geht
** vorher die alte Position/Ende merken, ein paar Byte (z.B. Integer) als Platzhalter einfügen, mit SaveToStream das Jpeg anhängen und dann den Platzhalter mit der Größe überschreiben
** oder SaveToStream in einen zwischenStream (TMemoryStream) und dann Stream.Size in den Blob und anschließend die Daten mit CopyData rüberkopieren
* beim Laden weiß dann aber LoadFromStream nicht, wo es endet
** also das Size auslesen, die Daten in einen TMemoryStream, MS.Position zurück auf 0 und LoadFromStream(MS)
** und dann so weiter, für die anderen Streams

Smiley 23. Mai 2022 12:29

AW: BlobStream in StringGrid speichern
 
Na, jetzt wissen wir wenigstens dass es so nicht funktioniert.
Wäre ja zu schön gewesen.

Ich lasse meine Praktikanten auch immer erst mal einige Zeit suchen und sich mit dem Thema beschäftigen und gebe nur kleine Hinweise aber irgendwann ist dann doch die Schmerzgrenze erreicht und dann sage ich ihnen wie es geht.


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:36 Uhr.

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