FireDAC、阵列 DML、SQL 服务器和 IDENTITY_INSERT

FireDAC, Array DML, SQL Server, and IDENTITY_INSERT

我正在尝试使用 Firedac 阵列 DML 功能将数据导出到 SQL 服务器。在目标 table 中,有一个 IDENTITY 列,我需要给它一个明确的值。

但我的查询失败并显示以下错误消息:

SQL state: 23000. Native code: 544. Message: [Microsoft][SQL Server Native Client 11.0][SQL Server]Cannot insert explicit value for identity column in table 'dic_cities' when IDENTITY_INSERT is set to OFF.

目的地table定义如下:

CREATE TABLE dbo.dic_cities (
  id int IDENTITY(1,1) PRIMARY KEY,
  city_name varchar(50) NOT NULL
)

我的代码:

MyFDQuery.Connection.ExecSQL('SET IDENTITY_INSERT dbo.dic_cities ON');  

MyFDQuery.SQL.Text := 'INSERT INTO dbo.dic_cities (id, city_name) VALUES (:id, :city_name)';  

//Populating parameters and preparing the query
{...}

//Execute Array DML query with batch size of 100 
MyFDQuery.Execute(100, 0);  

//Finally, set IDENTITY_INSERT off for the destination table
MyFDQuery.Connection.ExecSQL('SET IDENTITY_INSERT dbo.dic_cities OFF');  

我必须注意,当我使用带参数的常规 TFDQuery 时(即不使用 Array DML 功能时)一切正常。但它对数组 DML 失败。

多年来,我也以类似上述代码的方式将 Array DML 用于其他几个 DBMS,并取得了成功。

那么,如何使用 Array DML 功能向 SQL Server IDENTITY 列插入显式值?

更新#2: 参见 https://social.msdn.microsoft.com/Forums/sqlserver/en-US/e652377d-0607-45ca-b4a0-274361bff85a/how-to-set-identityinsert-in-dynamic-sql?forum=transactsql

我还没有完全消化它,但OP的问题似乎很相似。

我尝试构建 SQL 以使用 EXEC

  FDQuery1.SQL.Text := 'EXEC (' + ''''+ sSetIIOn + ';' + sInsert + ';' + sSetIIOff + '''' + ')';

以避免使用 sp_executesql,但不幸的是 FD 无法正确解析 SQL,因此在设置参数时会产生 "argument out of range" 错误。
更新:越来越好奇...

以下代码在 SS2014 上执行时没有错误,并插入了预期的 10 行:

const
  sEmptyTable = 'delete from dbo.identtest';
  sSetIIOn = 'set identity_insert dbo.identtest ON';
  sSetIIOff = 'set identity_insert dbo.identtest OFF';
  sSelect = 'select * from dbo.identtest';
  sInsert = 'insert dbo.identtest (ID, Name) values(%d, %s)';

procedure TForm2.TestIdentityInsert;
var
  i : Integer;
  S : String;
begin
  FDQuery1.ExecSql(sEmptyTable);
  FDQuery1.ExecSql(sSetIIOn);

  for i := 1 to 10 do begin
    S := Format(sInsert, [i, '''Name' + IntToStr(i) + '''']);
    FDQuery1.ExecSQL(S);
  end;

  FDQuery1.ExecSql(sSetIIOff);
  FDQuery1.Sql.Text := sSelect;
  FDQuery1.Open;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  TestIdentityInsert;
end;

但是,将for循环替换为

  FDQuery1.SQL.Text := sSetIIOn + ';' + sInsert + ';' + sSetIIOff;

  FDQuery1.Params.ArraySize := Rows;
  for i := 0 to Rows - 1 do begin
    FDQuery1.Params[0].AsIntegers[i] := i;
    FDQuery1.Params[1].AsStrings[i] := 'Name' + IntToStr(i);
  end;

产生你引用的异常。我已经使用 SSMS Profiler 验证了发送到服务器的 SQL 似乎是正确的(而不是 f.i。有时会被 MDac 层破坏):

exec sp_executesql N'set identity_insert dbo.identtest ON;insert dbo.identtest values(@P1, @P2);set identity_insert dbo.identtest OFF',N'@P1 int,@P2 nvarchar(4000)',0,N'Name0' [etc, repeated 9 times]

所以问题似乎是为什么不使用 sp_executesql 尊重 Identity_Insert 设置,还有其他方法吗?