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 插入数据库

SqlDataAdapter 用法详解

.Net SqlDataAdapter 在使用列上的默认值插入后检索身份

当 RowState = Modified 时 SQLDataAdapter.Update() 不更新

SQL 命令生成器查询值

SqlDataAdapter#Fill:`SelectCommand.connection` 属性尚未初始化