为什么FireDAC忽略索引名称?

时间:2019-07-01 19:09:55

标签: sql-server delphi firedac

我正在尝试使用FireDAC在SQL Server数据库中创建一个表。但是,FireDAC并未使用我提供的索引名,而是使用了错误的索引名,引发了异常,并且未创建表。 我做错什么了吗?如果没有,是否有解决方法?

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

最简单的测试用例:

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中不是有效的索引名,这是问题的症结所在。

  • 如果我删除行Table.IndexDefs.Add,则表创建正确,但是没有索引。
  • 如果我用以下内容替换该行,则会出现相同的问题:

    with Table.IndexDefs.AddIndexDef do begin
      Name := 'PK_XYZ';
      Options := [ixPrimary];
      Fields := 'TableID';
    end;
    
  • 如果我将设置表名替换为以下内容,则会出现相同的问题:

    Table.TableName := 'TestTable';
    Table.SchemaName := 'cnf';
    

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

  • Embarcadero®Delphi 10.1 Berlin版本24.0.25048.9432
  • SQL Server 2016(SP2-CU4)-13.0.5233.0(X64)

1 个答案:

答案 0 :(得分:4)

  

我做错什么了吗?
  为什么使用它自己的(错误的)索引名称而不是我给它命名的名称?

您似乎在做所有正确的事情。问题出在您所跟踪的生成的SQL命令中。使用ALTER TABLE添加约束时,SQL Server doesn't allow schema name in constraint name。通过这种方式创建的约束将自动成为相关表的架构的一部分,但是您以后应在引用约束时使用架构名称:

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 (';

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

  

有解决方法吗?

是的,您可以钩住有问题的方法,也可以在自己的派生类中覆盖它。实施这些都不是一件容易的事,由于法律问题,我不会在这里扩展它,因为我将不得不复制原始的FireDAC代码。

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

DecodeObjName

固定约束名称将比此更为麻烦,因为该方法仅接收索引列名称作为参数,并且没有明显的访问原始rName.FCatalog := ''; rName.FSchema := ''; rName.FBaseObject := ''; rName.FLink := ''; 的权限,在原始IndexDefs中,您可以仅使用索引名称作为主键约束名称。从那里访问索引名也将使您摆脱将表名解码/编码成索引名的麻烦。但是,此过程对于SQL Server以外的其他DMBS可能是必不可少的。

PS:如果所有问题中只有一半是用这种方式写的...谢谢您提出这个精彩的问题。