Print

Integrating Enterprise Library Validation Application Block With LINQ to SQL and Entity Framework Part 4: Using Metadata to Automate Validations

This article describes how to extract information from your generated LINQ to SQL entities to automate validations like maximum string length and disallowing null values.

In the previous parts I wrote how to integrate the Enterprise Library Validation Application Block with LINQ to SQL and Entity Framework projects, enabled using the context within custom validators, and wrote about the complexity of custom validators.

What's nice about LINQ to SQL (L2S) is that it adds all sort of metadata to the generated entities. Two things that come to mind are the maximum length of a database varchar column and whether a column can contain null values or not. While the Validation Application Block (VAB) has validators for these types of constraints (the NotNullValidator and StringLengthValidator), adding them manually can be cumbersome. Automating away boring labour is always welcome. In this article I'll show you how, while building on top of the solution presented in the previous articles.

Please note that Entity Framework (EF) adds much less metadata to its generated entities. With EF you still have to add those validations using the VAB configuration (also remember this when migrating from L2S to EF). However, the future of EF is bright and EF 4.0 will allow you to specify your own T4 template and therefore completely control the shape of the generated code.

In part 1 and part 2 I defined the EntityValidator class. Before writing the code for the validations, let's first change the EntityValidator class, so it becomes more flexible for these and possible future changes:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;

interface IEntityValidator
{
IEnumerable<ValidationResult>
Validate(IEnumerable<object> entities);
}

public static class EntityValidator
{
private static readonly IEntityValidator[] Validators;

static EntityValidator()
{
var types = Assembly.GetExecutingAssembly().GetTypes();

var validators =
from type in types
where !type.IsAbstract
where typeof(IEntityValidator).IsAssignableFrom(type)
select (IEntityValidator)Activator.CreateInstance(type);

Validators = validators.ToArray();
}

public static void Validate(object context,
IEnumerable<object> entities)
{
using (new ContextScope(context))
{
var invalidResults = (
from validator in Validators
from result in validator.Validate(entities)
select result).ToArray();

// Throw an exception when there are invalid results.
if (invalidResults.Length > 0)
{
throw new ValidationException(invalidResults);
}
}
}
}

This new EntityValidator implementation uses a plug-in model, where different validation modules can be added without any changes to existing code (The Open/Closed principle). The plug-ins must implement the IEntityValidator interface. The static constructor of the EntityValidator loads all available plug-ins by searching for all types in the same assembly that implement the IEntityValidator interface. All found types will be instantiated and added to a static list.

Note that there are of course other possible implementations. For instance, a dependency injection framework (such as my own Simple Injector) could be used to retrieve the list of IEntityValidator implementations or you could create a plug-in model where the application loads assemblies located in a plugin directory (but I wouldn't advice this last option in this scenario). I chose this particular implementation, because it’s both flexible and just a few lines of code (less code is important, because this plug-in model isn’t the subject of this post).

The EntityValidator.Validate method uses the static list of Validators and invokes each validator's Validate method. The validate method returns a collection of VAB ValidationResult objects, one for each failed validation rule. The Validate method has become even simpler than it was before. All framework dependent validations are now extracted from this method, which is good. What is left is the infrastructure.

Note that there is a little tweak from the previous implementation: The previous implementations used a collection of ValidationResults objects (a ValidationResults object is a staticly typed collection of ValidationResult objects). In this new implementation we directly use a collection of ValidationResult objects. The extra grouping made things more difficult than strictly needed.

What we need to do next is to create some implementations of the IEntityValidator interface. Let's start with the VAB validation infrastructure:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Validation;

sealed class VABEntityValidator : IEntityValidator
{
public IEnumerable<ValidationResult> Validate(
IEnumerable<object> entities)
{
return
from entity in entities
let type = entity.GetType()
let validator =
ValidationFactory.CreateValidator(type)
let results = validator.Validate(entity)
where !results.IsValid
from result in results
select result;
}
}

This code should look pretty familiar. It's the code that was part of the EntityValidator.Validate method in part 1. With this code our solution is back to it’s old behavior.

Let's now define an entity validator that uses the LINQ to SQL metadata to validate properties that must not contain a null value:

using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;

class LinqToSqlNullPropertyValidator : IEntityValidator
{
public IEnumerable<ValidationResult> Validate(
IEnumerable<object> entities)
{
return
from entity in entities
from result in GetValidationResultsFor(entity)
select result;
}

private static IEnumerable<ValidationResult>
GetValidationResultsFor(object entity)
{
var properties = entity.GetType().GetProperties();

return
from property in properties
where property.GetValue(entity, null) == null
where !PropertyCanBeNull(property)
select new ValidationResult("The value cannot be null.",
entity, property.Name, null, null);
}

private static bool PropertyCanBeNull(PropertyInfo property)
{
object[] columnAttributes = property.GetCustomAttributes(
typeof(ColumnAttribute), true);

if (columnAttributes.Length == 0)
{
return true;
}

var column = (ColumnAttribute)columnAttributes[0];
return column.IsDbGenerated || column.CanBeNull;
}
}

<Update 2010-03-07>
There was a problem concerning database generated columns. I fixed a bug in the LinqToSqlNullPropertyValidator by checking the IsDbGenerated property.
</Update>

This relatively simple class uses LINQ extensively. Did I tell you how much I love LINQ for its expressiveness? :-). The class iterates all supplied entities and for each entity iterates its public properties to check whether it has been marked with a ColumnAttribute. If the attribute is found and it’s CanBeNull property is false while the actual property value is null, a ValidationResult will be added to the returned collection.

While validating for null values is useful, checking the string length of a property would be even more useful. Here’s the code to do this:

using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;

class LinqToSqlMaximumFieldLengthValidator : IEntityValidator
{
public IEnumerable<ValidationResult> Validate(
IEnumerable<object> entities)
{
return
from entity in entities
from result in GetMaxLengthErrorsFor(entity)
select result;
}

private static IEnumerable<ValidationResult>
GetMaxLengthErrorsFor(object entity)
{
var properties = entity.GetType().GetProperties();

return
from property in properties
where property.PropertyType == typeof(string)
let value = (string)property.GetValue(entity, null)
let length = value == null ? 0 : value.Length
where length > 0
let maximumLength = GetMaximumLength(property)
where length > maximumLength
select BuildResult(entity, property, maximumLength);
}

private static ValidationResult BuildResult(object entity,
PropertyInfo property, int maximumLength)
{
const string FormatMessage =
"The length of the value must be less " +
"or equal to {0} characters.";

string message =
string.Format(FormatMessage, maximumLength);

return new ValidationResult(message, entity,
property.Name, null, null);
}

private static int GetMaximumLength(PropertyInfo property)
{
ColumnAttribute attribute =
GetColumnAttribute(property);

if (attribute == null)
{
return Int32.MaxValue;
}

string dbType = attribute.DbType;

if (!IsTruncatableTextType(dbType))
{
return Int32.MaxValue;
}

int leftBracketIndex = dbType.IndexOf("(");
int rightBracketIndex = dbType.IndexOf(")");

try
{
string length = dbType.Substring(leftBracketIndex + 1,
rightBracketIndex - leftBracketIndex - 1);

return int.Parse(length);
}
catch (SystemException ex)
{
string message = string.Format(
"Property {0} of type {1} has a {2} defined " +
"with a DbType with value '{3}'. The maximum " +
"length of that text field can not be extracted.",
property.Name, property.DeclaringType,
typeof(ColumnAttribute), dbType);

throw new InvalidOperationException(message, ex);
}
}

private static ColumnAttribute GetColumnAttribute(
PropertyInfo property)
{
object[] columnAttributes =
property.GetCustomAttributes(typeof(ColumnAttribute), true);

if (columnAttributes.Length == 0)
{
return null;
}

return ((ColumnAttribute)columnAttributes[0]);
}

private static bool IsTruncatableTextType(string dbType)
{
StringComparison ignoreCase =
StringComparison.InvariantCultureIgnoreCase;

return
dbType != null && (
dbType.StartsWith("Char", ignoreCase) ||
dbType.StartsWith("NChar", ignoreCase) ||
dbType.StartsWith("VarChar", ignoreCase) ||
dbType.StartsWith("NVarChar", ignoreCase));
}
}

This class is slightly more complicated than the previous one, but the concept is the same. The class iterates all supplied entities and for each entity iterates the entity's public string properties to check whether its value's length is longer than the maximum that is specified in the ColumnAttribute. The DbType property of the ColumnAttribute specifies the length of the string. Problem here is that the DbType property is a string and the maximum string length is buried within that string, which might be defined as: "NVarChar(15) NOT NULL". That's why some dirty string splitting is going on.

Note that this is a bit naive implementation and it might be slow. I could imagine the class to cache the needed metadata to improve performance, but I leave this as an exercise for the reader.

<UPDATE 2010-01-31>
I did some testing to compare the difference between the naive implementation and one that caches the metadata and I did this by validating 10.000 Employee entities (since Employee is the biggest entity in the Northwind domain). I measured an overhead of 0.5 ms per validated Employee entity on my dev box. This isn't that much, but a cached approach might be useful in situations where you validate large sets of entities.
</UPDATE>

Conclusion

As you can see, after doing a bit refactoring, adding support for automatic validation according to the generated metadata is a breeze.

Happy validating.

- .NET General, C#, Enterprise Library, Entity Framework, LINQ, LINQ to SQL, O/RM, Validation Application Block - No comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

No comments:


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.