|
Overview
Multiuser applications often must allow for multiple users to edit the same record
concurrently. In database-locking parlance, the term optimistic
concurrency means that your application assumes that no one else will be making
changes to the same record that it is currently editing.
Although the Data Adapter Configuration Wizard in Visual Studio
.NET generates code to check for optimistic concurrency violations, it does
not handle the errors itself. Running into an optimistic concurrency violation simply
causes your program to blow up by throwing an exception. You can add a
Try…Catch block to handle the actual exception, but you’ll
probably want to be able to give your users the best overall experience by
intelligently handling the violation itself.
In addition to Try/Catch and exceptions, the ADO.NET data architecture allows you to
add error messages to each row of Data in a DataSet. SqlDataAdapters attach error
messages to Rows if updates or other actions fail. Furthermore, you can filter
for rows in error to present them to the user, or pass them to error handling
functions.
Check for Errors before Update
As users work on a set of data contained in a DataSet, you can mark each change with
an error if the change causes some validation failure. You can mark an entire DataRow
with an error message using the RowError property. You can also set errors on
each column of the row with the SetColumnError method.
Before updating a data source with a DataSet, it's recommended that you first invoke
the GetChanges method on the target DataSet. The method results in a DataSet that
contains only the changes made to the original. Before sending the DataSet to the
data source for updating, check the HasErrors property of each table to see if any
errors have been attached to the rows or columns in the rows.
// **** Handle Errors before UPDATE
protected void btnUpdate_Click (object sender, System.EventArgs e)
{
// Use the GetChanges method to create a
second DataSet
// object that is then used to update a data source.
DataSet dataSetChanged =
_dataSet.GetChanges(DataRowState.Modified);
// Test to make sure all the changed rows
are without errors
bool okayFlag = true;
if (dataSetChanged.HasErrors)
{
okayFlag = false;
string msg = "Error in row with customer
ID ";
// Examine each
table in the changed DataSet
foreach (DataTable theTable in
dataSetChanged.Tables)
{
// If any table has errors, find out which rows
if
(theTable.HasErrors)
{
// Get the rows with errors
DataRow[] errorRows = theTable.GetErrors();
// iterate through the errors and correct
// (in our case, just identify)
foreach (DataRow theRow in errorRows)
{
msg = msg + theRow["CustomerID"];
}
}
}
lblMessage.Text = msg;
}
// If we have no errors
if (okayFlag)
{
// Update the
database on second dataset
_dataAdapter.Update(dataSetChanged,"Customers");
// Inform the
user
Application.DoEvents();
// Commit the changes
_dataSet.AcceptChanges();
}
else
{ // If we had errors, reject
the changes
_dataSet.RejectChanges();
}
}
Example
The following example loads a DataSet, sets some errors, and then shows the Errors in
the Rows. You can then get the
errors in a DataTable with the GetErrors() method.
You can also test for errors using HasErrors.
namespace ADO.HandlingRowErrors
{
using System;
using System.Data;
using System.Data.SqlClient;
public class handleerrors
{
public static void Main()
{
handleerrors myhandleerrors = new handleerrors();
myhandleerrors.Run();
}
public void Run()
{
// Create a new Connection and
SqlDataAdapter
SqlConnection myConnection = new SqlConnection(
"server=xeon;uid=sa;pwd=manager;database=northwind");
SqlDataAdapter mySqlDataAdapter = new
SqlDataAdapter(
"select * from customers",
myConnection);
// Create the new instance of
the DataSet
DataSet myDataSet = new DataSet();
try
{
// Load the
customer table from the database
// into a table called Customers in the
dataset
mySqlDataAdapter.Fill(myDataSet,"Customers");
// Create a new
dataview instance on the Customers
// table that was just created
DataView myDataView = new
DataView(myDataSet.Tables["Customers"]);
// Sort the view
based on the FirstName column
myDataView.Sort = "CustomerID";
// Manually add
Errors ...
myDataSet.Tables["Customers"].Rows[0].RowError =
"Manually added an Error in
Row 1";
myDataSet.Tables["Customers"].Rows[1].RowError =
"Manually added an Error in
Row 2";
// ... and check
the DataTable for these inserted Errors
if (
myDataSet.Tables["Customers"].HasErrors )
{
//
If we have Errors, get them ...
DataRow[] ErrDataRows =
myDataSet.Tables["Customers"].GetErrors();
Console.WriteLine("DataTable
{0} has {1} Error(s)!",
myDataSet.Tables["Customers"].TableName,ErrDataRows.Length.ToString());
//
... for each Row in the DataTable
for (int i = 0; i <=
ErrDataRows.Length -1; i++)
{
Console.WriteLine("Row Error for row {0} -- Error Msg={1}",
ErrDataRows[i]["CustomerID"].ToString(),ErrDataRows[i].RowError);
}
}
else
{
Console.WriteLine("=================");
Console.WriteLine("DataTable
{0} Has no errors",
myDataSet.Tables["Customers"].TableName);
}
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
|