Simple Validation with LINQ to SQL Classes

Published 25 February 08 05:04 PM

In the last few posts on LINQ to SQL I've showed how to set up an object model using the O/R designer and how to handle a couple data binding scenarios with Comboboxes here and here. Last post on this topic we implemented a one-to-many data entry form and I showed how to work with stored procs as well as how to properly configure delete behaviors. In this post I want to explore how to easily add validation rules to our LINQ to SQL classes and how we can get these rules automatically displayed in the UI.

LINQ to SQL Classes -- A Closer Look

Classes that implement the IDataErrorInfo interface in conjunction with INotifyPropertyChanged are able to automatically notify UI objects like the ErrorProvider and the DataGridView to display validation errors. LINQ to SQL classes that are generated for you when you create your object models already implement the INotifyPropertyChanged interface and using partial methods we can easily add validation to our classes. For this example we'll expand the One-to-Many data entry form we built to include some business rules.

First let's open up the generated LINQ to SQL classes by opening the .Designer.vb file under the dbml file (if you don't see the designer file, just click the "show all files" button on the Solution Explorer tool strip first). If we take a look at our Order class we will see the following class definition:

<Table(Name:="dbo.Orders")>  _
Partial Public Class [Order]
    Implements System.ComponentModel.INotifyPropertyChanging, _
System.ComponentModel.INotifyPropertyChanged

As you can see, this class is just a plain old CLR object (POCO) that implements interfaces that notify when properties are changing or changed on the class. That's it. It's the DataContext that does the heavy lifting, knowing what objects have changes, which ones were added and removed and how to persist these to the database. It does its simple mapping via the attributes on the class and it's properties. Here's the property for CustomerID on our Order class:

<Column(Storage:="_CustomerID", DbType:="Int NOT NULL", UpdateCheck:=UpdateCheck.Never)>  _
    Public Property CustomerID() As Integer
        Get
            Return Me._CustomerID
        End Get
        Set
            If ((Me._CustomerID = value)  _
                        = false) Then
                If Me._Customer.HasLoadedOrAssignedValue Then
                    Throw New System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException
                End If
                Me.OnCustomerIDChanging(value)
                Me.SendPropertyChanging
                Me._CustomerID = value
                Me.SendPropertyChanged("CustomerID")
                Me.OnCustomerIDChanged
            End If
        End Set
    End Property

Partial Classes and Methods

In order to add validation and business rules we can add code to the OnCustomerIDChanging and OnCustomerIDChanged methods. But we don't add them here in this generated file, instead we add them into the Partial Class. This is possible because of a new feature in Visual Studio 2008 called Partial Methods. Partial Classes were introduced in Visual Studio 2005 as a way to extend generated classes with additional functionality by allowing you to split classes across physical files. You can add new methods or properties in order to extend a generated class easily by using the Partial Class keyword and then writing your own code. The compiler will "merge" these files into one class.

In Visual Studio 2008 we can go a step further using Partial methods. Instead of raising and handling private events, partial methods can be used instead as a better performing and cleaner alternative. They are declared by creating a private method with an empty body and decorating it with the Partial keyword. The method may then be "re-implemented" elsewhere within its containing class. If the method is implemented, then the compiler will redirect all calls to the partial method to the implementing method. If the method is not implemented in its containing class, then the compiler silently removes any calls to it from the program.

If we take a look at the generated LINQ to SQL Order class again you can see there is a region called "Extensibility Method Definitions" that contain Partial methods. There will be On...Changed and On...Changing partial methods here for each of the properties on the class.

#Region "Extensibility Method Definitions"
    Partial Private Sub OnLoaded()
    End Sub
    Partial Private Sub OnValidate(action As System.Data.Linq.ChangeAction)
    End Sub
    Partial Private Sub OnCreated()
    End Sub
    Partial Private Sub OnCustomerIDChanging(value As Integer)
    End Sub
    Partial Private Sub OnCustomerIDChanged()
    End Sub
.
.
.

Notice that the Changing and Changed methods are called at the appropriate times in each of the property setters. This allows us to write clean business rules on these properties in our partial class by defining the partial method. However if we don't write any code for these partial methods, the calls to them are removed from the IL. To access the partial class code open the model (dbml file) in the O/R designer, select a class, right-click on the class name and select "View Code". This will create a file named after your model where the partial class code that we write can reside.

So to write some business rules for our classes in this example we're going to place code in the On...Changing methods and then implement IDataErrorInfo so that we can have the UI automatically display the validation messages. To make it easier to implement IDataErrorInfo on all our LINQ to SQL classes I'm going to create a base class that we can inherit from called BaseBusiness.

Implementing IDataErrorInfo

IDataErrorInfo requires us to implement only two properties, one called Error and one default property Item that both return a string. Error is used to describe what is wrong with the entire object. For instance, if we are displaying this object in a row of a DataGridView this property indicates what error message appears on the row header. If you use DataSets, this corresponds to the DataRow.RowError property. The Item property is used to determine the error message for a specific property (or column) that is passed in. In order to collect these messages for each property on our object (i.e. column in our table) we can use a generic dictionary of strings and either add or remove messages from the dictionary depending on the validation rules. Here's an example implementation of IDataErrorInfo on our base class:

Imports System.ComponentModel

''' <summary>
''' Base class for our LINQ to SQL classes.
''' This class demonstrates one way to implement the IDataErrorInfo
'''  interface so that the ErrorProvider and DataGridView can display
'''  validation errors in the UI.
''' </summary>
''' <remarks></remarks>
Public Class BaseBusiness
    Implements IDataErrorInfo

    'This dictionary contains a list of our validation errors for each field
    Private validationErrors As New Dictionary(Of String, String)

    Protected Sub AddError(ByVal columnName As String, ByVal msg As String)
        If Not validationErrors.ContainsKey(columnName) Then
            validationErrors.Add(columnName, msg)
        End If
    End Sub

    Protected Sub RemoveError(ByVal columnName As String)
        If validationErrors.ContainsKey(columnName) Then
            validationErrors.Remove(columnName)
        End If
    End Sub

    Public Overridable ReadOnly Property HasErrors() As Boolean
        Get
            Return (validationErrors.Count > 0)
        End Get
    End Property

    Public ReadOnly Property [Error]() As String _
        Implements System.ComponentModel.IDataErrorInfo.Error
        Get
            If validationErrors.Count > 0 Then
                Return String.Format("{0} data is invalid.", TypeName(Me))
            Else
                Return Nothing
            End If
        End Get
    End Property

    Default Public ReadOnly Property Item(ByVal columnName As String) As String _
        Implements System.ComponentModel.IDataErrorInfo.Item
        Get
            If validationErrors.ContainsKey(columnName) Then
                Return validationErrors(columnName).ToString
            Else
                Return Nothing
            End If
        End Get
    End Property
End Class

Next we need to inherit from this class in all our LINQ to SQL Classes. Back in our partial class file we can inherit all our LINQ to SQL classes from BaseBusiness:

Partial Class Customer
    Inherits BaseBusiness

End Class

Partial Class Product
    Inherits BaseBusiness

End Class

Partial Class OrderDetail
    Inherits BaseBusiness

End Class

Partial Class Order
    Inherits BaseBusiness

End Class

Writing the Business Rules

Now we need to write our business rules into these classes. Let's take Order as an example. There are a couple rules I want to implement here:

  1. The OrderDate cannot be after the ShipDate
  2. The Customer must be specified

There are a couple places that we need to run these rules. One is when any of these fields change and the other is before the changes are submitted to the database. We need to handle both places because the user doesn't necessarily change all the properties and raise the On...Changing event. We need to handle the case when the user doesn't fill out a field and immediately attempts to save. This partial method is called OnValidate on our LINQ to SQL classes. This method is called automatically by the DataContext right before it attempts to submit the changes to the database when SubmitChanges is called.

Because you have to run the rules in both of these situations it's probably easier to create a private method that checks each rule and call that method from both Partial On...Changing and OnValidate methods. If the rule is broken then we just call AddError to add the error message to the dictionary, otherwise we call RemoveError to remove it from the dictionary. Here's the code for my Order Partial class:

Partial Class Order
    Inherits BaseBusiness

    Private Sub OnCustomerIDChanging(ByVal value As Integer)
        Me.CheckCustomerID(value)
    End Sub

    Private Sub OnOrderDateChanging(ByVal value As Date?)
        Me.CheckOrderDate(value)
    End Sub

    Private Sub OnShipDateChanging(ByVal value As Date?)
        Me.CheckShipDate(value)
    End Sub

    Private Sub OnValidate(ByVal action As System.Data.Linq.ChangeAction)
        Me.CheckCustomerID(Me.CustomerID)
        Me.CheckOrderDate(Me.OrderDate)
        Me.CheckShipDate(Me.OrderDate)

        If Me.HasErrors Then
            Throw New ValidationException(Me.Error)
        End If
    End Sub

    Private Sub CheckCustomerID(ByVal value As Integer)
        If value < 1 Then
            Me.AddError("CustomerID", "Customer cannot be empty.")
        Else
            Me.RemoveError("CustomerID")
        End If
    End Sub

    Private Sub CheckOrderDate(ByVal value As Date?)
        If value.HasValue Then
            If value > Me.ShipDate Then
                Me.AddError("OrderDate", "Order date cannot be after the ship date.")
            Else
                Me.RemoveError("OrderDate")
            End If
        End If
    End Sub

    Private Sub CheckShipDate(ByVal value As Date?)
        If value.HasValue Then
            If value < Me.OrderDate Then
                Me.AddError("ShipDate", "Ship date cannot be before the order date.")
            Else
                Me.RemoveError("ShipDate")
            End If
        End If
    End Sub

    Public Overrides ReadOnly Property HasErrors() As Boolean
        Get
            If Not MyBase.HasErrors Then
                'Returns True if any order details are invalid 
                For Each detail In Me.OrderDetails
                    If detail.HasErrors Then
                        Return True
                    End If
                Next
            End If
            Return MyBase.HasErrors
        End Get
    End Property

End Class

So here we are calling our validation methods from the On...Changing as well as the OnValidate partial methods. If you throw an exception from OnValidate then that will halt the SubmitChanges from going further. I created my own ValidationException class that we can use to check from our save code on the form. I also included any Order Detail errors in the HasErrors property of this Order class. This means that if any order details have errors then this Order also reports that HasErrors is True.

Displaying Validation Errors in the UI

Now that we have our business rules implemented we can hook up the UI to display these messages. Objects that are being displayed in a DataGridView will automatically pick up our validation messages but for simple controls like textboxes we need to hook up an ErrorProvider. Just drag the ErrorProvider from the toolbox onto your form and then specify the BindingSource as the DataSource property, for this example it's the OrderBindingSource. That's it. The ErrorProvider will look for any error messages on our objects through the IDataErrorInfo interface. This occurs when a property changes. For instance, if I run the form now and change the Order Date to be after the Ship Date, an error will be displayed as I tab off of the Order Date:

In order to display the messages after validation fails when we attempt to submit changes to the database, we need to write some code to refresh the display. In this scenario we also need to check the OrderBindingSource's position to make sure that the user is sitting on the invalid Order so the visuals are clear as to what needs fixing. This is why I created my own exception class called ValidationException and added a property to our BaseBusiness class called HasErrors, so that we could easily handle this case.

So back in our form we'll write the following Save code:

Private Sub OrderBindingNavigatorSaveItem_Click() _
    Handles OrderBindingNavigatorSaveItem.Click

    Me.Validate()
    Me.OrderBindingSource.EndEdit()
    Me.OrderDetailsBindingSource.EndEdit()

    Try
        db.SubmitChanges()

        MsgBox("Your data was saved.")

    Catch ex As ValidationException
Me.DisplayErrors() MsgBox("Please correct the errors on this form before saving.") Catch ex As Exception MsgBox(ex.ToString) End Try End Sub ''' <summary> ''' Displays any error information and navigates to the first error row ''' </summary> ''' <remarks></remarks> Private Sub DisplayErrors() Me.ErrorProvider1.UpdateBinding() Me.OrderDetailsDataGridView.Refresh()
    If Me.OrderBindingSource.Position > -1 Then
        Dim currentOrder As Order = CType(Me.OrderBindingSource.Current, Order)

        If Not currentOrder.HasErrors Then
            'The error is not in view so navigate to it
            For i = 0 To Me.OrderBindingSource.Count - 1
                Dim order As Order = CType(Me.OrderBindingSource(i), Order)
                If order.HasErrors Then
                    Me.OrderBindingSource.Position = i
                    Exit Sub
                End If
            Next
        End If
    End If
End Sub

To test this, add a new Order then navigate away from it. When you click Save the validation will fail and you will be positioned back on the order that failed.

I placed the code for this article (including the previous article code on this topic) into a Code Gallery project for you to play with.

Enjoy!

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# BioSensorAB » Simple Validation with LINQ to SQL Classes said on February 25, 2008 8:20 PM:

PingBack from http://www.biosensorab.org/2008/02/25/simple-validation-with-linq-to-sql-classes/

# どっとねっとふぁんBlog said on February 26, 2008 12:01 AM:

Simple Validation with LINQ to SQL Classes LINQ to SQLクラスを拡張し、Windowsアプリのデータチェック機能と連動させる方法がまとめられています。...

# karl1406 said on February 26, 2008 8:44 AM:

Beth,

Fantastic article.  You are a SUPER teacher.

Question:  What is your approach to the problem of using generated entity classes that require custom attributes?

Example:  How would you implment the Enterprise VALIDATION application block with the generated classes?  

The VAB requires attributes be applied to individual properties.  Since the designer is creating the entity classes and we are being told not to mess with these, how can we get the attributes on the enitity properties?

Best to you.

Karl

# rogerj said on February 26, 2008 12:23 PM:

Beth,

Great tutorial!

Your tutorials have become to VB projects what Scott Guthrie's are to C# and ASP.NET projects.

--rj

# Beth Massi said on February 26, 2008 1:21 PM:

Hi Karl,

You can actually create your own business classes and have them work with the DataContext manually so you don't need to have the O/R designer generate the classes if you don't want. That gives you complete control. Jim Wooley has some great examples in these webcasts:

http://www.aspnetpodcast.com/VideoFiles/ASPNETPodcast20070628-LINQPart1WithJimWooley.wmv

http://www.aspnetpodcast.com/VideoFiles/ASPNETPodcast20070708-LINQPart2WithJimWooley.wmv

http://www.aspnetpodcast.com/VideoFiles/ASPNETPodcast20070711-LINQPart3WithJimWooley.wmv

# Beth Massi said on February 26, 2008 1:22 PM:

Hi Roger,

THANKS!

-B

# karl1406 said on February 28, 2008 8:59 AM:

Beth,

I'm hoping to be able to use the VS 2008 O/R Designer.  A lot of work went into this tool and it does a good job of generating business layer entities that are extendable.

I can ripe all my business layer code apart and remove the codeless declarations that drive it and place similar code in methods.  In other words, no longer use attributes and place the rules in code.  I'm OK with this.

Doing this will make my Partial class compatbile with the VS O/R classes.

However, I have one issue that I'm hoping you can provide some direction so that I can take this business layer I've written and use it with the VS O/R code.

I have posted a question on the LINQ forum.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2920231&SiteID=1

Basically I need to be able to make one VERY minor change to the code that is outputted.  Basically, I need to pass the OnChanging ByRef instead of ByVal so that my business layer can alter the inputted value.  My business layer adjusts the case of the text based on business rules.  This is the be location that I could find to inject this behavior.

I'm hoping that there is a template somewhere that can be modified.  I looked but could find one.

I know that I can purchase Code Smith, write the classes by hand or generate them myself, I'm just hoping that there is a way to integrate the classes with what VS generates.

Thank you!

# Beth Massi said on February 28, 2008 12:25 PM:

Hi Karl,

You can achieve the same results by adding the code that modifies the property into the On...Changed() partial method. For instance, in the above example for my customer partial class I could write it like this:

Partial Class Customer

   Inherits BaseBusiness

   Private Sub OnLastNameChanging(ByVal value As String)

       Me.CheckLastName(value)

   End Sub

   Private Sub OnValidate(ByVal action As System.Data.Linq.ChangeAction)

       Me.CheckLastName(Me.LastName)

       If Me.HasErrors Then

           Throw New ValidationException(Me.Error)

       End If

   End Sub

   Private Sub CheckLastName(ByRef value As String)

       If String.IsNullOrEmpty(value) Then

           Me.AddError("LastName", "Customer name cannot be empty.")

       Else

           Me.RemoveError("LastName")

           value = value.ToUpper

       End If

   End Sub

   Private Sub OnLastNameChanged()

       Me._LastName = Me.LastName.ToUpper

   End Sub

End Class

HTH,

-B

# karl1406 said on February 28, 2008 1:39 PM:

Beth,

Thank you very much for a SUPER fast response and great example.  

When I looked at the entity classes that were generated, with all the private _LastName variables, my brain didn't connect the dots that I had access to then in my partial class.  (ooooops).

Cheers,

Karl

# Courtney Smith said on March 2, 2008 5:36 AM:

Hi Beth,

I've just started using Linq and VB.NET and have to say your tutorials are an excellent source of information!

I was just wondering if you've come across a problem with partial classes and the object datasource.

Eg.

- Create a Linq-SQL Entity with the OR tool.

- Add a new property to the partial class for the entity.

- Create an object datasource for that Entity

In the object datasource the new property is not shown.  It seems to only be displayed if I add it directly to the designer file (which then automatically gets removed at times).

Am I doing something wrong?  Is there something special I have to do to get the properties from the partial class to show in the object datasource?

Thanks for your help.

Kind Regards,

Courtney

# K.HABTAY said on March 4, 2008 7:13 AM:

I WANT TO LEARN MORE ABOUT YOUR EXPERIENCE AS SYSTEMS SPECAILIST  FOR I AM JUST BEGINNING TO LEARN ABOUT THIS SUBJECT.

# Josh F said on April 9, 2008 2:32 PM:

Pulling my hair out. Using VS 2008, the generated classes do not implement INotify*. Any help is greatly appreciated.

# Beth Massi - Sharing the goodness that is VB said on April 12, 2008 12:27 PM:

In my previous posts on LINQ to SQL I showed how to build LINQ to SQL classes and set up the data binding

# Beth Massi - Sharing the goodness that is VB said on April 14, 2008 2:08 PM:

In my last post we built the service and data access layer for our LINQ to SQL N-Tier application. In

# Beth Massi said on April 21, 2008 3:07 PM:

Hi Josh,

Are you using the RTM version of VS 08 (not beta, right)? If so, when you "Add New Item" to your project and select "Linq to SQL Classes", then drag a SQL-Server table onto the design surface it will generate the class definition in the designer file. (You need to "show all files" to see this file). The interfaces are implemented there. Are you sure you aren't looking in the partial class file (where your custom code belongs)?

HTH,

-B

# Brian Ilyas said on April 22, 2008 3:53 PM:

Great work.

There are couple of issues, when you add new record error info does not show up.

# Matthew from Sydney said on April 24, 2008 7:33 PM:

I had already written something similar to your BaseBusiness class except I was using a generic list rather than the dictionary object.

       Public ColumnErrors As New List(Of ColumnError)

       Public Class ColumnError

           Public PropertyName As String

           Public ErrorText As String

       End Class

I have now changed my code now to use the dictionary object which I think is much more elegant - thanks for that!

I have used ADO.NET heavily until Linq to SQL arrived in VB9 and vastly prefer this approach for the data layer.

Beth, I enjoy reading you fantastic blogs - keep up the good work!

-Matt

# Brian in England said on May 3, 2008 9:15 AM:

Ok so we can validate, but how can we set defaults for new rows?

# Beth Massi said on May 6, 2008 2:05 PM:

Hi Brian,

It depends on how you implement the list of business objects. In this example I'm just setting the results of the query directly to the DataSource so there is no default behavior. If you place the results of the query into your own subclass of the BindingList for instance, then you can override the AddNewCore method.

This is the same principle as normal object binding to your collections of business objects. Here's one of my favorite books on data binding: http://www.amazon.com/Data-Binding-Windows-Forms-2-0/dp/032126892X

HTH,

-B

# Josh F said on May 10, 2008 2:59 PM:

Beth,

Thank you for the reply, and I apologize for the long delay.

Yes, I'm using VS 2008 RTM. And, yes, I'm looking at the designer file. Still losing hair!

Cheers,

Josh

# Beth said on May 10, 2008 3:26 PM:

After adding a primary key to my table, the code generated the INotify* implementation.

Thanks again,

Josh

# MikeBB said on May 12, 2008 9:52 AM:

Hi! I am sorry bad english. How validate work if i delete row

Orders.DeleteOnSubmit(dtl_item);

How recognize row state in LINQ? But delete row don't validate!

# MikeBB said on May 13, 2008 2:20 AM:

Hi! Validate and delete row. If use ADO.Net we write validate code ( that makr before save)

           foreach (DataSet.SchetRow row in Schet.Rows)

           {

               switch (row.RowState)

               {

                   case DataRowState.Added:

                   case DataRowState.Modified:

                       row.ClearErrors();

                       if (row.Isrsh_numNull())

h. e. we don't validate delete row! How true validate in LINQ?

# pat said on May 14, 2008 5:53 PM:

Hi,

do you have an example to implement on an asp.net page?

thanks

# MikeBB said on May 15, 2008 4:41 AM:

Hi Beth! May be you don't understand me? You make test

1. Edit master- detail documnet

2 In detail part add some row and delete theirs

3 if detail class has validation

 partial class detail

   {

       partial void OnamountChanging(decimal? value)

       {

           Checked_amount(value);

       }

       private void Checked_amount(decimal? value)

       {

       }

       partial void OnValidate(ChangeAction action)

       {            

           Checked_amount(this.amount);

           if (HasErrod)

               throw new Exception("No amount");

       }

}

4. Save document

datacontext.SubmitChanges();

5 This method call  OnValidate and delete row are present in process validate!!!

Leave a Comment

(required) 
(optional)
(required) 

About Beth Massi

Beth Massi is a Program Manager on the VS Community Team working with the Visual Basic Team producing developer content on MSDN and her blog (http://blogs.msdn.com/bethmassi). As a VB community champion and a member of the Microsoft community she helps run a .NET user group in the San Francisco Bay Area and is a frequent speaker at various software development events. Before Microsoft she was a Senior Systems Architect at a health care software product company and was a Microsoft Solutions Architect MVP. Over the last decade she has worked on distributed applications and frameworks using Visual Basic.NET, ASP.NET, SQL-Server, and Visual FoxPro. She has worked on various projects including developing object-oriented middle-tier frameworks, COM, .NET, Web and Windows-based applications using Microsoft development tools for a variety of businesses. She loves teaching, mountain biking, and modifying cars.

This Blog

Syndication

Page view tracker