Вопрос по stream, delphi, delphi-xe2, sql-server-2008-r2 – TSQLQuery правильно передает только первые 1 МБ данных для больших строк

3

(см. Редактирование # 1 с трассировкой стека и Редактирование # 2 с обходным решением в конце публикации)

При устранении неполадокTSQLQuery.FieldByName (). AsString - & gt; TStringStream портит данныеЯ обнаружил, чтоTSQLQuery.FieldByName().AsBytes будет транслироваться только 1 МБvarchar(max) данные правильно.

  • Using WireShark, I verified that the data is all being handed to the Delphi app correctly.
  • I verified that it always writes out the correct number of bytes to the output file, but any bytes that exceed exactly 1MB are null bytes.
  • Additionally, TSQLQuery.FieldByName().AsString and .AsWideString also exhibit the same behavior.

Что вызвало бы.AsBytes предоставить правильное количество байтов дляTFileStream, ноnull все байты, которые превышают 1 МБ?

Test Case

Этот тестовый пример создает два выходных файла.Plus14.txt 1 МБ + 14 байт.Plus36.txt 1 МБ + 36 байт. В обоих случаях байты размером более 1 МБnull байтовые значения. Я даже попробовал строку 16 МБ. Первые 1 МБ выходного файла были правильными; следующие 15 МБ были всеnull байт.

SQL Server

use tempdb
go
create procedure RunMe
as
  declare @s1 varchar(max), @s2 varchar(max)

  set @s1 = '0123456789ABCDEF'
  set @s2 = @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 -- 128 bytes
  set @s1 = @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 -- 1,024 bytes
  set @s2 = @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 -- 8,192 bytes
  set @s1 = @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 + @s2 -- 65,536 bytes
  set @s2 = @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 + @s1 -- 524,288 bytes
  set @s1 = @s2 + @s2                                     -- 1,048,576 bytes

  set @s2 = @s1 + 'this is a test'                        -- 1MB + 14 bytes
  set @s1 = @s1 + 'of the emergency broadcasting system'  -- 1MB + 36 bytes

  select @s2 as Plus14, @s1 as Plus36
go
grant execute on RunMe to public
go

Delphi DFM

Форма по умолчанию, с этимTSQLConnection упал на него (и одинTButton):

object SQLConnection1: TSQLConnection
  DriverName = 'MSSQL'
  GetDriverFunc = 'getSQLDriverMSSQL'
  LibraryName = 'dbxmss.dll'
  LoginPrompt = False
  Params.Strings = (
    'User_Name=user'
    'Password=password'
    'SchemaOverride=%.dbo'
    'DriverUnit=Data.DBXMSSQL'

      'DriverPackageLoader=TDBXDynalinkDriverLoader,DBXCommonDriver160.' +
      'bpl'

      'DriverAssemblyLoader=Borland.Data.TDBXDynalinkDriverLoader,Borla' +
      'nd.Data.DbxCommonDriver,Version=16.0.0.0,Culture=neutral,PublicK' +
      'eyToken=91d62ebb5b0d1b1b'

      'MetaDataPackageLoader=TDBXMsSqlMetaDataCommandFactory,DbxMSSQLDr' +
      'iver160.bpl'

      'MetaDataAssemblyLoader=Borland.Data.TDBXMsSqlMetaDataCommandFact' +
      'ory,Borland.Data.DbxMSSQLDriver,Version=16.0.0.0,Culture=neutral' +
      ',PublicKeyToken=91d62ebb5b0d1b1b'
    'GetDriverFunc=getSQLDriverMSSQL'
    'LibraryName=dbxmss.dll'
    'VendorLib=sqlncli10.dll'
    'VendorLibWin64=sqlncli10.dll'
    'HostName=localhost'
    'Database=tempdb'
    'MaxBlobSize=-1'
    'LocaleCode=0000'
    'IsolationLevel=ReadCommitted'
    'OSAuthentication=False'
    'PrepareSQL=True'
    'BlobSize=-1'
    'ErrorResourceFile='
    'OS Authentication=True'
    'Prepare SQL=False')
  VendorLib = 'sqlncli10.dll'
  Left = 8
  Top = 8
end

Delphi PAS

Код дляTButton.OnClick:

procedure TForm1.Button1Click(Sender: TObject);
var qry: TSQLQuery;

  procedure save(str: string);
  var data: TBytes; fs: TFileStream;
  begin
    fs := TFileStream.Create(Format('c:\%s.txt', [str]), fmCreate);
    try
      data := qry.FieldByName(str).AsBytes;
      if data <> nil then
        fs.WriteBuffer(data[0], Length(data));
    finally
      FreeAndNil(fs);
    end;
  end;

begin
  SQLConnection1.Open;
  qry := TSQLQuery.Create(nil);
  try
    qry.MaxBlobSize := -1;
    qry.SQLConnection := SQLConnection1;
    qry.SQL.Text := 'set nocount on; exec RunMe';
    qry.Open;
    save('Plus14');
    save('Plus36');
  finally
    FreeAndNil(qry);
  end;
  SQLConnection1.Close;
end;

<<< Edit #1 - Stack Trace >>>

Я проследил код Embarcadero и нашел место, гдеnull байты сначала появляются.

  • FMethodTable.FDBXRow_GetBytes
  • Data.DBXDynalink.TDBXDynalinkByteReader.GetBytes(0,0,(...),0,1048590,True)
  • Data.SqlExpr.TCustomSQLDataSet.GetFieldData(1,$7EC80018)
  • Data.SqlExpr.TCustomSQLDataSet.GetFieldData(???,$7EC80018)
  • Data.DB.TDataSet.GetFieldData($66DB18,$7EC80018,True)
  • Data.SqlExpr.TSQLBlobStream.ReadBlobData
  • Data.SqlExpr.TSQLBlobStream.Read((no value),1048590)
  • System.Classes.TStream.ReadBuffer((no value),1048590) 1MB + 14b
  • Data.DB.TBlobField.GetAsBytes
  • Unit1.save('Plus14')

когдаFDBXRow_GetBytes возвращается,Value: TBytes составляет 1048590 байт, сnull значения, установленные для последних 14 байтов.

Я не уверен, что попробовать дальше. Любая помощь очень ценится.

<<< Edit #2 - Workaround >>>

Я поставилSQLConnection1.MaxBlobSize := 2097152и теперь все байты правильно передаются в выходные файлы. Таким образом, проблема возникает только тогда, когда.MaxBlobSize = -1.

Срочность решения проблемы ушла, когда я нашел обходной путь. Тем не менее, я все еще хотел бы получить-1 работать, если это возможно, так как значения из моей базы данных иногда превышают 50 мегабайт. Поэтому любые предложения или помощь по-прежнему приветствуются.

<<< Edit #3 - Bug Report >>>

Я отправил сообщение об ошибке в Embarcadero (QC # 108475). Я сообщу, как только ошибка будет подтверждена / исправлена.

<<< Edit #4 - Escalated Bug Report >>>

Сегодня я обнаружил, что использование этого обходного пути иногда вызываетTClientDataSet броситьEOleException с текстом & apos;Catastrophic Failure& APOS ;. ВидимоTClientDataSet предпочитаетMaxBlobSize := '-1';, Следовательно, я расширил отчет об ошибках на Embarcadero. Надеемся, что они предоставят исправление или лучший обходной путь для этого в ближайшее время.

такая же проблема возникает, если вы устанавливаете maxblobsize для qry напрямую, а не для sqlconnection? т.е. qry.maxblobsize: = -1? Hendra
Я думаю, что это ошибка. Пожалуйста, подайте отчет о КК для этого с воспроизводимым случаем вqc.embarcadero.com Jeroen Wiert Pluimers
Правильный. Я проследил код Embarcadero. Это определенно в.AsBytes код. Я добавлю в вопрос трассировку псевдостека. James L.
What would cause TSQLQuery to supply the correct number of bytes to the TFileStream, but null all bytes that exceed 1MB? То есть вы наблюдалиWriteBuffer корректно работать с буферами размером более 1 МБ, и этоAsBytes что возвращает неправильные данные? ta.speot.is
Как насчет не использовать asBytes? Интересно, есть ли такая же проблема у TBlobField (qry.fieldByName (str)). SaveToStream (fs)? Hendra

Ваш Ответ

1   ответ
1

Я смог обойти эту проблему. Может быть возможно просто установитьTSQLConnection свойства как это:

sqlcon.Params.Values['MaxBlobSize'] := '250000000'; // 250 megs
sqlcon.Params.Values['BlobSize'] := '-1';

Но я установил намного больше свойств, чем эти, используяDBXRegDB блок для настройки всехTSQLConnection компоненты. Я не использую редактор свойств среды IDE ... Некоторые настройки изDBXRegDB устройство может также потребоваться для обходного пути (я не знаю наверняка). Я включуDBXRegDB блок + инструкция как его использовать на всякий случай.

  • Add the DBXRegDB unit to the .dpr file.
  • Add DBXRegDB to the form's uses clause.
  • Execute the following code, passing it the TSQLConnection component on your form:

.

procedure SetupMSSqlConnection(const sqlcon: TSQLConnection; const hostname, port, maxcon, dbname, username, password: string);
begin
  sqlcon.Params.Clear;
  sqlcon.DriverName := 'MSSQL_Con';
  sqlcon.VendorLib := sqlcon.Params.Values[TDBXPropertyNames.VendorLib];
  sqlcon.LibraryName := sqlcon.Params.Values[TDBXPropertyNames.LibraryName];
  sqlcon.GetDriverFunc := sqlcon.Params.Values[TDBXPropertyNames.GetDriverFunc];

  sqlcon.Params.Values[TDBXPropertyNames.HostName] := hostname;
  sqlcon.Params.Values[TDBXPropertyNames.Port]     := port;
  sqlcon.Params.Values[TDBXPropertyNames.Database] := dbname;
  sqlcon.Params.Values[TDBXPropertyNames.UserName] := username;
  sqlcon.Params.Values[TDBXPropertyNames.Password] := password;
end;

Наконец, установитеTSQLQuery.MaxBlobSize на «0», чтобы он автоматически копировал значение из своегоTSQLConnection.

ЗдесьDBXRegDB блок, для тех, кто хочет его использовать. Я адаптировал это из того, что нашел здесь:DBX без развертывания DBXDrivers.ini, Убедитесь, что вы не установилиBlobSize до 250 мегабайт или вы получите ошибки памяти.

unit DBXRegDB;

interface

implementation

uses
  DBXCommon, DBXDynalinkNative, DBXMSSQL, Forms, Classes;

type
  TDBXInternalDriver = class(TDBXDynalinkDriverNative)
  public
    constructor Create(DriverDef: TDBXDriverDef); override;
  end;

  TDBXInternalProperties = class(TDBXProperties)
  public
    constructor Create(DBXContext: TDBXContext); override;
  end;

{ TDBXInternalDriver }

constructor TDBXInternalDriver.Create(DriverDef: TDBXDriverDef);
begin
  inherited Create(DriverDef, TDBXDynalinkDriverLoader);
  InitDriverProperties(TDBXInternalProperties.Create(DriverDef.FDBXContext));
end;

{ TDBXInternalProperties }

constructor TDBXInternalProperties.Create(DBXContext: TDBXContext);
begin
  inherited Create(DBXContext);

  Values[TDBXPropertyNames.SchemaOverride]         := '%.dbo';
  Values[TDBXPropertyNames.DriverUnit]             := 'DBXMSSQL';
  Values[TDBXPropertyNames.DriverPackageLoader]    := 'TDBXDynalinkDriverLoader,DBXCommonDriver160.bpl';
  Values[TDBXPropertyNames.DriverAssemblyLoader]   := 'Borland.Data.TDBXDynalinkDriverLoader,Borland.Data.DbxCommonDriver,Version=15.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
  Values[TDBXPropertyNames.MetaDataPackageLoader]  := 'TDBXMsSqlMetaDataCommandFactory,DbxMSSQLDriver160.bpl';
  Values[TDBXPropertyNames.MetaDataAssemblyLoader] := 'Borland.Data.TDBXMsSqlMetaDataCommandFactory,Borland.Data.DbxMSSQLDriver,Version=15.0.0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b';
  Values[TDBXPropertyNames.GetDriverFunc]          := 'getSQLDriverMSSQL';
  Values[TDBXPropertyNames.LibraryName]            := 'dbxmss.dll';
  Values[TDBXPropertyNames.VendorLib]              := 'sqlncli10.dll';
  Values[TDBXPropertyNames.HostName]               := 'ServerName';
  Values[TDBXPropertyNames.Database]               := 'Database Name';
  Values[TDBXPropertyNames.MaxBlobSize]            := '250000000';
  Values['LocaleCode']                             := '0000';
  Values[TDBXPropertyNames.IsolationLevel]         := 'ReadCommitted';
  Values['OSAuthentication']                       := 'False';
  Values['PrepareSQL']                             := 'True';
  Values[TDBXPropertyNames.UserName]               := 'user';
  Values[TDBXPropertyNames.Password]               := 'password';
  Values['BlobSize']                               := '-1';
  Values[TDBXPropertyNames.ErrorResourceFile]      := '';
  Values['OS Authentication']                      := 'False';
  Values['Prepare SQL']                            := 'True';
  Values[TDBXPropertyNames.ConnectTimeout]         := '30';
end;

var
  InternalConnectionFactory: TDBXMemoryConnectionFactory;

initialization

  TDBXDriverRegistry.RegisterDriverClass('MSSQL_Con', TDBXInternalDriver);
  InternalConnectionFactory := TDBXMemoryConnectionFactory.Create;
  InternalConnectionFactory.Open;
  TDBXConnectionFactory.SetConnectionFactory(InternalConnectionFactory);

end.

Похожие вопросы