Ошибка при использовании SQLite ATTACH - DETACH в Delphi

У меня возникла неразрешимая для меня проблема с ATTACH и DETACH в SQLite с использованием Delphi (Firedac).

У меня подключен один файл базы данных и прикреплен второй файл с:

FDConnection1.ExecSQL('ATTACH DATABASE "' + Import_DB_filename + '" AS IMPORTDB;');

Здесь переменная 'Import_DB_filename' содержит полный путь и имя файла базы данных.

Это работает нормально, и я могу получить доступ к обеим базам данных в рамках соединения через запросы FireDac и могу без проблем выполнять кодирование. Однако при отсоединении что-то идет не так:

FDConnection1.ExecSQL('DETACH DATABASE IMPORTDB;');

В режиме отладки всегда получаю ошибку:

Уведомление об исключении отладчика
Проект E My_Program.EXE вызвал исключение класса $C0000005 с сообщением «Нарушение доступа по адресу 0x00405d7b: чтение адреса 0x00000000».

Видимо что-то не так с назначением памяти, так как отладчик останавливается в (ассемблерной) функции SysFreeMem(P:Pointer): Integer; в GETMEM.INC.

Что бы я ни пытался, ошибка сохраняется и связана с утечкой памяти, которая в конечном итоге приводит к сбою компилятора (Delphi Seattle Enterprise).

Даже присоединение и последующее отсоединение базы данных без передачи какого-либо кода приводит к той же ошибке.

(FDconnection: режим блокировки = lmNormal; JournalMode = jmOff или jmWALL или jmdelete)

Я надеюсь, что вы можете помочь мне с этой постоянной проблемой.


person JGMS    schedule 15.03.2016    source источник
comment
На всякий случай: значит, у вас уже есть надлежащее подключение к Firedac и теперь вы хотите использовать SQLite ATTACH, чтобы добавить еще один файл базы данных к текущему соединению ? И, пожалуйста, отредактируйте свой вопрос, включив в него версию Delphi.   -  person Jan Doggen    schedule 15.03.2016
comment
Почему вам нужно использовать один и тот же FDConnection с обоими файлами базы данных? Почему бы не иметь отдельное подключение для каждого из них.   -  person MartynA    schedule 15.03.2016
comment
Честно говоря, я не уверен, пробовал ли я это уже. Я проверю. Спасибо за предложение.   -  person JGMS    schedule 15.03.2016
comment
Я хочу использовать один FDConnection, потому что я копирую данные из второй базы данных в существующую базу данных с помощью операторов вставки...выбора.   -  person JGMS    schedule 15.03.2016
comment
Что ж, вы можете сделать это независимо от того, создаете ли вы операторы Sql в коде.   -  person MartynA    schedule 15.03.2016
comment
Я делаю это в данный момент. Я меняю вставку... выбираю в вставку... значения и создаю строку со значениями из второй базы данных. Это займет у меня довольно много времени. Но я уверен, что это не может быть необходимо. Прикрепление отсоединения так распространено!   -  person JGMS    schedule 15.03.2016
comment
Не могли бы вы также просто проверить, что FDConnection1 не равно nil во время вызова detach. Звучит глупо, но важно избавиться от этого.   -  person Stijn Sanders    schedule 15.03.2016


Ответы (1)


Если вы запустите приведенный ниже проект, вы обнаружите, что:

  • Вы можете легко получить доступ к двум базам данных Sqlite, используя отдельные FDConnections и FDTables.

  • Вы можете перемещать данные из таблицы в одной базе данных в таблицу с тем же именем в другой, используя компонент FireDAC FDDataMove.

Код:

unit BatchMoveu;

interface

[...]

type
  TForm3 = class(TForm)
    FDConnection1: TFDConnection;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
    DBNavigator1: TDBNavigator;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
    Button1: TButton;
    FDTable1: TFDTable;
    FDConnection2: TFDConnection;
    DataSource2: TDataSource;
    DBGrid2: TDBGrid;
    btnBatchMove: TButton;
    FDDataMove1: TFDDataMove;
    FDTable2: TFDTable;
    procedure btnBatchMoveClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    procedure PopulateTable1;
    procedure TestDataMove;
  public
    procedure CreateDatabase(DBName : String; FDConnection : TFDConnection;
      FDTable : TFDTable);
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

const
  DBName1 = 'd:\delphi\code\sqlite\db1.sqlite';
  DBName2 = 'd:\delphi\code\sqlite\db2.sqlite';

procedure TForm3.Button1Click(Sender: TObject);
begin
  CreateDatabase(DBName1, FDConnection1, FDTable1);
  CreateDatabase(DBName2, FDConnection2, FDTable2);

  PopulateTable1;

  FDTable2.Open;

end;

procedure TForm3.CreateDatabase(DBName : String; FDConnection : TFDConnection;
  FDTable : TFDTable);
var
  AField : TField;
  i : Integer;
begin
  if FileExists(DBName) then
    DeleteFile(DBName);

  AField := TLargeIntField.Create(Self);
  AField.FieldName := 'ID';
  AField.DataSet := FDTable;
  AField.Name := AField.DataSet.Name + 'IDField';

  AField := TWideStringField.Create(Self);
  AField.Size := 80;
  AField.FieldName := 'Name';
  AField.DataSet := FDTable;
  AField.Name := AField.DataSet.Name + 'NameField';

  FDConnection.Params.Values['database'] := DBName;
  FDConnection.Connected:= True;
  FDTable.CreateTable(False, [tpTable]);
end;

procedure TForm3.PopulateTable1;
var
  i : Integer;
begin
  FDTable1.Open;

  for i:= 1 to 1000 do begin
    FDTable1.InsertRecord([i, 'Row ' + IntToStr(i)]);
  end;
  FDTable1.Close;
  //FDConnection1.Commit;

  FDTable1.Open;
end;

procedure TForm3.TestDataMove;
var
  Item : TFdMappingItem;
begin
  Item := FDDataMove1.Mappings.Add;
  Item.SourceFieldName := 'ID';
  Item.DestinationFieldName := 'ID';

  Item := FDDataMove1.Mappings.Add;
  Item.SourceFieldName := 'Name';
  Item.DestinationFieldName := 'Name';

  FDDataMove1.Source := FDTable1;
  FDDataMove1.Destination := FDTable2;
  FDDataMove1.Options := FDDataMove1.Options - [poOptimiseSrc];
  FDDataMove1.Execute;

  FDConnection2.Connected := False;
  FDTable2.Open;
end;
procedure TForm3.btnBatchMoveClick(Sender: TObject);
begin
  TestDataMove;
end;

procedure TForm3.FormDestroy(Sender: TObject);
begin
  FDConnection1.Close;
end;

end.
person MartynA    schedule 16.03.2016
comment
Спасибо МартинА, - person JGMS; 17.03.2016
comment
Спасибо MartynA, Вы убедили меня, что использование двух компонентов FDConnection — это хороший способ обойти использование ATTACH и DETACH. Я изменил свой код соответственно. Однако ошибка сохраняется. Судя по всему, это имело мало отношения к прикреплению, а скорее к назначению памяти. Должно быть, я его где-то накрутил. Возможно, вы можете подсказать мне, как устранить такие ошибки. Я установил MadExcept, но это не дало мне никакой подсказки. К сожалению, шестнадцатеричные адреса мне кажутся китайскими. - person JGMS; 17.03.2016
comment
Ну, чтение адреса 0x00000000 предполагает, что что-то не было должным образом инициализировано, возможно, объект Delphi, доступ к которому осуществляется до того, как на нем был вызван .Create(). Убедитесь, что в параметрах проекта включено использование отладочных DCU, а затем выполните сборку и запуск проекта. При возникновении ошибки в среде IDE выберите Просмотр | Окна отладки | Вызовите Stack, после чего вы сможете просмотреть стек и увидеть, где возникает исключение. - person MartynA; 17.03.2016