当增量包含对数据库具有 UNIQUE 约束的字段时,我无法解决缓存更新问题。我有一个具有以下 DDL 模式的数据库(内存中的 SQLite 可用于重现):
create table FOO
(
ID integer primary key,
DESC char(2) UNIQUE
);
初始数据库表包含一条 ID = 1 和 DESC = R1 的记录
使用 TFDQuery(select * from FOO)访问此表,如果执行以下步骤,生成的增量将正确应用 ApplyUpdates:
- 将记录 ID = 1 更新为 DESC = R2
- 附加一个新记录 ID = 2,DESC = R1
Delta 包括以下内容:
- R2
- R1
ApplyUpdates 不会产生错误,因为对 delta 的第一个操作将是更新。第二个将是一个插入。由于记录 1 现在是 R2,因此可以插入,因为没有违反此事务的唯一约束。
现在,执行以下步骤,将生成完全相同的增量(查看 FDQuery.Delta 属性),但将生成 UNIQUE 约束违规。
- 附加一个新的临时记录 ID = 2,DESC = TT
- 将第一条记录ID=1更新为DESC=R2
- 更新临时记录 2 - TT 为 DESC = R1
Delta 包括以下内容:
- R2
- R1
请注意,FireDAC 在两种情况下生成相同的增量,这可以通过 FDquery 的增量属性查看。
此步骤可用于重现错误:
文件 > 新的 VCL 表格申请;在表单上删除 FDConnection 和 FDQuery;将 FDConnection 设置为使用 SQLite 驱动程序(在内存数据库中使用);在表单上放置两个按钮,一个用于重现正确的行为,另一个用于重现错误,如下所示:
确定按钮:
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();
最佳答案
更新 自从写了这个答案的原始版本,我做了更多的调查,我开始认为在 FireDAC 对 Sqlite 的支持中 ApplyUpdates 等有问题(在西雅图,至少),或者我们没有正确使用 FD 组件。它需要 FireDAC 的作者(他是这里的贡献者)来说明它是什么。
暂时搁置 ApplyUpdates
业务,您的代码还有许多其他问题,即您的数据集导航假设了 qry
中行的顺序> 及其 Fields
的编号。
我使用的测试用例是从包含单行的 Foo 表开始(在执行应用程序之前)
(1, 'R1')
然后,我执行以下 Delphi 代码,同时使用外部应用程序(FireFox 的 Sqlite 管理器插件)监视 Foo 的内容。代码执行时没有在应用程序中报告错误,但请注意它不调用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 的作者。他的名字是 Dmitry Arefiev,他已经回答了很多关于 SO 的 FD 问题,尽管在过去几个月左右的时间里我没有注意到他在这里。您可以尝试通过在 EMBA 的 FireDAC NG 论坛发帖来引起他的注意,https://forums.embarcadero.com/forum.jspa?forumID=502 .
关于database - 如何使用 Delphi FireDAC 对数据库应用具有 UNIQUE 约束的缓存更新 FDQuery,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39908898/