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中发帖来吸引他的注意力.