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 列插入显式值?
我还没有完全消化它,但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 设置,还有其他方法吗?
我正在尝试使用 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 列插入显式值?
我还没有完全消化它,但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 设置,还有其他方法吗?