Validation Method Timing in LINQ To SQL
The LINQ To SQL designer-generated classes contain 3 different ways to implement validation based on property values. Two of these are internal, in the form of partial methods, and one is external in the form events on the domain classes themselves. However, the timing around these differs slightly, so I'm going to examine them according to the timing.
To look into these approaches, I'm using the Customers table from Northwind. I've got it on my LINQ To SQL designer, and I've implemented the following additional partial class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
public partial class Customer
{
partial void OnCityChanging(string value)
{
if (value == "Cape Town")
throw new Exception("Not in my town!");
}
partial void OnCityChanged()
{
if (this.City == "Johannesburg")
throw new Exception("What are you doing up there?!");
}
}
}
In this class, you can see that I'm validating the "city" property both before and after it is set. What is important is that this validation code will fire directly during the property setter on the domain class. For instance, an exception will be thrown for both of the following two cases:
Customer c = new Customer();
c.City = "Cape Town";
and
Customer c = new Customer();
c.City = "Johannesburg";
and in fact the validation is roughly equivalent in terms of timing because both of these methods are called during the property setter, as the following code snippet from the designer-generated code show:
[Column(Storage="_City", DbType="NVarChar(15)")]
public string City
{
get
{
return this._City;
}
set
{
if ((this._City != value))
{
this.OnCityChanging(value);
this.SendPropertyChanging();
this._City = value;
this.SendPropertyChanged("City");
this.OnCityChanged();
}
}
}
Of course, this also implies that validation outside of the Customer class structure (i.e. using the PropertyChanging or PropertyChanged events, fired via the SendPropertyChanging and SendPropertyChanged methods, respectively) will occur at the same. As a point of common sense, I would recommend doing the validation in the "changing" methods because then the value has not yet in fact been set and the change can be effectively "rolled back".
In contrast to the above approach, it is possible to subsume all of your validation logic into one place and implementing the OnValidate partial method, as follows:
partial void OnValidate(System.Data.Linq.ChangeAction action)
{
if (this.ContactName == "Hilton Giesenow")
throw new Exception("He's too young for Northwind!!");
}
But this method has two important points of interest. First off, it allows you to potentially control the type of change that is occurring in the database (via the ChangeAction enumeration, which has values of None, Insert, Update and Delete). In addition, the timing is quite different on this method - it only fires as part of the final SubmitChanges method call on the datacontext - i.e. when the database call is about to be made. If you are creating a new instance, for instance for an Insert, and setting the properties just before the call to SubmitChanges, the difference in timing between these two approaches is basically academic. However, if you are creating a new Customer and setting properties as part of a series of UI operations for instance, and only planning to make the SubmitChanges database call at a later stage, this difference is of some importance.
I would suggest that the safest approach is to combine the two by moving the actual validation logic out into a separate method (to keep it DRY, of course) and calling it from both places, like so:
partial void OnValidate(System.Data.Linq.ChangeAction action)
{
ValidateCity(this.City);
}
partial void OnCityChanging(string value)
{
ValidateCity(value);
}
partial void OnCityChanged()
{
ValidateCity(this.City);
}
protected void ValidateCity(string value)
{
if (value == "Cape Town")
throw new Exception("Not in my town!");
else if (value == "Johannesburg")
throw new Exception("What are you doing up there?!");
}
(I know I've removed the ContactName validation and replaced it with the ValidateCity call in the OnValidate - I was trying to illustrate the consolidated approach).
This approach doesn't cover the external clients, but this sort of validation should be internal to the class anyway.
For an different angle on this stuff with some great additional areas of coverage, check out Simple Validation with LINQ to SQL Classes by Beth Massi.