【问题标题】:How to apply a cached update FDQuery using Delphi FireDAC with an UNIQUE constraint on the database如何使用 Delphi FireDAC 在数据库上应用 UNIQUE 约束的缓存更新 FDQuery
【发布时间】:2016-10-07 03:27:07
【问题描述】:

当 delta 包含对数据库具有 UNIQUE 约束的字段时,我无法解决缓存更新问题。我有一个具有以下 DDL 架构的数据库(内存中的 SQLite 可用于重现):

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

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

使用 TFDQuery(从 FOO 中选择 *)访问此表,如果执行以下步骤,生成的 delta 将通过 ApplyUpdates 正确应用:

  1. 将记录 ID = 1 更新为 DESC = R2
  2. 使用 DESC = R1 附加新记录 ID = 2

Delta 包括以下内容:

  1. R2
  2. R1

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

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

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

Delta 包括以下内容:

  1. R2
  2. R1

请注意,FireDAC 在两种情况下都会生成相同的 delta,这可以通过 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();

【问题讨论】:

  • 我要回答的是,在事务中,您可以在更新之前删除唯一约束,并在 Applyupdates 之后重新启用它。但是貌似Sqlite不能去掉唯一约束(需要新建一个没有约束的新表,把所有数据都移到那里),所以这不是一个可行的方案。
  • 欢迎来到 StackOverflow 和写得很好的问题。即使使用CachedUpdates := False,我也可以在 D 西雅图复制此内容。与 Sql 脚本一样执行的步骤不会出错。有趣的是,在qrry.Next 之前插入一个额外的ApplyUpdates 后仍然会出现错误。

标签: database sqlite delphi unique firedac


【解决方案1】:

更新 自从写了这个答案的原始版本以来,我做了一些更多的调查,并开始认为 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 的操作要么有缺陷,要么没有正确使用。

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

【讨论】:

    猜你喜欢
    • 2021-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    • 2016-01-29
    • 2016-10-27
    相关资源
    最近更新 更多