Validate data in datasets in .NET Framework applications

Note

Datasets and related classes are legacy .NET Framework technologies from the early 2000s that enable applications to work with data in memory while the applications are disconnected from the database. They are especially useful for applications that enable users to modify data and persist the changes back to the database. Although datasets have proven to be a very successful technology, we recommend that new .NET applications use Entity Framework Core. Entity Framework provides a more natural way to work with tabular data as object models, and it has a simpler programming interface.

Validating data is the process of confirming that the values being entered into data objects conform to the constraints within a dataset's schema. The validation process also confirms that these values are following the rules that have been established for your application. It's a good practice to validate data prior to sending updates to the underlying database. This reduces errors as well as the potential number of round trips between an application and the database.

You can confirm that data that's being written to a dataset is valid by building validation checks into the dataset itself. The dataset can check the data no matter how the update is being performed — whether directly by controls in a form, within a component, or in some other way. Because the dataset is part of your application (unlike the database backend), it's a logical place to build application-specific validation.

The best place to add validation to your application is in the dataset's partial class file. In Visual Basic or Visual C#, open the Dataset Designer and double-click the column or table for which you want to create validation. This action opens up the code file, where you can create a ColumnChanging or RowChanging event handler.

private static void OnColumnChanging(object sender, DataColumnChangeEventArgs e)
{

}

Validate data

Validation within a dataset is accomplished in the following ways:

Several events are raised by the DataTable object when a change is occurring in a record:

  • The ColumnChanging and ColumnChanged events are raised during and after each change to an individual column. The ColumnChanging event is useful when you want to validate changes in specific columns. Information about the proposed change is passed as an argument with the event.
  • The RowChanging and RowChanged events are raised during and after any change in a row. The RowChanging event is more general. It indicates that a change is occurring somewhere in the row, but you don't know which column has changed.

By default, each change to a column therefore raises four events. The first is the ColumnChanging and ColumnChanged events for the specific column that's being changed. Next are the RowChanging and RowChanged events. If multiple changes are being made to the row, the events will be raised for each change.

Note

The data row's BeginEdit method turns off the RowChanging and RowChanged events after each individual column change. In that case, the event is not raised until the EndEdit method has been called, when the RowChanging and RowChanged events are raised just once. For more information, see Turn off constraints while filling a dataset.

The event you choose depends on how granular you want the validation to be. If it's important that you catch an error immediately when a column changes, build validation by using the ColumnChanging event. Otherwise, use the RowChanging event, which might result in catching several errors at once. Additionally, if your data is structured so that the value of one column is validated based on the contents of another column, perform your validation during the RowChanging event.

When records are updated, the DataTable object raises events that you can respond to as changes are occurring and after changes are made.

If your application uses a typed dataset, you can create strongly typed event handlers. This adds four additional typed events for which you can create handlers: dataTableNameRowChanging, dataTableNameRowChanged, dataTableNameRowDeleting, and dataTableNameRowDeleted. These typed event handlers pass an argument that includes the column names of your table that make code easier to write and read.

Data update events

Event Description
ColumnChanging The value in a column is being changed. The event passes the row and column to you, along with the proposed new value.
ColumnChanged The value in a column has been changed. The event passes the row and column to you, along with the proposed value.
RowChanging The changes that were made to a DataRow object are about to be committed back into the dataset. If you have not called the BeginEdit method, the RowChanging event is raised for each change to a column immediately after the ColumnChanging event has been raised. If you called BeginEdit before making changes, the RowChanging event is raised only when you call the EndEdit method.

The event passes the row to you, along with a value indicating what type of action (change, insert, and so on) is being performed.
RowChanged A row has been changed. The event passes the row to you, along with a value indicating what type of action (change, insert, and so on) is being performed.
RowDeleting A row is being deleted. The event passes the row to you, along with a value indicating what type of action (delete) is being performed.
RowDeleted A row has been deleted. The event passes the row to you, along with a value indicating what type of action (delete) is being performed.

The ColumnChanging, RowChanging, and RowDeleting events are raised during the update process. You can use these events to validate data or perform other types of processing. Because the update is in process during these events, you can cancel it by throwing an exception, which prevents the update from finishing.

The ColumnChanged, RowChanged and RowDeleted events are notification events that are raised when the update has finished successfully. These events are useful when you want to take further action based on a successful update.

Validate data during column changes

Note

The Dataset Designer creates a partial class in which validation logic can be added to a dataset. The designer-generated dataset doesn't delete or change any code in the partial class.

You can validate data when the value in a data column changes by responding to the ColumnChanging event. When raised, this event passes an event argument (ProposedValue) that contains the value that's being proposed for the current column. Based on the contents of e.ProposedValue, you can:

  • Accept the proposed value by doing nothing.

  • Reject the proposed value by setting the column error (SetColumnError) from within the column-changing event handler.

  • Optionally use an ErrorProvider control to display an error message to the user. For more information, see ErrorProvider component.

Validation can also be performed during the RowChanging event.

Validate data during row changes

You can write code to verify that each column you want to validate contains data that meets the requirements of your application. Do this by setting the column to indicate that it contains an error if a proposed value is unacceptable. The following examples set a column error when the Quantity column is 0 or less. The row-changing event handlers should resemble the following examples.

To validate data when a row changes (Visual Basic)

  1. Open your dataset in the Dataset Designer. For more information, see Walkthrough: Creating a Dataset in the Dataset Designer.

  2. Double-click the title bar of the table you want to validate. This action automatically creates the RowChanging event handler of the DataTable in the dataset's partial-class file.

    Tip

    Double-click to the left of the table name to create the row-changing event handler. If you double-click the table name, you can edit it.

    Private Sub Order_DetailsDataTable_Order_DetailsRowChanging(
        ByVal sender As System.Object, 
        ByVal e As Order_DetailsRowChangeEvent
      ) Handles Me.Order_DetailsRowChanging
    
        If CType(e.Row.Quantity, Short) <= 0 Then
            e.Row.SetColumnError("Quantity", "Quantity must be greater than 0")
        Else
            e.Row.SetColumnError("Quantity", "")
        End If
    End Sub
    

To validate data when a row changes (C#)

  1. Open your dataset in the Dataset Designer. For more information, see Walkthrough: Creating a dataset in the Dataset Designer.

  2. Double-click the title bar of the table you want to validate. This action creates a partial-class file for the DataTable.

    Note

    The Dataset Designer does not automatically create an event handler for the RowChanging event. You have to create a method to handle the RowChanging event, and run code to hook up the event in the table's initialization method.

  3. Copy the following code into the partial class:

    public override void EndInit()
    {
        base.EndInit();
        Order_DetailsRowChanging += TestRowChangeEvent;
    }
    
    public void TestRowChangeEvent(object sender, Order_DetailsRowChangeEvent e)
    {
        if ((short)e.Row.Quantity <= 0)
        {
            e.Row.SetColumnError("Quantity", "Quantity must be greater than 0");
        }
        else
        {
            e.Row.SetColumnError("Quantity", "");
        }
    }
    

To retrieve changed rows

Each row in a data table has a RowState property that keeps track of the current state of that row by using the values in the DataRowState enumeration. You can return changed rows from a dataset or data table by calling the GetChanges method of a DataSet or DataTable. You can verify that changes exist prior to calling GetChanges by calling the HasChanges method of a dataset.

Note

After you commit changes to a dataset or data table (by calling the AcceptChanges method), the GetChanges method returns no data. If your application needs to process changed rows, you must process the changes before calling the AcceptChanges method.

Calling the GetChanges method of a dataset or data table returns a new dataset or data table that contains only records that have been changed. If you want to get specific records — for example, only new records or only modified records — you can pass a value from the DataRowState enumeration as a parameter to the GetChanges method.

Use the DataRowVersion enumeration to access the different versions of a row (for example, the original values that were in a row prior to processing it).

To get all changed records from a dataset

  • Call the GetChanges method of a dataset.

    The following example creates a new dataset called changedRecords and populates it with all the changed records from another dataset called dataSet1.

    DataSet changedRecords = dataSet1.GetChanges();
    

To get all changed records from a data table

  • Call the GetChanges method of a DataTable.

    The following example creates a new data table called changedRecordsTable and populates it with all the changed records from another data table called dataTable1.

    DataTable changedRecordsTable = dataTable1.GetChanges();
    

To get all records that have a specific row state

  • Call the GetChanges method of a dataset or data table and pass a DataRowState enumeration value as an argument.

    The following example shows how to create a new dataset called addedRecords and populate it only with records that have been added to the dataSet1 dataset.

    DataSet addedRecords = dataSet1.GetChanges(DataRowState.Added);
    

    The following example shows how to return all records that were recently added to the Customers table:

    private NorthwindDataSet.CustomersDataTable GetNewRecords()
    {
        return (NorthwindDataSet.CustomersDataTable)
            northwindDataSet1.Customers.GetChanges(DataRowState.Added);
    }
    

Access the original version of a DataRow

When changes are made to data rows, the dataset retains both the original (Original) and new (Current) versions of the row. For example, before calling the AcceptChanges method, your application can access the different versions of a record (as defined in the DataRowVersion enumeration) and process the changes accordingly.

Note

Different versions of a row exist only after it has been edited and before it the AcceptChanges method has been called. After the AcceptChanges method has been called, the current and original versions are the same.

Passing the DataRowVersion value along with the column index (or column name as a string) returns the value from that column's particular row version. The changed column is identified during the ColumnChanging and ColumnChanged events. This is a good time to inspect the different row versions for validation purposes. However, if you have temporarily suspended constraints, those events won't be raised, and you will need to programmatically identify which columns have changed. You can do this by iterating through the Columns collection and comparing the different DataRowVersion values.

To get the original version of a record

  • Access the value of a column by passing in the DataRowVersion of the row you want to return.

    The following example shows how to use a DataRowVersion value to get the original value of a CompanyName field in a DataRow:

    string originalCompanyName;
    originalCompanyName = northwindDataSet1.Customers[0]
        ["CompanyName", DataRowVersion.Original].ToString();
    

Access the current version of a DataRow

To get the current version of a record

  • Access the value of a column, and then add a parameter to the index that indicates which version of a row you want to return.

    The following example shows how to use a DataRowVersion value to get the current value of a CompanyName field in a DataRow:

    string currentCompanyName;
    currentCompanyName = northwindDataSet1.Customers[0]
        ["CompanyName", DataRowVersion.Current].ToString();