Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to retrieve IDENTITY column value on insert using SqlCommandBuilder (without using Stored Proc)?

Tags:

.net

ado.net

FYI: I am running on dotnet 3.5 SP1

I am trying to retrieve the value of an identity column into my dataset after performing an update (using a SqlDataAdapter and SqlCommandBuilder). After performing SqlDataAdapter.Update(myDataset), I want to be able to read the auto-assigned value of myDataset.tables(0).Rows(0)("ID"), but it is System.DBNull (despite the fact that the row was inserted).

(Note: I do not want to explicitly write a new stored procedure to do this!)

One method often posted http://forums.asp.net/t/951025.aspx modifies the SqlDataAdapter.InsertCommand and UpdatedRowSource like so:

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

Apparently, this seemed to work for many people in the past, but does not work for me.

Another technique: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=619031&SiteID=1 doesn't work for me either, as after executing the SqlDataAdapter.Update, the SqlDataAdapter.InsertCommand.Parameters collection is reset to the original (losing the additional added parameter).

Does anyone know the answer to this???

like image 566
tbone Avatar asked Sep 25 '08 22:09

tbone


5 Answers

This is a problem that I've run into before, the bug seems to be that when you call da.Update(ds); the parameters array of the insert command gets reset to the inital list that was created form your command builder, it removes your added output parameters for the identity.

The solution is to create a new dataAdapter and copy in the commands, then use this new one to do your da.update(ds);

like

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);
like image 155
Ray Fitzharris Avatar answered Nov 14 '22 09:11

Ray Fitzharris


The insert command can be instructed to update the inserted record using either output parameters or the first returned record (or both) using the UpdatedRowSource property...

InsertCommand.UpdatedRowSource = UpdateRowSource.Both;

If you wanted to use a stored procedure, you'd be done. But you want to use a raw command (aka the output of the command builder), which doesn't allow for either a) output parameters or b) returning a record. Why is this? Well for a) this is what your InsertCommand will look like...

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

There's no way to enter an output parameter in the command. So what about b)? Unfortunately, the DataAdapter executes the Insert command by calling the commands ExecuteNonQuery method. This does not return any records, so there is no way for the adapter to update the inserted record.

So you need to either use a stored proc, or give up on using the DataAdapter.

like image 29
dbugger Avatar answered Nov 14 '22 07:11

dbugger


I found the answer that works for me here: http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-ado-net/4933/Have-soln-but-need-understanding-return-IDENTITY-issue

Code that worked (from the site - attributed to 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);
like image 4
Mladen Mihajlovic Avatar answered Nov 14 '22 08:11

Mladen Mihajlovic


What works for me is configuring a MissingSchemaAction:

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

This lets me retrieve the primary key (if it is an identity, or autonumber) after an insert.

Good luck.

like image 4
Rob Vermeulen Avatar answered Nov 14 '22 07:11

Rob Vermeulen


I had the same problem. It was just solved when I cloned the command generated by the commandbuilder. It looks that even when you change the commandText of the insertcommand, it keeps getting the command generated by the Commandbuilder... Here it's the code that I used to clone the command...

        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);            
    }
like image 2
Pedrao Avatar answered Nov 14 '22 09:11

Pedrao