SqlDataAdapter 插入后无法获取行
Posted
技术标签:
【中文标题】SqlDataAdapter 插入后无法获取行【英文标题】:SqlDataAdapter fails to get row after insert 【发布时间】:2018-10-15 15:48:40 【问题描述】:我正在尝试使用 SqlDataAdapter 插入一行,然后立即获取同一行。我遵循了this post 上的建议并使用了 SCOPE_IDENTITY,但它不起作用。这是我的代码...
using (var conn = _db.OpenConnection())
var sqlQuery = "SELECT * FROM RotationItem WHERE [RotationItemId] = SCOPE_IDENTITY()";
var adapter = new SqlDataAdapter(sqlQuery, conn);
var builder = new SqlCommandBuilder(adapter);
var dataSet = new DataSet();
adapter.Fill(dataSet);
Debug.WriteLine("First fill, rows " + dataSet.Tables[0].Rows.Count);
var table = dataSet.Tables[0];
table.Rows.InsertAt(table.NewRow(), 0);
CopyJsonToRow(table, 0, item);
if (adapter.Update(dataSet) != 1)
throw new InvalidOperationException("Insert failed");
// After insert, fetch the new record
dataSet.Clear();
adapter.Fill(dataSet);
Debug.WriteLine("Second fill, rows " + dataSet.Tables[0].Rows.Count);
我的输出是:
第一次填充,第 0 行 第二次填充,第 0 行为什么第二次填充失败?它不应该得到我刚刚插入的行吗?!
我没有使用任何交易。表的定义如下...
CREATE TABLE [dbo].[RotationItem] (
[RotationItemId] INT NOT NULL IDENTITY(1,1),
[RotationScheduleId] INT NOT NULL,
[QuestionnaireId] INT NOT NULL,
[Order] INT NOT NULL,
PRIMARY KEY CLUSTERED ([RotationItemId] ASC)
);
【问题讨论】:
SCOPE_IDENTITY 在同一个会话上工作。我怀疑您的插入可能在不同的会话中,因此后续的 SCOPE_IDENTITY 将没有任何返回。您可以在执行此操作时运行探查器跟踪来进行验证,并查看 session_id 值返回什么。更好的方法是使用存储过程插入行并将 SCOPE_IDENTITY 值作为输出参数返回 @MartinCairney 我认为您是正确的,因为当我将查询更改为使用IDENT_CURRENT('RotationItem')
而不是 SCOPE_IDENTITY()
时,它就可以工作了!!但是,如果许多插入同时在不同的线程上运行,它通常会给出错误的答案。太糟糕了。
这里是关于使用 ADO.NET 检索身份值的文档页面:docs.microsoft.com/en-us/dotnet/framework/data/adonet/…
@DavidBrowne-Microsoft 谢谢,但该页面对我没有帮助,因为我使用的是SqlCommandBuilder
。您认为使用 SqlCommandBuilder 是不可能的吗?我尝试将“;SELECT SCOPE_IDENTITY()”附加到builder.GetInsertCommand().CommandText
的末尾,但它没有效果。我尝试设置adapter.InsertCommand
,但没有效果。创建 SqlCommandBuilder 后,我似乎根本无法自定义它。
【参考方案1】:
这是我在玩了几个小时后发现的。
您不应该使用 IDENT_CURRENT,因为它非常不可靠 您不能在SqlDataAdapter.Update
之后使用SCOPE_IDENTITY
(如在OP 中),因为它关闭了范围。
当您调用new SqlCommandBuilder(adapter)
时,它会向您的适配器隐藏巫术,使其无法自定义。特别是,您对 InsertCommand
所做的任何更改都会被忽略。
这是我巧妙的解决方法(有些人可能会说是可怕的 hack)。这很有效,即使我与许多并发客户端一起敲击它。
using (var conn = _db.OpenConnection())
var sqlQuery = "SELECT * FROM RotationItem WHERE [RotationItemId] = SCOPE_IDENTITY()";
// Create a dummy adapter, so we can generate an appropriate INSERT statement
var dummy = new SqlDataAdapter(sqlQuery, conn);
var insert = new SqlCommandBuilder(dummy).GetInsertCommand();
// Append the SELECT to the end of the INSERT command,
// and set a flag to copy the result back into the dataSet table row
insert.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
insert.CommandText += ";" + sqlQuery;
// Now proceed as usual...
var adapter = new SqlDataAdapter(sqlQuery, conn);
adapter.InsertCommand = insert;
var dataSet = new DataSet();
adapter.Fill(dataSet);
Debug.WriteLine("First fill, rows " + dataSet.Tables[0].Rows.Count);
var table = dataSet.Tables[0];
table.Rows.InsertAt(table.NewRow(), 0);
CopyJsonToRow(table, 0, item);
if (adapter.Update(dataSet) != 1)
throw new InvalidOperationException("Insert failed");
// After insert, the table AUTOMATICALLY has the new row ID (and any other computed columns)
Debug.WriteLine("The new id is " + table.Rows[0].ItemArray[0]);
这个hack的负面点是我需要创建两个SqlDataAdapters
,一个是一个虚拟的,只是提供给SqlCommandBuilder
。此外,使用字符串连接将两个 SQL 查询粘合在一起是非常狡猾的。由于注入问题,我不确定内部安全审计是否允许我这样做。谁有更好的主意?
【讨论】:
以上是关于SqlDataAdapter 插入后无法获取行的主要内容,如果未能解决你的问题,请参考以下文章
无法在 c# 中使用 SqlDataAdapter 和 MSSqlServer 插入数据库
.Net SqlDataAdapter 在使用列上的默认值插入后检索身份