可以使用SqlCommandBuilder检索插入的IDENTITY列值(不使用存储过程)?

时间:2008-09-25 22:15:14

标签: .net ado.net

仅供参考:我在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集合被重置为原始集合(丢失了额外添加的参数)。

有谁知道答案???

14 个答案:

答案 0 :(得分:10)

这是我之前遇到的一个问题,当你打电话时,这个错误似乎就是这个问题 da.Update(DS); insert命令的parameters数组被重置为从命令构建器创建的初始列表,它会删除您为标识添加的输出参数。

解决方案是创建一个新的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);

答案 1 :(得分:4)

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

InsertCommand.UpdatedRowSource = UpdateRowSource.Both;

如果您想使用存储过程,那么您就完成了。但是您希望使用原始命令(也就是命令构建器的输出),它不允许a)输出参数或b)返回记录。为什么是这样?好吧a)这就是你的InsertCommand看起来像......

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

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

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

答案 2 :(得分:4)

对我来说有用的是配置MissingSchemaAction:

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

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

祝你好运。

答案 3 :(得分:2)

实际上,这对我有用:

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

干杯。

答案 4 :(得分:2)

我遇到了同样的问题。当我克隆命令生成器生成的命令时,它才刚刚解决。看起来即使你更改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);            
    }

答案 5 :(得分:2)

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

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

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

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

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

  5. 示例:

        '-- 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
    

答案 6 :(得分:2)

我在这里找到了适合我的答案: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);

答案 7 :(得分:1)

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

答案 8 :(得分:1)

问题在于我放置了代码。

在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;
     }
}

答案 9 :(得分:0)

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

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)

答案 10 :(得分:0)

实际上,它更容易,不需要任何自定义命令。在数据集设计器中创建tableadapter时,请指定&#34;刷新数据表&#34;在向导的高级选项中。在此之后,在发出dataadapter.update mydataset之后,您将在数据表中找到使用数据库中的新值填充的标识列。

答案 11 :(得分:0)

我的解决方案:

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();

答案 12 :(得分:0)

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

# Initialising the variables
$Array = @()
$Server = (Get-ComputerInfo).CsName
$measurecmd = (measure-command { 1..1000 | % {random} | sort }).Seconds
$datetime = Get-Date
$sortsec = "1000"
$LogTime = Get-Date -Format "MM-dd-yyyy_hh-mm-ss"
$LogFile = 'C:\Avanthi\PS_script_'+$LogTime+".log"

 # Starting the timer to iterate the output till session is closed
 $timer = [Diagnostics.Stopwatch]::StartNew()
 while ($timer.Elapsed.TotalSeconds -lt 1800) {
    Try {
    
        # Processor utilization
        $Processor = (Get-WmiObject -ComputerName $Server -Class win32_processor -ErrorAction Stop | Measure-Object -Property LoadPercentage -Average | Select-Object Average).Average
 
        # Memory utilization
        $ComputerMemory = Get-WmiObject -ComputerName $Server -Class win32_operatingsystem -ErrorAction Stop
        $Memory = ((($ComputerMemory.TotalVisibleMemorySize - $ComputerMemory.FreePhysicalMemory)*100)/ $ComputerMemory.TotalVisibleMemorySize)
        $RoundMemory = [math]::Round($Memory, 2)
         
        # Creating custom object
        # MachineName Date Time sort100kseconds
        $Object = New-Object PSCustomObject
        $Object | Add-Member -MemberType NoteProperty -Name "Server name" -Value $Server
        $datetime = Get-Date
        $Object | Add-Member -MemberType NoteProperty -Name "Date Time" -Value $datetime
        $Object | Add-Member -MemberType NoteProperty -Name "CPU %" -Value $Processor
        $Object | Add-Member -MemberType NoteProperty -Name "Memory %" -Value $RoundMemory
        $Object | Add-Member -MemberType NoteProperty -Name "Meaure CPU speed" -Value (measure-command { 1..1000 | % {random} | sort }).Seconds
        $Object | Add-Member -MemberType NoteProperty -Name "Sort Seconds" -Value $sortsec
 
        $Object
        $Array += $Object
    }
    
    Catch {
        Write-Host "Something went wrong ($Server): "$_.Exception.Message
        Continue
    }
    
#Final results - Writing to Log file
If ($Array) { 
    $Array | Out-File $LogFile -Force
            }

    
# Sleep for amount of seconds
Start-Sleep -Seconds 60
}

答案 13 :(得分:-1)

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