#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 до DESC = R2
- Добавьте новую запись ID = 2 с помощью DESC = R1
Delta включает в себя следующее:
- R2
- R1
При ApplyUpdates ошибка не будет сгенерирована, поскольку первой операцией над delta будет обновление. Вторым будет вставка. Поскольку запись 1 теперь равна R2, вставка может быть выполнена, поскольку в этой транзакции нет нарушения уникального ограничения.
Теперь, выполнив следующие шаги, будет сгенерирована точно такая же дельта (посмотрите на FDQuery.Свойство Delta), но будет сгенерировано УНИКАЛЬНОЕ нарушение ограничения.
- Добавьте новую временную запись ID = 2 с помощью DESC = TT
- Обновите первую запись ID = 1 до DESC = R2
- Обновите временную запись 2 — TT до DESC = R1
Delta включает в себя следующее:
- R2
- 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 .