【问题标题】:Possible to retrieve IDENTITY column value on insert using SqlCommandBuilder (without using Stored Proc)?可以使用 SqlCommandBuilder(不使用存储过程)在插入时检索 IDENTITY 列值吗?
【发布时间】:2008-09-25 22:15:14
【问题描述】:

仅供参考:我在 dotnet 3.5 SP1 上运行

我试图在执行更新后(使用 SqlDataAdapter 和 SqlCommandBuilder)将标识列的值检索到我的数据集中。 执行 SqlDataAdapter.Update(myDataset) 后,我希望能够读取 myDataset.tables(0).Rows(0)("ID") 的自动分配值,但它是 System.DBNull(尽管插入了行)。

(注意:我不想显式地写一个新的存储过程来做这个!)

http://forums.asp.net/t/951025.aspx 经常发布的一个方法修改 SqlDataAdapter.InsertCommand 和 UpdatedRowSource,如下所示:

SqlDataAdapter.InsertCommand.CommandText += "; SELECT MyTableID = SCOPE_IDENTITY()"
InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord

显然,这在过去似乎对许多人有用,但对我不起作用。

另一种技术:http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=619031&SiteID=1 对我也不起作用,因为在执行 SqlDataAdapter.Update 之后,SqlDataAdapter.InsertCommand.Parameters 集合被重置为原始集合(丢失了额外添加的参数)。

有谁知道这个问题的答案???

【问题讨论】:

  • 仅供参考:代码项目文章“Using SCOPE_IDENTITY with CommandBuilder and DataAdapter”是帮助我的解决方案。 (我不是文章的作者)。
  • 我知道这是旧的,但在 vs 2015 中这仍然是非常错误的。我遇到了同样的问题,并且已经在使用上述文章中的代码。对我来说,解决方法是将“set ID to SCOPE_IDENTITY()”重命名为“SET SCOPE_ID = SCOPE_IDENTITY()”是的,我知道没有真正的区别,但现在由于某种原因它又可以工作了

标签: .net ado.net


【解决方案1】:

这是我之前遇到过的问题,错误似乎是当您调用时 da.更新(ds); 插入命令的参数数组被重置为从您的命令生成器创建的初始列表,它会删除您为身份添加的输出参数。

解决方案是创建一个新的 dataAdapter 并复制命令,然后使用这个新的来执行您的 da.update(ds);

喜欢

SqlDataAdapter da = new SqlDataAdapter("select Top 0 " + GetTableSelectList(dt) + 
"FROM " + tableName,_sqlConnectString);
SqlCommandBuilder custCB = new SqlCommandBuilder(da);
custCB.QuotePrefix = "[";
custCB.QuoteSuffix = "]";
da.TableMappings.Add("Table", dt.TableName);

da.UpdateCommand = custCB.GetUpdateCommand();
da.InsertCommand = custCB.GetInsertCommand();
da.DeleteCommand = custCB.GetDeleteCommand();

da.InsertCommand.CommandText = String.Concat(da.InsertCommand.CommandText, 
"; SELECT ",GetTableSelectList(dt)," From ", tableName, 
" where ",pKeyName,"=SCOPE_IDENTITY()");

SqlParameter identParam = new SqlParameter("@Identity", SqlDbType.BigInt, 0, pKeyName);
identParam.Direction = ParameterDirection.Output;
da.InsertCommand.Parameters.Add(identParam);

da.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;

//new adaptor for performing the update                     
SqlDataAdapter daAutoNum = new SqlDataAdapter();
daAutoNum.DeleteCommand = da.DeleteCommand;
daAutoNum.InsertCommand = da.InsertCommand;
daAutoNum.UpdateCommand = da.UpdateCommand;

daAutoNum.Update(dt);

【讨论】:

  • 谢谢!这种奇怪的解决方法有效。因此,故事的寓意是,如果您想调整命令构建器中的命令以在数据适配器中使用,那么为命令构建器创建一个单独的数据适配器。在比较了两个“完全相同”的命令的内部结构后,一个有效而另一个无效,我发现内部 IsDirty 对于无效的命令仍然是错误的。
  • 这种情况在NET.Core 2.2中依然存在。如果插入语句是通过 sqlCommandBuilder 生成的,那么在调用 dataAdapter.Update(table/rows) 之后,将恢复使用字符串操作对 SQL 语句的操作。这可以通过观察 CommandText 属性在 sql-profiler 和调试模式下观察到。提示:如果您想在插入后从数据库中检索更多的一列,请在插入语句中使用 OUTPUT INSERTED.*。这将返回完整的行。
【解决方案2】:

可以使用 UpdatedRowSource 属性指示插入命令使用输出参数或第一个返回的记录(或两者)更新插入的记录...

InsertCommand.UpdatedRowSource = UpdateRowSource.Both;

如果您想使用存储过程,那么您就完成了。但是您想使用原始命令(又名命令生成器的输出),它不允许 a) 输出参数或 b) 返回记录。为什么是这样?好吧,a) 这就是您的 InsertCommand 的样子...

INSERT INTO [SomeTable] ([Name]) VALUES (@Name)

无法在命令中输入输出参数。那么b)呢?不幸的是,DataAdapter 通过调用命令 ExecuteNonQuery 方法来执行插入命令。这不会返回任何记录,因此适配器无法更新插入的记录。

所以你需要要么使用存储过程,要么放弃使用 DataAdapter。

【讨论】:

  • “DataAdapter 通过调用命令 ExecuteNonQuery 方法执行插入命令”.....我想这可以解释我所看到的行为,但是其他人是如何获得我发布的示例 teqhniques去工作? UpdatedRowSource 属性的用途是什么???
  • 我看到的所有例子都使用了存储过程。 UpdatedRowSource 适用于存储过程。没有使用存储过程的那个没有使用 DataAdapters。
【解决方案3】:

我在这里找到了适合我的答案:http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-ado-net/4933/Have-soln-but-need-understanding-return-IDENTITY-issue

有效的代码(来自网站 - 归因于 Girish)

//_dataCommand is an instance of SqlDataAdapter
//connection is an instance of ConnectionProvider which has a property called DBConnection of type SqlConnection
//_dataTable is an instance of DataTable

SqlCommandBuilder bldr = new SqlCommandBuilder(_dataCommand);
SqlCommand cmdInsert = new SqlCommand(bldr.GetInsertCommand().CommandText, connection.DBConnection);
cmdInsert.CommandText += ";Select SCOPE_IDENTITY() as id";

SqlParameter[] aParams = new
SqlParameter[bldr.GetInsertCommand().Parameters.Count];
bldr.GetInsertCommand().Parameters.CopyTo(aParams, 0);
bldr.GetInsertCommand().Parameters.Clear();

for(int i=0 ; i < aParams.Length; i++)
{
 cmdInsert.Parameters.Add(aParams[i]);
}

_dataCommand.InsertCommand = cmdInsert;
_dataCommand.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
_dataCommand.Update(_dataTable);

【讨论】:

  • 这成功了!只需确保将“Select SCOPE_IDENTITY() as id”中的 id 替换为表的键字段名称即可。
【解决方案4】:

对我有用的是配置 MissingSchemaAction:

SqlCommandBuilder commandBuilder = new SqlCommandBuilder(myDataAdapter);
myDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;

这让我可以在插入后检索主键(如果它是标识或自动编号)。

祝你好运。

【讨论】:

  • 效果很好,不会弄乱命令。我想知道为什么我在其他任何地方都找不到这个金尖?非常感谢!
【解决方案5】:

我遇到了同样的问题。当我克隆由commandbuilder生成的命令时,它刚刚解决了。看起来即使您更改了 insertcommand 的 commandText,它也会不断获取 Commandbuilder 生成的命令... 这是我用来克隆命令的代码...

        private static void CloneBuilderCommand(System.Data.Common.DbCommand toClone,System.Data.Common.DbCommand repository)
    {
        repository.CommandText = toClone.CommandText;
        //Copying parameters
        if (toClone.Parameters.Count == 0) return;//No parameters to clone? go away!
        System.Data.Common.DbParameter[] parametersArray= new System.Data.Common.DbParameter[toClone.Parameters.Count];
        toClone.Parameters.CopyTo(parametersArray, 0);
        toClone.Parameters.Clear();//Removing association before link to the repository one
        repository.Parameters.AddRange(parametersArray);            
    }

【讨论】:

    【解决方案6】:

    如果您只想 (a) 向表中插入一条记录,(b) 使用 DataSet,并且 (c) 不使用存储过程,那么您可以按照以下步骤操作:

    1. 创建您的 dataAdapter,但在 select 语句中添加 WHERE 1=0,这样您就不必下载整个表 - 性能的可选步骤

    2. 使用范围标识选择语句和输出参数创建自定义 INSERT 语句。

    3. 进行正常处理,填充数据集,添加记录并保存表更新。

    4. 现在应该能够直接从参数中提取身份。

    例子:

        '-- post a new entry and return the column number
        ' get the table stucture
        Dim ds As DataSet = New DataSet()
        Dim da As SqlDataAdapter = New SqlDataAdapter(String.Concat("SELECT * FROM [", fileRegisterSchemaName, "].[", fileRegisterTableName, "] WHERE 1=0"), sqlConnectionString)
        Dim cb As SqlCommandBuilder = New SqlCommandBuilder(da)
    
        ' since we want the identity column back (FileID), we need to write our own INSERT statement
        da.InsertCommand = New SqlCommand(String.Concat("INSERT INTO [", fileRegisterSchemaName, "].[", fileRegisterTableName, "] (FileName, [User], [Date], [Table]) VALUES (@FileName, @User, @Date, @Table); SELECT @FileID = SCOPE_IDENTITY();"))
        da.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
        With da.InsertCommand.Parameters
            .Add("@FileName", SqlDbType.VarChar, 1024, "FileName")
            .Add("@User", SqlDbType.VarChar, 24, "User")
            .Add("@Date", SqlDbType.DateTime, 0, "Date")
            .Add("@Table", SqlDbType.VarChar, 128, "FileName")
            ' allow the @FileID to be returned back to us
            .Add("@FileID", SqlDbType.Int, 0, "FileID")
            .Item("@FileID").Direction = ParameterDirection.Output
        End With
    
        ' copy the table structure from the server and create a reference to the table(dt)
        da.Fill(ds, fileRegisterTableName)
        Dim dt As DataTable = ds.Tables(fileRegisterTableName)
    
        ' add a new record
        Dim dr As DataRow = dt.NewRow()
        dr("FileName") = fileName
        dr("User") = String.Concat(Environment.UserDomainName, "\", Environment.UserName)
        dr("Date") = DateTime.Now()
        dr("Table") = targetTableName
        dt.Rows.Add(dr)
    
        ' save the new record
        da.Update(dt)
    
        ' return the FileID (Identity)
        Return da.InsertCommand.Parameters("@FileID").Value
    

    但是做和这个一样的事情很啰嗦......

    ' add the file record
        Dim sqlCmd As SqlCommand = New SqlCommand(String.Concat("INSERT INTO [", fileRegisterSchemaName, "].[", fileRegisterTableName, "] (FileName, [User], [Date], [Table]) VALUES (@FileName, @User, @Date, @Table); SELECT SCOPE_IDENTITY();"), New SqlConnection(sqlConnectionString))
        With sqlCmd.Parameters
            .AddWithValue("@FileName", fileName)
            .AddWithValue("@User", String.Concat(Environment.UserDomainName, "\", Environment.UserName))
            .AddWithValue("@Date", DateTime.Now())
            .AddWithValue("@Table", targetTableName)
        End With
        sqlCmd.Connection.Open()
        Return sqlCmd.ExecuteScalar
    

    【讨论】:

      【解决方案7】:

      对我来说问题是代码的放置位置。

      在 RowUpdating 事件处理程序中添加代码,如下所示:

      void dataSet_RowUpdating(object sender, SqlRowUpdatingEventArgs e)
      {
          if (e.StatementType == StatementType.Insert)
          {
              e.Command.CommandText += "; SELECT ID = SCOPE_IDENTITY()";
              e.Command.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
           }
      }
      

      【讨论】:

        【解决方案8】:

        实际上,这对我有用:

        SqlDataAdapter.InsertCommand.CommandText += "; SELECT MyTableID = SCOPE_IDENTITY()" InsertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;
        

        干杯。

        【讨论】:

          【解决方案9】:

          如果这些其他方法对您不起作用,.Net 提供的工具 (SqlDataAdapter) 等在灵活性方面并没有真正提供太多其他方法。您通常需要将其提升到一个新的水平并开始手动做一些事情。存储过程将是继续使用 SqlDataAdapter 的一种方式。否则,您需要转移到另一个数据访问工具,因为 .Net 数据库具有限制,因为它们的设计很简单。如果您的模型不符合他们的愿景,您必须在某个点/级别推出自己的代码。

          【讨论】:

            【解决方案10】:

            另一种仅使用命令对象的方式。

            Dim sb As New StringBuilder
            sb.Append(" Insert into ")
            sb.Append(tbl)
            sb.Append(" ")
            sb.Append(cnames)
            sb.Append(" values ")
            sb.Append(cvals)
            sb.Append(";Select SCOPE_IDENTITY() as id") 'add identity selection
            
            Dim sql As String = sb.ToString
            
            Dim cmd As System.Data.Common.DbCommand = connection.CreateCommand
            cmd.Connection = connection
            cmd.CommandText = sql
            cmd.CommandType = CommandType.Text
            cmd.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord
            
            'retrieve the new identity value, and update the object
            Dim dec as decimal = CType(cmd.ExecuteScalar, Decimal)
            

            【讨论】:

              【解决方案11】:

              其实更简单,不需要任何自定义命令。在数据集设计器中创建表适配器时,请在向导的高级选项中指定“刷新数据表”。这样,在您发出 dataadapter.update mydataset 之后,您会发现数据表中的识别列填充了数据库中的新值。

              【讨论】:

                【解决方案12】:

                我的解决方案:

                SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Mytable;" , sqlConnection);
                
                SqlCommandBuilder cb = new SqlCommandBuilder(da);
                
                da.DeleteCommand = (SqlCommand)cb.GetDeleteCommand().Clone();
                da.UpdateCommand = (SqlCommand)cb.GetUpdateCommand().Clone();
                
                SqlCommand insertCmd = (SqlCommand)cb.GetInsertCommand().Clone();
                
                insertCmd.CommandText += ";SET @Id = SCOPE_IDENTITY();";
                insertCmd.Parameters.Add("@Id", SqlDbType.Int, 0, "Id").Direction = ParameterDirection.Output;
                da.InsertCommand = insertCmd;
                da.InsertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;
                
                cb.Dispose();
                

                【讨论】:

                  【解决方案13】:

                  问题是您对InsertCommand所做的更改在使用SqlDataAdapter.Update(DataSet)方法时没有考虑在内. 完美的解决方案是克隆 CommandBuilder 生成的 InsertCommand

                  SqlCommandBuilder cb = new SqlCommandBuilder(da);
                  cb.ConflictOption = ConflictOption.OverwriteChanges;
                  
                  da.InsertCommand = cb.GetInsertCommand().Clone();
                  da.InsertCommand.CommandText += "; SELECT idTable = SCOPE_IDENTITY()";
                  da.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
                  
                  da.Update(dt);
                  

                  【讨论】:

                    【解决方案14】:

                    您是否考虑过改用 LINQ?我知道这并不能解决您的实际问题,但是如果您使用的是 .NET 3.5,您真的应该尝试使用 LINQ。事实上,随着 Code First EntityFramework 的出现,我认为您可以轻松地选择 LINQ to SQL 或 EF 作为滚动您自己的 DAL 的相对轻量级的替代方案。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-08-19
                      • 2011-03-18
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-07-03
                      • 2011-07-20
                      相关资源
                      最近更新 更多