Retrieving Identity or Autonumber Values

You can set a column in a DataTable to be an auto-incrementing primary key in order to ensure a unique value for each row in the table. However, you may have multiple clients for your application, and each of those clients can be working with a separate instance of the DataTable. In this case, you might end up with duplicate values between the separate instances of the DataTable. Because all your clients are working with a single data source, you can resolve this conflict by letting the data source define the auto-incremented value. To accomplish this you use Identity fields in Microsoft SQL Server, or Autonumber fields in Microsoft Access.

Using the data source to populate an Identity or Autonumber column for a new row added to a DataSet creates a unique situation because the DataSet has no direct connection to the data source. As a result, the DataSet is unaware of any values generated automatically by the data source. However, with a data source that can create stored procedures with output parameters, such as Microsoft SQL Server, you can specify the automatically generated values, such as a new identity value, as an output parameter and use the DataAdapter to map that value back to the column in the DataSet.

Your data source may not support stored procedures with output parameters. In this case you may be able to use the RowUpdated event to retrieve an automatically generated value and place it in the inserted or updated row in the DataSet. This section includes a sample that shows how, with Microsoft Access 2000 or later, and using the Jet 4.0 OLE DB Provider, you can add code to the RowUpdated event to determine if an insert has occurred and to retrieve the auto-incremented value and store it in the currently updated row.

The following stored procedure and code example show how to map the auto-incremented identity value from a Microsoft SQL Server table back to its corresponding column in a row added to a table in a DataSet. The stored procedure is used to insert a new row into the Categories table of the Northwind database and to return the identity value returned from SCOPE_IDENTITY() as an output parameter.

CREATE PROCEDURE InsertCategory
  @CategoryName nchar(15),
  @Identity int OUT
AS
INSERT INTO Categories (CategoryName) VALUES(@CategoryName)
SET @Identity = SCOPE_IDENTITY()

The InsertCategory stored procedure can then be specified as the source of the DataAdapter.InsertCommand. A parameter is created to receive the identity output parameter. That parameter has a Direction of ParameterDirection.Output, and has a SourceColumn specified as the CategoryID column of the local Categories table in the DataSet. When the InsertCommand is processed for an added row, the auto-incremented identity value is returned as this output parameter and is placed in the CategoryID column of the current row.

The following code example shows how to return the auto-incremented value as the output parameter and specify it as the source value for the CategoryID column in the DataSet.

Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")

Dim catDA As SqlDataAdapter = New SqlDataAdapter("SELECT CategoryID, CategoryName FROM Categories", nwindConn)

catDA.InsertCommand = New SqlCommand("InsertCategory", nwindConn)
catDA.InsertCommand.CommandType = CommandType.StoredProcedure

catDA.InsertCommand.Parameters.Add("@CategoryName", SqlDbType.NChar, 15, "CategoryName")

Dim myParm As SqlParameter = catDA.InsertCommand.Parameters.Add("@Identity", SqlDbType.Int, 0, "CategoryID")
myParm.Direction = ParameterDirection.Output

nwindConn.Open()

Dim catDS As DataSet = New DataSet
catDA.Fill(catDS, "Categories")

Dim newRow As DataRow = catDS.Tables("Categories").NewRow()
newRow("CategoryName") = "New Category"
catDS.Tables("Categories").Rows.Add(newRow)

catDA.Update(catDS, "Categories")

nwindConn.Close()
[C#]
SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");

SqlDataAdapter catDA = new SqlDataAdapter("SELECT CategoryID, CategoryName FROM Categories", nwindConn);

catDA.InsertCommand = new SqlCommand("InsertCategory", nwindConn);
catDA.InsertCommand.CommandType = CommandType.StoredProcedure;

catDA.InsertCommand.Parameters.Add("@CategoryName", SqlDbType.NChar, 15, "CategoryName");

SqlParameter myParm = catDA.InsertCommand.Parameters.Add("@Identity", SqlDbType.Int, 0, "CategoryID");
myParm.Direction = ParameterDirection.Output;

nwindConn.Open();

DataSet catDS = new DataSet();
catDA.Fill(catDS, "Categories");

DataRow newRow = catDS.Tables["Categories"].NewRow();
newRow["CategoryName"] = "New Category";
catDS.Tables["Categories"].Rows.Add(newRow);

catDA.Update(catDS, "Categories");

nwindConn.Close();

Microsoft Access does not support stored procedures or batch command processing, so it is not possible to map an output parameter to the source column in the table in the preceding example. However, Microsoft Access 2000 or later does support the @@IDENTITY property to retrieve the value of an Autonumber field after an INSERT. Using the RowUpdated event, you can determine if an INSERT has occurred, retrieve the latest @@IDENTITY value, and place that in the identity column of the local table in the DataSet.

The following code example shows how to insert a new value into the Categories table of the Microsoft Access 2000 Northwind database. The example uses the RowUpdated event to fill in the Autonumber values generated by the Jet engine and the Access database when a record is inserted into the Categories table. Note that this will only work with the Jet 4.0 OLE DB provider and Microsoft Access 2000 or later.

Imports System
Imports System.Data
Imports System.Data.OleDb
Imports Microsoft.VisualBasic

Public class Sample

  Shared nwindConn As OleDbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" & _
                                              "Data Source=c:\Program Files\Microsoft Office\Office\Samples\northwind.mdb;")

  Public Shared Sub Main() 

    ' Use the DataAdapter to fill and update the DataSet.
    Dim catDA As OleDbDataAdapter = New OleDbDataAdapter("SELECT CategoryID, CategoryName FROM Categories ORDER BY CategoryID", nwindConn)

    catDA.InsertCommand = New OleDbCommand("INSERT INTO Categories (CategoryName) Values(?)", nwindConn)
    catDA.InsertCommand.CommandType = CommandType.Text

    catDA.InsertCommand.Parameters.Add("@CategoryName", OleDbType.Char, 15, "CategoryName")

    nwindConn.Open()
 
    ' Fill the DataSet.
    Dim catDS As DataSet = New DataSet
    catDA.Fill(catDS, "Categories")

    ' Add a new row.
    Dim newRow As DataRow = catDS.Tables("Categories").NewRow()
    newRow("CategoryName") = "New Category"
    catDS.Tables("Categories").Rows.Add(newRow)

    ' Include an event to fill in the Autonumber value.
    AddHandler catDA.RowUpdated, New OleDbRowUpdatedEventHandler(AddressOf OnRowUpdated)

    ' Update the DataSet.
    catDA.Update(catDS, "Categories")

    nwindConn.Close()
  End Sub

  Private Shared Sub OnRowUpdated(sender As Object, args As OleDbRowUpdatedEventArgs)
    ' Include a variable and a command to retrieve the identity value from the Access database.
    Dim newID As Integer = 0
    Dim idCMD As OleDbCommand = New OleDbCommand("SELECT @@IDENTITY", nwindConn)

    If args.StatementType = StatementType.Insert
      ' Retrieve the identity value and store it in the CategoryID column.
      newID = CInt(idCMD.ExecuteScalar())
      args.Row("CategoryID") = newID
    End If
  End Sub
End Class
[C#]
using System;
using System.Data;
using System.Data.OleDb;

public class Sample
{
  static OleDbConnection nwindConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" +
                                                  @"Data Source=c:\Program Files\Microsoft Office\Office\Samples\northwind.mdb;");

  public static void Main() 
  {
    // Use the DataAdapter to fill and update the DataSet.
    OleDbDataAdapter catDA = new OleDbDataAdapter("SELECT CategoryID, CategoryName FROM Categories ORDER BY CategoryID", nwindConn);

    catDA.InsertCommand = new OleDbCommand("INSERT INTO Categories (CategoryName) Values(?)", nwindConn);
    catDA.InsertCommand.CommandType = CommandType.Text;

    catDA.InsertCommand.Parameters.Add("@CategoryName", OleDbType.Char, 15, "CategoryName");

    nwindConn.Open();
 
    // Fill the DataSet.
    DataSet catDS = new DataSet();
    catDA.Fill(catDS, "Categories");

    // Add a new row.
    DataRow newRow = catDS.Tables["Categories"].NewRow();
    newRow["CategoryName"] = "New Category";
    catDS.Tables["Categories"].Rows.Add(newRow);

    // Include an event to fill in the Autonumber value.
    catDA.RowUpdated += new OleDbRowUpdatedEventHandler(OnRowUpdated);

    // Update the DataSet.
    catDA.Update(catDS, "Categories");

    nwindConn.Close();
  }

  protected static void OnRowUpdated(object sender, OleDbRowUpdatedEventArgs args)
  {
    // Include a variable and a command to retrieve the identity value from the Access database.
    int newID = 0;
    OleDbCommand idCMD = new OleDbCommand("SELECT @@IDENTITY", nwindConn);

    if (args.StatementType == StatementType.Insert)
    {
      // Retrieve the identity value and store it in the CategoryID column.
      newID = (int)idCMD.ExecuteScalar();
      args.Row["CategoryID"] = newID;
    }
  }
}

See Also

Sample ADO.NET Scenarios | Accessing Data with ADO.NET | Using .NET Framework Data Providers to Access Data