database - 如何使用 Delphi FireDAC 对数据库应用具有 UNIQUE 约束的缓存更新 FDQuery

标签 database sqlite delphi unique firedac

当增量包含对数据库具有 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 包括以下内容:

  1. R2
  2. R1

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

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

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

Delta 包括以下内容:

  1. R2
  2. 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/

相关文章:

php - 存储插入正确的数据和一个空行 Laravel 5.7

MySQL 在同一行中选择不同的值

delphi - 为什么我不能在Delphi程序中获取AOL验证码图像?

delphi - 使用 poPropagateChanges 和 poFetchDetailsOnDemand 避免 ClientDataSets 中的内存损坏?

php - 如何从 MySQL 查询创建数组?

database - 如果与不同的实体共享标签,如何实现标签?

python - 加快Python/SQLite更新速度?

c# - 在 C# 中加密 SQLite 数据库

mysql - sqlite3 的迁移中断,但在 mysql2 中运行

delphi - Delphi Web脚本:执行脚本后访问变量