create table FOO ( ID integer primary key, DESC char(2) UNIQUE );
初始数据库表包含一个ID = 1且DESC = R1的记录
使用TFDQuery(从FOO中选择*)访问此表,如果执行以下步骤,将使用ApplyUpdates正确应用生成的增量:
>将记录ID = 1更新为DESC = R2
>使用DESC = R1附加新记录ID = 2
Delta包括以下内容:
> R2
> R1
ApplyUpdates不会生成错误,因为delta上的第一个操作将是更新.第二个是插入.由于记录1现在是R2,因此可以完成插入,因为在此事务中没有违反唯一约束.
现在,执行以下步骤将生成完全相同的delta(查看FDQuery.Delta属性),但将生成UNIQUE约束违例.
>使用DESC = TT附加新的临时记录ID = 2
>将第一个记录ID = 1更新为DESC = R2
>更新临时记录2 – TT到DESC = R1
Delta包括以下内容:
> R2
> R1
请注意,FireDAC在两种情况下都会生成相同的增量,可以通过FDquery的Delta属性查看.
此步骤可用于重现错误:
文件>新的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 Manager插件)监视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;
在Con.Close执行之后,外部应用程序看不到添加的行(ID = 2),我觉得这很令人费解.一旦调用了Con.Close,外部应用程序就会将Foo显示为包含
(1, 'R2') (2, 'R1')
但是,如果我调用ApplyUpdates,我无法避免约束违规错误,无论我对代码做出任何其他更改,包括在第一个Post之后添加对ApplyUpdates的调用.
所以,在我看来,ApplyUpdates的操作有缺陷或者没有正确使用.
我提到了FireDAC的作者.他的名字是德米特里·阿雷菲耶夫(Dmitry Arefiev),他在SO上回答了很多关于他的问题,尽管在过去的几个月左右我没有在这里注意到他.您可以尝试通过在EMBA的FireDAC NG论坛https://forums.embarcadero.com/forum.jspa?forumID=502中发帖来吸引他的注意力.
