我正在尝试使用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
)
答案 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:如果所有问题中只有一半是用这种方式写的...谢谢您提出这个精彩的问题。