如何使用Delphi FireDAC在数据库上使用UNIQUE约束应用缓存更新FDQuery



当delta包含对数据库具有UNIQUE约束的字段时,我有一个问题来解决缓存更新。我有一个具有以下DDL模式的数据库(内存中的SQLite可用于复制):

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

初始数据库表包含一条ID = 1且DESC = R1的记录

使用TFDQuery (select * from FOO)访问该表,如果执行以下步骤,生成的增量将正确地应用于ApplyUpdates:

  1. 更新记录ID = 1到DESC = R2
  2. 添加ID = 2的DESC = R1

Delta包括以下内容:

    R2
  1. R1

ApplyUpdates上不会产生错误,因为delta上的第一个操作将是更新。第二个是插入。由于记录1现在是R2,因此可以进行插入,因为在此事务上没有违反惟一约束。

现在,执行以下步骤,将生成完全相同的增量(请查看FDQuery)。Delta属性),但会生成一个UNIQUE约束违反。

  1. 用DESC = TT添加新的临时记录ID = 2
  2. 将第一个记录ID = 1更新为DESC = R2
  3. 将临时记录2 - TT更新为DESC = R1

Delta包括以下内容:

    R2
  1. R1

请注意,FireDAC在两种情况下生成相同的增量,这可以通过FDquery的delta属性查看。

以下步骤可用于重现错误:

File>新建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();

Update自从写这个答案的原始版本以来,我做了一些更多的调查,我开始认为要么是ApplyUpdates等问题,在FireDAC对Sqlite的支持中(至少在西雅图),或者我们没有正确使用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;

添加的行(ID = 2)是不可见的外部应用程序,直到Con.Close已经执行,我发现困惑。一旦调用了Con.Close,外部应用程序显示Foo包含

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

然而,无论我对代码进行任何其他更改,包括在第一个Post之后添加对ApplyUpdates的调用,我都无法避免调用ApplyUpdates的约束违反错误。

所以,在我看来,ApplyUpdates的操作是有缺陷的,或者它没有被正确使用。

我提到FireDAC的作者。他的名字是Dmitry Arefiev,他回答了很多关于SO的问题,尽管我在过去的几个月里没有注意到他。你可以试着在EMBA的FireDAC NG论坛(https://forums.embarcadero.com/forum.jspa?forumID=502)上发帖来引起他的注意。

最新更新