Share via


Recupero dei valori Identity o Autonumber (ADO.NET)

Per chiave primaria di un database relazionale si intende una colonna o una combinazione di colonne che contengono sempre valori univoci. Se si conosce il valore della chiave primaria, è possibile individuare la riga che lo contiene. Nei motori dei database relazionali, ad esempio SQL Server, Oracle e Microsoft Access/Jet, è supportata la creazione di colonne a incremento automatico che è possibile impostare come chiavi primarie. Tali valori vengono generati dal server quando si aggiungono righe a una tabella. In SQL Server viene impostata la proprietà Identity di una colonna, in Oracle viene creata una sequenza, mentre in Microsoft Access viene creata una colonna Contatore.

È inoltre possibile utilizzare un oggetto DataColumn per generare valori a incremento automatico impostando la proprietà AutoIncrement su true. Se, tuttavia, più applicazioni client generano valori a incremento automatico in modo indipendente l'una dall'altra, è possibile che in istanze distinte di un oggetto DataTable siano presenti valori duplicati. Se invece i valori a incremento automatico vengono generati dal server, è possibile eliminare possibili conflitti in quanto si consente a ogni utente di recuperare il valore generato per ciascuna riga inserita.

Durante una chiamata al metodo Update di un DataAdapter, il database può restituire dati all'applicazione ADO.NET sotto forma di parametri di output o di primo record restituito del set di risultati di un'istruzione SELECT eseguita nello stesso batch dell'istruzione INSERT. ADO.NET può recuperare questi valori e aggiornare le colonne corrispondenti nell'oggetto DataRow da aggiornare.

Alcuni motori di database, ad esempio il motore di database Jet di Microsoft Access, non supportano parametri di output e non sono in grado di elaborare più istruzioni in un unico batch. Quando si utilizza il motore di database Jet, è possibile recuperare il nuovo valore del campo Contatore generato per una riga inserita eseguendo un comando SELECT distinto in un gestore eventi per l'evento RowUpdated di DataAdapter.

NotaNota

In alternativa a un valore a incremento automatico, è possibile utilizzare il metodo NewGuid di un oggetto Guid per generare nel computer client un GUID che può essere copiato nel server a ogni nuova riga inserita.Il metodo NewGuid genera un valore binario a 16 byte per la cui creazione viene utilizzato un algoritmo che consente con buona probabilità di evitare valori duplicati.In un database SQL Server un GUID viene archiviato in una colonna uniqueidentifier che SQL Server è in grado di generare automaticamente utilizzando la funzione Transact-SQL NEWID().L'utilizzo di un GUID come chiave primaria può influire negativamente sulle prestazioni.In SQL Server 2005 è stato introdotto il supporto per la funzione NEWSEQUENTIALID() che genera un GUID sequenziale di cui non viene garantita l'univocità a livello globale ma che può essere indicizzato in modo più efficiente.

Recupero dei valori delle colonne Identity di SQL Server

Quando si utilizza Microsoft SQL Server, è possibile creare una stored procedure con un parametro di output in modo che venga restituito il valore della colonna Identity per una riga inserita. Nella tabella seguente sono descritte le tre funzioni Transact-SQL disponibili in SQL Server che è possibile utilizzare per recuperare i valori delle colonne Identity.

Funzione

Descrizione

SCOPE_IDENTITY

Restituisce l'ultimo valore della colonna Identity compreso nell'ambito di esecuzione corrente. SCOPE_IDENTITY è consigliato per la maggior parte degli scenari.

@@IDENTITY

Contiene l'ultimo valore di colonna Identity generato in qualsiasi tabella nella sessione corrente. L'utilizzo di trigger può influire sul funzionamento di @@IDENTITY, pertanto è possibile che il valore previsto non venga restituito.

IDENT_CURRENT

Restituisce l'ultimo valore di colonna Identity generato per una tabella specifica in qualsiasi sessione e in qualsiasi ambito.

Nella stored procedure seguente viene illustrato come inserire una riga nella tabella Categories e utilizzare un parametro di output per restituire il nuovo valore Identity generato dalla funzione SCOPE_IDENTITY() Transact-SQL.

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

È quindi possibile specificare la stored procedure come origine di InsertCommand in un oggetto SqlDataAdapter. La proprietà CommandType di InsertCommand deve essere impostata su StoredProcedure. Per recuperare l'output relativo all'identità, viene creato un oggetto SqlParameter il cui ParameterDirection è Output. Quando viene eseguito InsertCommand, il valore Identity a incremento automatico viene restituito e inserito nella colonna CategoryID della riga corrente se si imposta la proprietà UpdatedRowSource del comando di inserimento su UpdateRowSource.OutputParameters o UpdateRowSource.Both.

Se il comando di inserimento esegue un batch in cui sono incluse sia un'istruzione INSERT che un'istruzione SELECT che restituisce il nuovo valore Identity, è possibile recuperare il nuovo valore impostando la proprietà UpdatedRowSource del comando di inserimento su UpdateRowSource.FirstReturnedRecord.

Private Sub RetrieveIdentity(ByVal connectionString As String)
    Using connection As SqlConnection = New SqlConnection( _
       connectionString)

        ' Create a SqlDataAdapter based on a SELECT query.
        Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
           "SELECT CategoryID, CategoryName FROM dbo.Categories", _
           connection)

        ' Create the SqlCommand to execute the stored procedure. 
        adapter.InsertCommand = New SqlCommand("dbo.InsertCategory", _
           connection)
        adapter.InsertCommand.CommandType = CommandType.StoredProcedure

        ' Add the parameter for the CategoryName. Specifying the
        ' ParameterDirection for an input parameter is not required.
        adapter.InsertCommand.Parameters.Add( _
          "@CategoryName", SqlDbType.NVarChar, 15, "CategoryName")

        ' Add the SqlParameter to retrieve the new identity value.
        ' Specify the ParameterDirection as Output.
        Dim parameter As SqlParameter = _
           adapter.InsertCommand.Parameters.Add( _
          "@Identity", SqlDbType.Int, 0, "CategoryID")
        parameter.Direction = ParameterDirection.Output

        ' Create a DataTable and fill it.
        Dim categories As DataTable = New DataTable
        adapter.Fill(categories)

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

        ' Update the database.
        adapter.Update(categories)

        Console.WriteLine("List All Rows:")
        Dim row As DataRow
        For Each row In categories.Rows
            Console.WriteLine("{0}: {1}", row(0), row(1))
        Next
    End Using
End Sub
private static void RetrieveIdentity(string connectionString)
{
    using (SqlConnection connection =
               new SqlConnection(connectionString))
    {
        // Create a SqlDataAdapter based on a SELECT query.
        SqlDataAdapter adapter =
            new SqlDataAdapter(
            "SELECT CategoryID, CategoryName FROM dbo.Categories",
            connection);

        //Create the SqlCommand to execute the stored procedure.
        adapter.InsertCommand = new SqlCommand("dbo.InsertCategory", 
            connection);
        adapter.InsertCommand.CommandType = CommandType.StoredProcedure;

        // Add the parameter for the CategoryName. Specifying the
        // ParameterDirection for an input parameter is not required.
        adapter.InsertCommand.Parameters.Add(
           new SqlParameter("@CategoryName", SqlDbType.NVarChar, 15,
           "CategoryName"));

        // Add the SqlParameter to retrieve the new identity value.
        // Specify the ParameterDirection as Output.
        SqlParameter parameter = 
            adapter.InsertCommand.Parameters.Add(
            "@Identity", SqlDbType.Int, 0, "CategoryID");
        parameter.Direction = ParameterDirection.Output;

        // Create a DataTable and fill it.
        DataTable categories = new DataTable();
        adapter.Fill(categories);

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

        adapter.Update(categories);

        Console.WriteLine("List All Rows:");
        foreach (DataRow row in categories.Rows)
        {
            {
                Console.WriteLine("{0}: {1}", row[0], row[1]);
            }
        }
    }
}

Unione di nuovi valori Identity

In uno scenario comune viene chiamato il metodo GetChanges di un oggetto DataTable per creare una copia che contiene solo le righe modificate e viene utilizzata la nuova copia durante la chiamata al metodo Update di un oggetto DataAdapter. Tale scenario risulta particolarmente utile quando è necessario effettuare il marshalling delle righe modificate in un componente diverso che esegue l'aggiornamento. In seguito all'aggiornamento, la copia può contenere nuovi valori Identity che devono essere reinseriti nell'oggetto DataTable originale. È probabile che i nuovi valori Identity siano diversi dai valori originali di DataTable. Per eseguire l'unione, è necessario mantenere i valori originali delle colonne AutoIncrement della copia per poter individuare e aggiornare le righe esistenti nell'oggetto DataTable originale, anziché aggiungere altre righe che contengono i nuovi valori Identity. Per impostazione predefinita, i valori originali vengono tuttavia persi dopo una chiamata al metodo Update di un oggetto DataAdapter, perché viene chiamato implicitamente AcceptChanges per ogni DataRow aggiornato.

Sono disponibili sono due tecniche per mantenere i valori originali di un oggetto DataColumn in un DataRow durante un aggiornamento di DataAdapter:

  • La prima tecnica per mantenere i valori originali consiste nell'impostare la proprietà AcceptChangesDuringUpdate di DataAdapter su false. Tale operazione influisce su ogni DataRow dell'oggetto DataTable da aggiornare. Per ulteriori informazioni e per un esempio di codice, vedere AcceptChangesDuringUpdate.

  • La seconda tecnica consiste invece nello scrivere codice nel gestore eventi RowUpdated di DataAdapter per impostare Status su SkipCurrentRow. DataRow viene aggiornato ma il valore originale di ogni DataColumn viene mantenuto. Questa tecnica consente di mantenere i valori originali per alcune righe ma non per altre. Ad esempio, il codice può mantenere i valori originali per le righe aggiunte e non per quelle modificate o eliminate controllando dapprima StatementType e quindi impostando Status su SkipCurrentRow solo per le righe in cui il valore di StatementType è Insert.

Quando si utilizza una di queste soluzioni per mantenere i valori originali di un oggetto DataRow durante un aggiornamento di DataAdapter, ADO.NET esegue una serie di azioni per impostare i valori correnti di DataRow sui valori nuovi restituiti dai parametri di output o dalla prima riga restituita di un set di risultati, pur mantenendo il valore originale in ogni DataColumn. Viene innanzitutto chiamato il metodo AcceptChanges di DataRow per mantenere i valori correnti come valori originali, quindi vengono assegnati i nuovi valori. In seguito a queste azioni, è possibile che inaspettatamente la proprietà RowState degli oggetti DataRows la cui proprietà RowState è stata impostata su Added verrà impostata su Modified.

La modalità di applicazione dei risultati del comando a ogni DataRow da aggiornare dipende dalla proprietà UpdatedRowSource di ogni DbCommand. Questa proprietà è impostata su un valore restituito dall'enumerazione UpdateRowSource.

Nella tabella seguente viene descritto il modo in cui i diversi valori dell'enumerazione UpdateRowSource influiscono sulla proprietà RowState delle righe aggiornate.

Nome membro

Descrizione

Both

Viene chiamato AcceptChanges e i valori dei parametri di output e/o i valori nella prima riga di qualsiasi set di risultati restituito vengono inseriti nell'oggetto DataRow da aggiornare. Se non sono disponibili valori da applicare, il valore di RowState sarà Unchanged.

FirstReturnedRecord

Se è stata restituita una riga, viene chiamato AcceptChanges e viene eseguito il mapping della riga alla riga modificata in DataTable, impostando RowState su Modified. Se non viene restituita nessuna riga, AcceptChanges non viene chiamato e RowState rimane impostato su Added.

None

Eventuali parametri o righe restituite vengono ignorati. Non viene eseguita nessuna chiamata a AcceptChanges e RowState rimane impostato su Added.

OutputParameters

Viene chiamato AcceptChanges e viene eseguito il mapping degli eventuali parametri di output alla riga modificata in DataTable, impostando RowState su Modified. Se non sono disponibili parametri di output, il valore di RowState sarà Unchanged.

Esempio

In questo esempio vengono illustrati l'estrazione di righe modificate da un oggetto DataTable e l'utilizzo di SqlDataAdapter per aggiornare l'origine dati e recuperare un nuovo valore per la colonna Identity. InsertCommand esegue due istruzioni Transact-SQL: la prima è l'istruzione INSERT, mentre la seconda è un'istruzione SELECT che utilizza la funzione SCOPE_IDENTITY per recuperare il valore Identity.

INSERT INTO dbo.Shippers (CompanyName) 
VALUES (@CompanyName);
SELECT ShipperID, CompanyName FROM dbo.Shippers 
WHERE ShipperID = SCOPE_IDENTITY();

La proprietà UpdatedRowSource del comando di inserimento viene impostata su UpdateRowSource.FirstReturnedRow, mentre la proprietà MissingSchemaAction di DataAdapter viene impostata su MissingSchemaAction.AddWithKey. DataTable viene riempito e il codice aggiunge una nuova riga a DataTable. Le righe modificate vengono quindi estratte in un nuovo oggetto DataTable, che viene passato a DataAdapter e utilizzato per l'aggiornamento del server.

Private Sub MergeIdentityColumns(ByVal connectionString As String)

    Using connection As SqlConnection = New SqlConnection( _
       connectionString)

        ' Create the DataAdapter
        Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
          "SELECT ShipperID, CompanyName FROM dbo.Shippers", connection)

        ' Add the InsertCommand to retrieve new identity value.
        adapter.InsertCommand = New SqlCommand( _
            "INSERT INTO dbo.Shippers (CompanyName) " & _
            "VALUES (@CompanyName); " & _
            "SELECT ShipperID, CompanyName FROM dbo.Shippers " & _
            "WHERE ShipperID = SCOPE_IDENTITY();", _
            connection)

        ' Add the parameter for the inserted value.
        adapter.InsertCommand.Parameters.Add( _
           New SqlParameter("@CompanyName", SqlDbType.NVarChar, 40, _
           "CompanyName"))
        adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.Both

        ' MissingSchemaAction adds any missing schema to 
        ' the DataTable, including identity columns
        adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey

        ' Fill the DataTable.
        Dim shipper As New DataTable
        adapter.Fill(shipper)

        ' Add a new shipper. 
        Dim newRow As DataRow = shipper.NewRow()
        newRow("CompanyName") = "New Shipper"
        shipper.Rows.Add(newRow)

        ' Add changed rows to a new DataTable. This
        ' DataTable will be used by the DataAdapter.
        Dim dataChanges As DataTable = shipper.GetChanges()

        ' Add the event handler. 
        AddHandler adapter.RowUpdated, New _
           SqlRowUpdatedEventHandler(AddressOf OnRowUpdated)

        ' Update the datasource with the modified records.
        adapter.Update(dataChanges)

        ' Merge the two DataTables.
        shipper.Merge(dataChanges)

        ' Commit the changes.
        shipper.AcceptChanges()

        Console.WriteLine("Rows after merge.")
        Dim row As DataRow
        For Each row In shipper.Rows
            Console.WriteLine("{0}: {1}", row(0), row(1))
        Next
    End Using
End Sub
private static void MergeIdentityColumns(string connectionString)
{
    using (SqlConnection connection =
               new SqlConnection(connectionString))
    {
        // Create the DataAdapter
        SqlDataAdapter adapter =
            new SqlDataAdapter(
            "SELECT ShipperID, CompanyName FROM dbo.Shippers",
            connection);

        //Add the InsertCommand to retrieve new identity value.
        adapter.InsertCommand = new SqlCommand(
            "INSERT INTO dbo.Shippers (CompanyName) " +
            "VALUES (@CompanyName); " +
            "SELECT ShipperID, CompanyName FROM dbo.Shippers " +
            "WHERE ShipperID = SCOPE_IDENTITY();", connection);

        // Add the parameter for the inserted value.
        adapter.InsertCommand.Parameters.Add(
           new SqlParameter("@CompanyName", SqlDbType.NVarChar, 40,
           "CompanyName"));
        adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.Both;

        // MissingSchemaAction adds any missing schema to 
        // the DataTable, including identity columns
        adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;

        // Fill the DataTable.
        DataTable shipper = new DataTable();
        adapter.Fill(shipper);

        // Add a new shipper. 
        DataRow newRow = shipper.NewRow();
        newRow["CompanyName"] = "New Shipper";
        shipper.Rows.Add(newRow);

        // Add changed rows to a new DataTable. This
        // DataTable will be used by the DataAdapter.
        DataTable dataChanges = shipper.GetChanges();

        // Add the event handler. 
        adapter.RowUpdated +=
            new SqlRowUpdatedEventHandler(OnRowUpdated);

        adapter.Update(dataChanges);
        connection.Close();

        // Merge the updates.
        shipper.Merge(dataChanges);

        // Commit the changes.
        shipper.AcceptChanges();

        Console.WriteLine("Rows after merge.");
        foreach (DataRow row in shipper.Rows)
        {
            {
                Console.WriteLine("{0}: {1}", row[0], row[1]);
            }
        }
    }
}

Il gestore eventi OnRowUpdated verifica la proprietà StatementType di SqlRowUpdatedEventArgs per determinare se la riga è un inserimento. In caso affermativo, la proprietà Status viene impostata su SkipCurrentRow. La riga viene aggiornata, tuttavia i valori originali della riga vengono mantenuti. Nel corpo principale della procedura viene chiamato il metodo Merge per unire il nuovo valore Identity nell'oggetto DataTable originale e infine viene chiamato AcceptChanges.

Private Sub OnRowUpdated( _
    ByVal sender As Object, ByVal e As SqlRowUpdatedEventArgs)
    ' If this is an insert, then skip this row.
    If e.StatementType = StatementType.Insert Then
        e.Status = UpdateStatus.SkipCurrentRow
    End If
End Sub
protected static void OnRowUpdated(
    object sender, SqlRowUpdatedEventArgs e)
{
    // If this is an insert, then skip this row.
    if (e.StatementType == StatementType.Insert)
    {
        e.Status = UpdateStatus.SkipCurrentRow;
    }
}

Recupero di valori del campo Contatore di Microsoft Access

Questa sezione include un esempio che illustra come recuperare valori Autonumber da un database Jet 4.0. Il motore del database Jet non supporta l'esecuzione di più istruzioni in un batch o l'utilizzo di parametri di output, pertanto non è possibile utilizzare nessuna di queste tecniche per restituire il nuovo valore di Autonumber assegnato a una riga inserita. È tuttavia possibile aggiungere codice al gestore eventi RowUpdated che esegue un'istruzione SELECT @@IDENTITY distinta per recuperare il nuovo valore di Autonumber.

Esempio

Anziché aggiungere informazioni sullo schema tramite MissingSchemaAction.AddWithKey, in questo esempio viene configurato un oggetto DataTable con lo schema corretto prima di chiamare OleDbDataAdapter per riempire un oggetto DataTable. In questo caso, la colonna CategoryID viene configurata in modo da ridurre il valore assegnato a ogni riga inserita a partire da zero, mediante l'impostazione di AutoIncrement su true, di AutoIncrementSeed su 0 e di AutoIncrementStep su -1. Il codice aggiunge quindi due nuove righe e utilizza GetChanges per aggiungere le righe modificate a un nuovo oggetto DataTable che viene passato al metodo Update.

Shared connection As OleDbConnection = Nothing

Private Shared Sub MergeIdentityColumns(ByVal connection As OleDbConnection)
    Using connection

        ' Create a DataAdapter based on a SELECT query.
        Dim adapter As OleDbDataAdapter = New OleDbDataAdapter( _
          "SELECT CategoryID, CategoryName FROM Categories", _
          connection)

        ' Create the INSERT command for the new category.
        adapter.InsertCommand = New OleDbCommand( _
          "INSERT INTO Categories (CategoryName) Values(?)", connection)
        adapter.InsertCommand.CommandType = CommandType.Text

        ' Add the parameter for the CategoryName.
        adapter.InsertCommand.Parameters.Add( _
          "@CategoryName", OleDbType.VarWChar, 15, "CategoryName")
        adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.Both

        ' Create a DataTable.
        Dim categories As DataTable = New DataTable

        ' Create the CategoryID column and set its auto
        ' incrementing properties to decrement from zero.
        Dim column As New DataColumn()
        column.DataType = System.Type.GetType("System.Int32")
        column.ColumnName = "CategoryID"
        column.AutoIncrement = True
        column.AutoIncrementSeed = 0
        column.AutoIncrementStep = -1
        categories.Columns.Add(column)

        ' Create the CategoryName column.
        column = New DataColumn()
        column.DataType = System.Type.GetType("System.String")
        column.ColumnName = "CategoryName"
        categories.Columns.Add(column)

        ' Set the primary key on CategoryID.
        Dim pKey(1) As DataColumn
        pKey(0) = categories.Columns("CategoryID")
        categories.PrimaryKey = pKey

        ' Fetch the data and fill the DataTable.
        adapter.Fill(categories)

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

        ' Add another new row.
        Dim newRow2 As DataRow = categories.NewRow()
        newRow2("CategoryName") = "Another New Category"
        categories.Rows.Add(newRow2)

        ' Add changed rows to a new DataTable that will be
        ' used to post the inserts to the database.
        Dim dataChanges As DataTable = categories.GetChanges()

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

        ' Update the database, inserting the new rows.
        adapter.Update(dataChanges)

        Console.WriteLine("Rows before merge:")
        Dim row1 As DataRow
        For Each row1 In categories.Rows
            Console.WriteLine("  {0}: {1}", row1(0), row1(1))
        Next

        ' Merge the two DataTables.
        categories.Merge(dataChanges)

        ' Commit the changes.
        categories.AcceptChanges()

        Console.WriteLine("Rows after merge:")
        Dim row As DataRow
        For Each row In categories.Rows
            Console.WriteLine("  {0}: {1}", row(0), row(1))
        Next
    End Using
End Sub
private static OleDbConnection connection = null;

private static void MergeIdentityColumns(OleDbConnection connection)
{
    using (connection)
    {
        // Create a DataAdapter based on a SELECT query.
        OleDbDataAdapter adapter = new OleDbDataAdapter(
         "SELECT CategoryID, CategoryName FROM Categories",
         connection);

        // Create the INSERT command for the new category.
        adapter.InsertCommand = new OleDbCommand(
          "INSERT INTO Categories (CategoryName) Values(?)", connection);
        adapter.InsertCommand.CommandType = CommandType.Text;

        // Add the parameter for the CategoryName.
        adapter.InsertCommand.Parameters.Add(
          "@CategoryName", OleDbType.VarWChar, 15, "CategoryName");
        adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.Both;

        // Create a DataTable
        DataTable categories = new DataTable();

        // Create the CategoryID column and set its auto 
        // incrementing properties to decrement from zero. 
        DataColumn column = new DataColumn();
        column.DataType = System.Type.GetType("System.Int32");
        column.ColumnName = "CategoryID";
        column.AutoIncrement = true;
        column.AutoIncrementSeed = 0;
        column.AutoIncrementStep = -1;
        categories.Columns.Add(column);

        // Create the CategoryName column.
        column = new DataColumn();
        column.DataType = System.Type.GetType("System.String");
        column.ColumnName = "CategoryName";
        categories.Columns.Add(column);

        // Set the primary key on CategoryID.
        DataColumn[] pKey = new DataColumn[1];
        pKey[0] = categories.Columns["CategoryID"];
        categories.PrimaryKey = pKey;

        // Fetch the data and fill the DataTable
        adapter.Fill(categories);

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

        // Add another new row.
        DataRow newRow2 = categories.NewRow();
        newRow2["CategoryName"] = "Another New Category";
        categories.Rows.Add(newRow2);

        // Add changed rows to a new DataTable that will be
        // used to post the inserts to the database.
        DataTable dataChanges = categories.GetChanges();

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

        // Update the database, inserting the new rows. 
        adapter.Update(dataChanges);

        Console.WriteLine("Rows before merge:");
        foreach (DataRow row in categories.Rows)
        {
            {
                Console.WriteLine("  {0}: {1}", row[0], row[1]);
            }
        }

        // Merge the two DataTables.
        categories.Merge(dataChanges);

        // Commit the changes.
        categories.AcceptChanges();

        Console.WriteLine("Rows after merge:");
        foreach (DataRow row in categories.Rows)
        {
            {
                Console.WriteLine("  {0}: {1}", row[0], row[1]);
            }
        }
    }
}

Il gestore eventi RowUpdated utilizza lo stesso oggetto OleDbConnection aperto come istruzione Update di OleDbDataAdapter. Verifica il valore di StatementType di OleDbRowUpdatedEventArgs per le righe inserite. Per ogni riga inserita viene creata un nuovo oggetto OleDbCommand per eseguire l'istruzione SELECT @@IDENTITY sulla connessione. Viene quindi restituito il nuovo valore di Autonumber, che viene inserito nella colonna CategoryID di DataRow. La proprietà Status viene quindi impostata su UpdateStatus.SkipCurrentRow per sopprimere la chiamata nascosta a AcceptChanges. Nel corpo principale della procedura viene chiamato il metodo Merge per unire i due oggetti DataTable e infine viene chiamato AcceptChanges.

Private Shared Sub OnRowUpdated( _
    ByVal sender As Object, ByVal e As OleDbRowUpdatedEventArgs)
    ' Conditionally execute this code block on inserts only.
    If e.StatementType = StatementType.Insert Then
        ' Retrieve the Autonumber and store it in the CategoryID column.
        Dim cmdNewID As New OleDbCommand("SELECT @@IDENTITY", _
           connection)
        e.Row("CategoryID") = CInt(cmdNewID.ExecuteScalar)
        e.Status = UpdateStatus.SkipCurrentRow
    End If
End Sub
private static void OnRowUpdated(
  object sender, OleDbRowUpdatedEventArgs e)
{
    // Conditionally execute this code block on inserts only.
    if (e.StatementType == StatementType.Insert)
    {
        OleDbCommand cmdNewID = new OleDbCommand("SELECT @@IDENTITY",
            connection);
        // Retrieve the Autonumber and store it in the CategoryID column.
        e.Row["CategoryID"] = (int)cmdNewID.ExecuteScalar();
        e.Status = UpdateStatus.SkipCurrentRow;
    }
}

Vedere anche

Concetti

Stati delle righe e versioni delle righe

AcceptChanges e RejectChanges

Unione del contenuto di un DataSet (ADO.NET)

Aggiornamenti di origini dati tramite DataAdapter (ADO.NET)

Altre risorse

Recupero e modifica di dati in ADO.NET

DataAdapter e DataReader (ADO.NET)