Autor Beitrag
Herby99
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Fr 19.10.07 13:03 
Hallo zusammen,

folgendes Projekt möchte ich realisieren: Eine Anwendung läuft mit dbGo Komponenten und SQL 2005 zusammen auf einem Client, der auf den SQL-Server (Express) zugreift. Teil des Datenaustausches sind auch viele große Binärdateien, diese werden u.a. wegen der 4GB-Grenze nicht als BLOB / varbinary abgespeichert, sondern es werden nur die Pfade und Dateinamen auf dem Server in Tabellen gespeichert.

Der Dateiaustausch sollte ohne Windows-Freigaben auf dem Server auskommen (also ohne einen Share-Ordner) und daher durch SQL "getunnelt" werden.

Wie kann man so etwas am besten realisieren? Meine Idee war, auf dem Server eine Stored Procedure (SP) zu erstellen, die mit Angabe einer ID eine Datei von seiner lokalen Platte in ein varbinary einliest und das Ergebnis im Client über TADOStoredProc zurückzulesen. SP's können aber anscheinend nur int zurückgeben, daher wäre der Aufbau eher so:

ausblenden SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
CREATE PROCEDURE GetFile
  @ID int,
  @fstream varbinary(max) = NULL OUT
AS 
BEGIN
        DECLARE @fname varchar(50);
  DECLARE @updatequery varchar(2000);

  SELECT @fname = fname FROM FileTable WHERE ID = @ID;
  
        -- diesen weg hier muss man gehen, da OPENROWSET keine variablen erlaubt
  SET @updatequery = '
  UPDATE FileTable
    SET fstream = (SELECT * FROM OPENROWSET(BULK '
'' + @fname + ''', SINGLE_BLOB) AS x) 
    WHERE fname='
''+ @fname +''';';   
  EXEC (@updatequery);
        

  SELECT @fstream = fstream FROM FileTable WHERE ID=@ID;
END


wobei @fstream dann ein InputOutput wäre. In Delphi liefert mir der folgende Code

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
  ADOStoredProc1.Parameters.Clear;
  ADOStoredProc1.ProcedureName := 'GetFile';

  ADOStoredProc1.Parameters.Refresh;
  ADOStoredProc1.Parameters.ParamByName('@ID').Value := 1;
  //ADOStoredProc1.Parameters.ParamByName('@fstream').Value := null; // <- müsste im Prinzip nicht initialisiert werden?

  ADOStoredProc1.ExecProc;


aber die Fehlermeldung "Ein Parameterobjekt ist nicht ordnungsgemäß definiert. Inkonsistente oder unvollständige Informationen wurden angegeben". Was muss man beim Typ ftVarByte noch beachten? Wie kann man anschliessend das Variant-Ergebnis in einen Stream oder Datei sichern?

Wie sähe das ganze andersherum aus, also einen Stream zur DB übergeben und diesen dort als lokale Datei sichern?

Oder gibt es komplett andere Möglichkeiten soetwas zu realisieren?

Gruß,
Jan
Agawain
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 460

win xp
D5, MySQL, devxpress
BeitragVerfasst: Fr 19.10.07 21:20 
Hi

da kenn ich mich nicht mit aus, aber da bisher kein anderer geantwortet hat:

Man könnte auch eine kleine eigene Serveranwendung schreiben, die die Daten dann streamt.

Gruß

Aga
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Fr 19.10.07 21:34 
Wieso lieferst Du das VarBinary nicht über ein
ausblenden Delphi-Quelltext
1:
SELECT @MyBinaryData as BinaryData					

und holst die Daten über ein TBlobfield ab?

Allerdings würde ich auch empfehlen, auf dem Server einen kleinen TCP-Server laufen zu lassen, der auf Anforderung einfach zu einem übergebenen Dateinamen dessen Inhalt liefert (oder einfach einen FTP-Server von Indy). Irgendwie sind SQL-Server nicht dazu da, als Quasi FTP-Server zu fungieren.

_________________
Na denn, dann. Bis dann, denn.
Herby99 Threadstarter
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Mo 22.10.07 13:58 
Hallo,

danke für die Antwort! Ich habe es jetzt anhand deines Tips wie folgt umgeändert:

ausblenden volle Höhe SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
CREATE PROCEDURE GetFile (
  @ID int)
AS 
BEGIN

  DECLARE @fstream varbinary(max);
  DECLARE @fname varchar(50);
  DECLARE @path_to_fname varchar(1000);
  DECLARE @updatequery varchar(2000)
  SET @path_to_fname = 'D:\temp2\myDBFolder\';
  
  -- prüfen, ob fstream geladen werden muss
  SELECT @fstream = fstream FROM FileTable WHERE ID=@ID;  
  IF @fstream IS NULL
  BEGIN
    -- OPENROWSET unterstützt keine variablen, daher der umweg:
    SELECT @fname = fname FROM FileTable WHERE ID = @ID;    
    SET @updatequery = '

      UPDATE FileTable
      SET fstream = (SELECT * FROM OPENROWSET(BULK ''' + @path_to_fname + @fname + ''', SINGLE_BLOB) AS x) 
      WHERE ID='+ @ID +';';

    --print @updatequery;
    EXEC (@updatequery);
  END;
  
  SELECT @fstream = fstream, @fname = fname FROM FileTable WHERE ID=@ID;    
  
  SELECT @fstream as BinaryData, @fname as FileName;    
  
  
END;


und abgefragt wird in Delphi über:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
  ADOStoredProc1.Close;
  ADOStoredProc1.Parameters.Clear;
  ADOStoredProc1.ProcedureName := 'dbo.GetFile';
  ADOStoredProc1.Parameters.AddParameter.Name := '@ID';
  
  ADOStoredProc1.Parameters.ParamByName('@ID').DataType := ftInteger;
  ADOStoredProc1.Parameters.ParamByName('@ID').Direction := pdInput;
  ADOStoredProc1.Parameters.ParamByName('@ID').Size := MaxInt;
  ADOStoredProc1.Parameters.ParamByName('@ID').Value := ID;

  ADOStoredProc1.Open;
  
  // speichern
  JvSaveDialog1.FileName := ExtractFileName(ADOStoredProc1.fields.FieldByName('FileName').Value);
  if JvSaveDialog1.Execute = True then
  begin
    TBlobField(ADOStoredProc1.fields.FieldByName('BinaryData')).SaveToFile(JvSaveDialog1.FileName);
  end;


Anscheinend gibt es wohl immer irgendwie Probleme mit den Parametern - so wie es jetzt da steht geht es, eigentlich sollte es aber auch ohne dass "AddParameter..." gehen?! Der Vollständigkeit halber für Interessierte gibt es noch den Code für das Hochladen und abspeichern auf dem Server:

ausblenden volle Höhe SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
CREATE PROCEDURE [dbo].[SendFile] (
  @filename varchar(50),  
  @fstream varbinary(max))
AS
BEGIN
  -- in tabelle zwischenspeichern
  INSERT INTO dbo.FileTable (fname, fstream) VALUES (@filename, @fstream);
  
  -- lokaler file dump
  DECLARE @local_filename varchar(200);
  DECLARE @bulk_export_action varchar(2000);
        -- zielverzeichnis auf dem server erstmal hart codieren:
  SET @local_filename = 'd:\temp2\myDBFolder\' + @filename;
  SET @bulk_export_action = '
bcp "SELECT TOP 1 fstream FROM Testdatenbank.dbo.FileTable ORDER BY ID DESC" queryout ' + @local_filename + ' -n -T';

  -- shell aktivieren  
  EXEC sp_configure '
awe enabled', 0; -- setzen auf 1 bei SQL Express 2005 nicht erlaubt
  EXEC sp_configure '
xp_cmdshell', 1;    
  RECONFIGURE WITH OVERRIDE;
  
  -- bulk export ausführen
  EXEC xp_cmdshell @bulk_export_action

  -- shell wieder deaktivieren
  EXEC sp_configure '
xp_cmdshell', 0
  RECONFIGURE WITH OVERRIDE;  

  -- BLOB aus DB löschen
  UPDATE FileTable SET fstream = NULL;
   
  -- resultierende ID rausgeben
  SELECT TOP 1 ID FROM dbo.FileTable ORDER BY ID DESC;
  
END


und in Delphi

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
  ADOStoredProc1.Close;
  ADOStoredProc1.Parameters.Clear;
  ADOStoredProc1.ProcedureName := 'dbo.SendFile';

  if JvOpenDialog1.Execute then
  begin
    ADOStoredProc1.Parameters.AddParameter.Name := '@filename';
    ADOStoredProc1.Parameters.AddParameter.Name := '@fstream';

    ADOStoredProc1.Parameters.ParamByName('@filename').DataType := ftString;
    ADOStoredProc1.Parameters.ParamByName('@filename').Direction := pdInput;
    ADOStoredProc1.Parameters.ParamByName('@filename').Size := 50;
    ADOStoredProc1.Parameters.ParamByName('@filename').Value := ExtractFileName(JvOpenDialog1.FileName);

    ADOStoredProc1.Parameters.ParamByName('@fstream').DataType := ftBlob;
    ADOStoredProc1.Parameters.ParamByName('@fstream').Direction := pdInput;
    ADOStoredProc1.Parameters.ParamByName('@fstream').Size := MaxInt;
    ADOStoredProc1.Parameters.ParamByName('@fstream').LoadFromFile(JvOpenDialog1.FileName, ftBlob);

    ADOStoredProc1.Open;
  end;


Zum allgemeinen Handling mit Dateien: Eine FTP-ähnliche Anwendung wäre sicherlich schicker, hat aber einige Nachteile:

- Auf dem Server müsste noch eine weitere Software installiert werden (und vom Client angesteuert werden)
- Der eigene Admin ist eh immer in Panik wenn irgendwas nicht auf Standardports funkt :-)
- Admin beim Kunden dankt es evtl. ebenso

Zudem werden nicht ständig große Mengen hin- und her geschoben, so dass sich die Serverlast in Grenzen hält.

Gruß,
Jan
alzaimar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2889
Erhaltene Danke: 13

W2000, XP
D6E, BDS2006A, DevExpress
BeitragVerfasst: Mo 22.10.07 14:03 
Von hinten durch die Brust ins Auge. Aber wenns funzt, funzt's.

_________________
Na denn, dann. Bis dann, denn.