为什么 FireDAC 忽略索引名称?

Why does FireDAC ignore index name?

我正在尝试使用 FireDAC 在 SQL 服务器数据库中创建 table。但是,FireDAC 没有使用我提供的索引名称,而是使用了错误的索引名称,引发了异常并且 table 没有被创建。 我是不是做错了什么?如果没有,是否有解决方法?

请注意,我为 TableName 使用了有效的数据库架构名称 cnf我特别需要在架构中创建 table

最简单的测试用例:

var
  Connection: TFDConnection;
  Table: TFDTable;
begin
  Connection := TFDConnection.Create(nil);
  Table := TFDTable.Create(nil);
  try
    Connection.Params.Add ('DriverID=MSSQL');
    Connection.Params.Add ('OSAuthent=No');
    Connection.Params.Add ('User_Name=sa');
    Connection.Params.Add ('Password=XXXXXX');
    Connection.Params.Add ('Server=DAVE-DELL\MSSQLSERVER2016');
    Connection.Params.Add ('Database=PROJECT_DB');
    Connection.Params.Add ('MARS=No');

    Connection.Open;
    Table.Connection := Connection;

    Table.TableName := 'cnf.TestTable';
    Table.FieldDefs.Add ('TableID', ftAutoInc, 0, true);
    Table.FieldDefs.Add ('Field1', ftInteger, 0, true);
    Table.FieldDefs.Add ('Field2', ftstring, 100, true);

    Table.IndexDefs.Add ('PK_XYZ', 'TableID', [ixPrimary]);   // should use this index name!

    Table.CreateTable (true);

  finally
    Table.Free;
    Connection.Free;
  end;

end;

出现异常:

[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near '.'.

运行 SQL Server Profiler 显示 FireDAC 正在尝试使用以下 SQL 代码创建索引:

ALTER TABLE temp.TestTable ADD CONSTRAINT [cnf].[PK_TestTable] PRIMARY KEY (TableID)

当然,[cnf].[PK_TestTable] 不是 T-SQL 中的有效索引名称,这就是问题的症结所在。

为什么它使用它自己的(错误的)索引名称,而不是我给它的名称?(即 PK_XYZ

Am I doing something wrong?
Why is it using it's own (wrong) index name, instead of the name I gave it?

你似乎做的一切都恰到好处。问题出在生成的 SQL 命令上,因为您已追踪到该命令。 SQL 服务器 doesn't allow schema name in constraint name 使用 ALTER TABLE 添加约束时。以这种方式创建的约束会自动成为相关 table 架构的一部分,但是您稍后应该在引用约束时使用架构名称:

SELECT OBJECT_ID('cnf.PK_XYZ')

现在哪里出了问题? FireDAC 使用 TFDPhysCommandGenerator 及其祖先为特定的 DBMS 生成 SQL 命令。您对 CreateTable 方法的调用导致对 TFDPhysCommandGenerator.GetCreatePrimaryKey 的调用,它负责为主键生成 SQL。它还包含此代码:

sTab := GetFrom;
FConnMeta.DecodeObjName(sTab, rName, nil, [doUnquote]);
rName.FObject := 'PK_' + rName.FObject;
Result := 'ALTER TABLE ' + sTab + ' ADD CONSTRAINT ' +
  FConnMeta.EncodeObjName(rName, nil, [eoQuote, eoNormalize]) + ' PRIMARY KEY (';

此代码的作用是将您的完全限定 table 名称 (sTab) 拆分为 (DecodeObjName) 部分 (rName) 前缀 'PK_' 到 table 名称并将部分 (EncodeObjName) 连接回完全限定名称,然后用作主键的约束名称。现在我们可以清楚地看到命令生成器忽略了您的索引名称并生成了错误的 T-SQL。这可能是错误或只是不受支持的功能。 EMBT 必须对此做出决定。我建议将其报告为错误。

Is there a work-around?

是的,您可以挂钩有问题的方法,也可以在您自己的派生方法中覆盖它 class。这些的实现 none 是微不足道的,由于法律问题,我不打算在这里扩展它,因为我必须复制原始的 FireDAC 代码。

至于语法错误,在 DecodeObjName 之后将这些行添加到 'TFDPhysCommandGenerator.GetCreatePrimaryKey' 实现将解决问题:

rName.FCatalog := '';
rName.FSchema := '';
rName.FBaseObject := '';
rName.FLink := '';

修复约束名称会比这更麻烦,因为该方法仅接收索引列名称作为参数并且没有明显访问原始 IndexDefs 的权限,您可以在其中使用索引名称作为主键约束姓名。从那里获得对索引名称的访问权限还可以让您摆脱索引名称中的 decoding/encoding table 名称。但是,此过程对于 SQL 服务器之外的其他 DMBS 可能是必不可少的。

PS:要是一半的问题都这样写就好了……谢谢你的好问题。