|
Overview
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
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.
Example Using Stored Procedure
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 and test the stored procedure on
SQL Server 2000
DROP PROCEDURE InsertCategory
GO
CREATE PROCEDURE InsertCategory
@CategoryName nchar(15),
@Identity int OUTPUT
AS
INSERT INTO Categories (CategoryName) VALUES(@CategoryName)
SET @Identity = SCOPE_IDENTITY()
GO
DECLARE @answer int
EXECUTE InsertCategory 'Akadia',@answer OUTPUT
SELECT 'Result = ', @answer
GO
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.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace Akadia.AutoIncrement
{
public class AutoIncrement : System.Windows.Forms.Form
{
private System.Windows.Forms.DataGrid
dgShowCat;
private System.Windows.Forms.Label
lblUpdate;
private System.Windows.Forms.Button
btnUpdate;
private System.ComponentModel.Container
components = null;
private DataSet catDS;
private SqlDataAdapter catDA;
private System.Windows.Forms.TextBox
txtCatName;
public AutoIncrement()
{
// Initialize GUI
InitializeComponent();
// Fetch Categories Table into DataSet
SqlConnection
nwindConn = new SqlConnection("Data Source=XEON; " +
"Initial Catalog=Northwind; User id=sa; Password=manager;");
catDA = new
SqlDataAdapter("SELECT CategoryID, "+
"CategoryName FROM Categories ORDER BY CategoryID", 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();
catDS = new
DataSet("CategoriesDS");
catDA.Fill(catDS,
"Categories");
dgShowCat.SetDataBinding(catDS,"Categories");
nwindConn.Close();
}
// Update Dataset
using the Stored Procedure and fetch
// Autoincrement Value back to the
Dataset
private void btnUpdate_Click(object
sender, System.EventArgs e)
{
DataRow newRow
= catDS.Tables["Categories"].NewRow();
newRow["CategoryName"] = txtCatName.Text;
catDS.Tables["Categories"].Rows.Add(newRow);
catDA.Update(catDS, "Categories");
}
....
....
[STAThread]
static void Main()
{
Application.Run(new AutoIncrement());
}
}
}
Example Using INSERT followed by SELECT
When working with auto-incrementing columns at a data source and auto-incrementing
columns in a DataSet, you create the column in the DataSet with an AutoIncrementStep
of -1 and an AutoIncrementSeed of 0, as well as ensuring that your data source
generates auto-incrementing identity values starting from 1 and incrementing with a positive step value. As a result, the
DataSet generates negative numbers for auto-incremented values that do not conflict
with the positive auto-increment values generated by the data source.
Next, you'll need to select the autoincrement value from the
server back into the DataSet upon update. SQL Server provides a couple of ways to do
this, including the @@Identity variable that returns the ID of the last row inserted
within your scope. Finally, if you want to continue using the DataSet on the client,
you would want to update the DataSet on the client by mergine the DataSet updated
with new values from the server. In order to do this, you need to make sure that
the DataAdapter does not accept changes when it updates the rows in the
DataSet; otherwise the inserted row that has been updated with the new values
will lose the original primary key values, and when you merge with the original
dataset it will be added as a new row. In order to prevent the DataAdapter from
calling AcceptChanges, you'll need to intercept the OnRowChanged event
and tell the DataAdapter to skip calling AcceptChanges on the current row. It's not
all as complicated as it sounds. The following code example shows the same example as
above. In order to have the returned value applied to the DataSet, it specifies that
the source of data to use to update the row is the first returned record. Finally, an
event handler is registered which intercepts the OnRowChanged event in order to
prevent AcceptChanges from being called.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace Akadia.AutoIncrement
{
public class AutoIncrement : System.Windows.Forms.Form
{
private System.Windows.Forms.DataGrid dgShowCat;
private System.Windows.Forms.Label lblUpdate;
private System.Windows.Forms.Button btnUpdate;
private System.ComponentModel.Container components = null;
private DataSet catDS;
private SqlDataAdapter catDA;
private System.Windows.Forms.TextBox txtCatName;
public AutoIncrement()
{
// Initialize GUI
InitializeComponent();
// Fetch Categories Table into DataSet
SqlConnection nwindConn = new SqlConnection("Data Source=XEON; " +
"Initial Catalog=Northwind; User id=sa; Password=manager;");
catDA = new SqlDataAdapter("SELECT CategoryID, "+
"CategoryName FROM Categories ORDER BY CategoryID", nwindConn);
// Change the INSERT command text to include a SELECT
// command as well, and make sure the INSERT command's
// UpdatedRowSource is set to UpdateRowSource.FirstReturnedRecord
SqlCommand cmdIn = new SqlCommand(
"INSERT INTO Categories(CategoryName) VALUES (@CategoryName);" +
"SELECT CategoryID, CategoryName " +
" FROM Categories " +
" WHERE (CategoryID = @@IDENTITY)",nwindConn);
cmdIn.Parameters.Add("@CategoryName",
typeof(String)).SourceColumn="CategoryName";
catDA.InsertCommand = cmdIn;
// The data in the first returned row is mapped to the
// changed row in the DataSet.
cmdIn.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
// Set UpdateRowSource and Register RowUpdatedEventHandler
catDA.RowUpdated += new SqlRowUpdatedEventHandler(myHandler);
DataTable catTable = new DataTable("Categories");
// Fetch Data into Dataset
nwindConn.Open();
catDS = new DataSet("CategoriesDS");
catDA.Fill(catDS, "Categories");
dgShowCat.SetDataBinding(catDS,"Categories");
nwindConn.Close();
}
// Next, you'll need to select the autoincrement
// value from the server back into the DataSet upon update.
// SQL Server provides a couple of ways to do this, including
// the @@Identity variable that returns the ID of the last row inserted
// within your scope. Finally, if you want to continue using the DataSet
// on the client, you would want to update the DataSet on the client by
// mergine the DataSet updated with new values from the server. In order
// to do this, you need to make sure that the DataAdapter does not accept
// changes when it updates the rows in the DataSet; otherwise the inserted
// row that has been updated with the new values will lose the
// original primary key values, and when you merge with the original
// dataset it will be added as a new row. In order to prevent the
// DataAdapter from calling AcceptChanges, you'll need to intercept
// the OnRowChanged event and tell the DataAdapter to skip calling
// AcceptChanges on the current row.
public static void myHandler(object adapter, SqlRowUpdatedEventArgs e)
{
// Don't call AcceptChanges
e.Status = UpdateStatus.SkipCurrentRow;
}
// Update Dataset using the INSERT Command and fetch
// Autoincrement Value back to the Dataset
private void btnUpdate_Click(object sender, System.EventArgs e)
{
// When working with auto-incrementing columns at a data source
// and auto-incrementing columns in a DataSet, you create the column
// in the DataSet with an AutoIncrementStep of -1 and an
// AutoIncrementSeed of 0, as well as ensuring that your data source
// generates auto-incrementing identity values starting from 1 and
// incrementing with a positive step value. As a result, the DataSet
// generates negative numbers for auto-incremented values that do not
// conflict with the positive auto-increment values generated by
// the data source.
catDS.Tables["Categories"].Columns["CategoryID"].AutoIncrementStep = -1;
catDS.Tables["Categories"].Columns["CategoryID"].AutoIncrementSeed = 0;
// Create a new Row and UPDATE it to the database
DataRow newRow = catDS.Tables["Categories"].NewRow();
newRow["CategoryName"] = txtCatName.Text;
catDS.Tables["Categories"].Rows.Add(newRow);
catDA.Update(catDS, "Categories");
}
...
....
[STAThread]
static void Main()
{
Application.Run(new AutoIncrement());
}
}
}
|