Как применить кэшированный update FDQuery с помощью Delphi FireDAC с УНИКАЛЬНЫМ ограничением для базы данных

#database #sqlite #delphi #unique #firedac

#База данных #sqlite #delphi #уникальный #firedac

Вопрос:

У меня проблема с разрешением обновлений кэша, когда delta включает поля, которые имеют УНИКАЛЬНОЕ ограничение для базы данных. У меня есть база данных со следующей схемой DDL (для воспроизведения можно использовать SQLite в памяти):

 create table FOO
(
  ID integer primary key,
  DESC char(2) UNIQUE
);
  

Исходная таблица базы данных содержит одну запись с ID = 1 и DESC = R1

Доступ к этой таблице с помощью TFDQuery (выберите * из FOO), если будут выполнены следующие шаги, сгенерированная дельта будет правильно применена с помощью ApplyUpdates:

  1. Обновите идентификатор записи = 1 до DESC = R2
  2. Добавьте новую запись ID = 2 с помощью DESC = R1

Delta включает в себя следующее:

  1. R2
  2. R1

При ApplyUpdates ошибка не будет сгенерирована, поскольку первой операцией над delta будет обновление. Вторым будет вставка. Поскольку запись 1 теперь равна R2, вставка может быть выполнена, поскольку в этой транзакции нет нарушения уникального ограничения.

Теперь, выполнив следующие шаги, будет сгенерирована точно такая же дельта (посмотрите на FDQuery.Свойство Delta), но будет сгенерировано УНИКАЛЬНОЕ нарушение ограничения.

  1. Добавьте новую временную запись ID = 2 с помощью DESC = TT
  2. Обновите первую запись ID = 1 до DESC = R2
  3. Обновите временную запись 2 — TT до DESC = R1

Delta включает в себя следующее:

  1. R2
  2. R1

Обратите внимание, что FireDAC генерирует одинаковую дельту в обоих сценариях, это можно просмотреть через свойство Delta FDQuery.

Эти шаги можно использовать для воспроизведения ошибки:

Файл> Новое приложение VCL Forms; Поместите FDConnection и FDQuery в форму; Установите FDConnection на использование драйвера SQLite (с использованием базы данных в памяти); Поместите две кнопки на форму, одну для правильного воспроизведения поведения, а другую для воспроизведения ошибки, следующим образом:

Кнопка OK:

 procedure TFrmMain.btnOkClick(Sender: TObject);
begin
  // create the default database with a FOO table
  con.Open();
  con.ExecSQL('create table FOO'   '(ID integer primary key, DESC char(2) UNIQUE)');
  // insert a default record
  con.ExecSQL('insert into FOO values (1,''R1'')');
  qry.CachedUpdates := true;
  qry.Open('select * from FOO');
  // update the first record to T2
  qry.First();
  qry.Edit();
  qry.Fields[1].AsString := 'R2';
  qry.Post();
  // append the second record to T1
  qry.Append();
  qry.Fields[0].AsInteger := 2;
  qry.Fields[1].AsString := 'R1';
  qry.Post();
  // apply will not generate a unique constraint violation
  qry.ApplyUpdates();
end;
  

Ошибка кнопки:

   // create the default database with a FOO table
  con.Open();
  con.ExecSQL('create table FOO'   '(ID integer primary key, DESC char(2) UNIQUE)');
   // insert a default record
  con.ExecSQL('insert into FOO values (1,''R1'')');
  qry.CachedUpdates := true;
  qry.Open('select * from FOO');
  // append a temporary record (TT)
  qry.Append();
  qry.Fields[0].AsInteger := 2;
  qry.Fields[1].AsString := 'TT';
  qry.Post();
  // update R1 to R2
  qry.First();
  qry.Edit();
  qry.Fields[1].AsString := 'R2';
  qry.Post();
  qry.Next();
  // update TT to R1
  qry.Edit();
  qry.Fields[1].AsString := 'R1';
  qry.Post();
  // apply will generate a unique contraint violation
  qry.ApplyUpdates();
  

Комментарии:

1. Я собирался ответить, что внутри транзакции вы можете удалить уникальное ограничение перед обновлением и повторно включить его после Applyupdates. Но похоже, что Sqlite не может удалить уникальные ограничения (вам нужно создать новую таблицу без ограничения и переместить туда все данные), так что это неосуществимое решение.

2. Добро пожаловать в StackOverflow и превосходно написанный вопрос. Я могу воспроизвести это в D Seattle, даже с CachedUpdates := False . Те же шаги, которые выполняются как сценарий Sql, работают без ошибок. Интересно, что ошибка по-прежнему возникает после вставки дополнительного ApplyUpdates непосредственно перед qrry.Next этим .

Ответ №1:

Обновление С момента написания исходной версии этого ответа я провел еще некоторое расследование и начинаю думать, что либо есть проблема с ApplyUpdates и т. Д. В поддержке FireDAC для Sqlite (по крайней мере, в Сиэтле), либо мы неправильно используем компоненты FD. Для этого потребуется, чтобы автор FireDAC (который является участником здесь) сказал, что это такое.

Оставляя ApplyUpdates на мгновение в стороне бизнес, существует ряд других проблем с вашим кодом, а именно, ваша навигация по набору данных делает предположения о порядке строк в qry и нумерации its Fields .

Тестовый пример, который я использовал, заключается в запуске (до выполнения приложения) с таблицей Foo, содержащей одну строку

 (1, 'R1')
  

Затем я выполняю следующий код Delphi, одновременно отслеживая содержимое Foo с помощью внешнего приложения (подключаемый модуль Sqlite Manager для FireFox). Код выполняется без сообщения об ошибке в приложении, но обратите внимание, что он не вызывается ApplyUpdates .

   Con.Open();
  Con.StartTransaction;
  qry.Open('select * from FOO');
  qry.InsertRecord([2, 'TT']);
  assert(qry.Locate('ID', 1, []));
  qry.Edit;
  qry.FieldByName('DESC').AsString := 'R2';
  qry.Post;
  assert(qry.Locate('ID', 2, []));
  qry.Edit;
  qry.FieldByName('DESC').AsString := 'R1';
  qry.Post;
  Con.Commit;
  qry.Close;
  Con.Close;
  

Добавленная строка (ID = 2) не видна внешнему приложению до тех пор, пока Con.Close не будет выполнена, что я нахожу загадочным. После Con.Close вызова внешнее приложение показывает Foo как содержащий

 (1, 'R2')
(2, 'R1')
  

Однако мне не удалось избежать ошибки нарушения ограничений при вызове ApplyUpdates , независимо от любых других изменений, которые я вношу в код, включая добавление вызова ApplyUpdates после первого Post .

Итак, мне кажется, что либо операция ApplyUpdates ошибочна, либо она используется неправильно.

Я упомянул автора FireDAC. Его зовут Дмитрий Арефьев, и он ответил на множество вопросов FD по SO, хотя я не замечал его здесь в последние пару месяцев или около того. Вы можете попытаться привлечь его внимание, разместив сообщение на форуме FireDAC NG EMBA, https://forums.embarcadero.com/forum.jspa?forumID=502 .