0

I'm very new to EF4. I've posted a couple of times I think regarding inheritance, validation but my overall aim is to reduce the amount of code I write as much as possible. I'm not interested (yet) in POCOs, masses of ObjectContext fiddling: I want the benefit of EF and minimum of coding.

So, the thorny issue of validation. Take a look at this simplified example and (aside from DRY Buddies and dodgy usings aliases), is this looking like a half-decent approach?

namespace Model
{
    using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
    using DataAnnotations = System.ComponentModel.DataAnnotations;
    using Validation = Microsoft.Practices.EnterpriseLibrary.Validation;

    [HasSelfValidation]
    [DataAnnotations.MetadataType(typeof(PersonValidator))]
    public partial class Person
    {
        [SelfValidation]
        public Validation.ValidationResults Validate()
        {
            var validationResults = Validation.Validation.Validate(this);

            if (string.IsNullOrEmpty(this.LastName) || this.LastName.Length > 4)
            {
                validationResults.AddResult(new Validation.ValidationResult("This is a test error message for a custom validation error.", this, null, null, null));
            }

            return validationResults;
        }
    }

    [HasSelfValidation]
    public class PersonValidator
    {
        [NotNullValidator(MessageTemplate = "First Name must be supplied.")]
        [ContainsCharactersValidator("Rcd", ContainsCharacters.All, MessageTemplate = "{1} must contains characters \"{3}\" ({4}).")]
        [StringLengthValidator(5, 50, MessageTemplate = "{1} (\"{0}\") must be between {3} ({4}) and {5} ({6}) characters in length.")]
        public string FirstName { get; set; }

        [NotNullValidator(MessageTemplate = "Last Name must be supplied.")]
        [ContainsCharactersValidator("Wes", ContainsCharacters.All, MessageTemplate = "{1} must contains characters \"{3}\" ({4}).")]
        [StringLengthValidator(5, 50, MessageTemplate = "{1} (\"{0}\") must be between {3} ({4}) and {5} ({6}) characters in length.")]
        public string LastName { get; set; }
    }
}

There's something rather cool about this. I can call the above like this:

var validationResults = person.Validate();

BUT, if I just want some basic checking, I can strip out Validate(), the [SelfValidation] stuff, keep the attributes and then just call:

var validationResults = Validation.Validate(person);

I only need to include as much validation as I need and there's ZERO configuration in web.config.

How's the cut of my jib? :)

Richard

Steven
  • 166,672
  • 24
  • 332
  • 435
Richard
  • 5,810
  • 6
  • 29
  • 36
  • personally, i don't think you should validate in your model for things like "length of lastname". That is validation of *input*, which should be done in the presentation tier. Last-minute validation should be done by EF itself which will validate the fields against the underlying database constraints (e.g `NVARCHAR(4)` - which prevents anything > 4 characters). at what point in your application would you call `person.Validate`? – RPM1984 Dec 07 '10 at 02:39
  • Like I said, a simple example. Imagine it in the context of a real world app with real world validation that would take too long to code up as an example. – Richard Dec 07 '10 at 03:17
  • I guess (again simply!) call like:using (var context = new ModelContainer()) { var person = new Person(); var validationResults = Validation.Validate(person); if (validationResults.IsValid) { context.AddToPeople(person); } else { WriteValidationResults(validationResults); }} but that EF stuff is still new to me, so... – Richard Dec 07 '10 at 03:18
  • CTP 5 has DataAnnotations validation built in. Can you wait? – Craig Stuntz Dec 08 '10 at 20:33
  • I recently noticed the built in support CTP 5 has for DataAnnotations BUT seems code-only at this point? I want model / database first. Typical :) For now, I'm going with the answer below and I gues it'll be happy refactoring time in 6 months or so. – Richard Dec 23 '10 at 23:46

1 Answers1

1

I'm personally not a fan of calling validation directly in code, and especially not directly on a entity itself. There will be a lot of places were you will call Validate and it is easy to forget to call Validate. Instead, let the ObjectContext invoke the underlying validation framework automatically for ALL entities that have changed, and throw a special exception (that can be caught in the presentation layer) when validation errors occur.

You can do this by hooking onto the ObjectContext.SavingChanges event and trigger validation there. You can write your partial ObjectContext as follows:

public partial class ModelContainer
{
    partial void OnContextCreated()
    {
        this.SavingChanges +=
            (sender, e) => Validate(this.GetChangedEntities());
    }

    private IEnumerable<object> GetChangedEntities()
    {
        const EntityState AddedAndModified =
            EntityState.Added | EntityState.Modified;

        var entries = this.ObjectStateManager
            .GetObjectStateEntries(AddedAndModified);

        return entries.Where(e => e != null);
    }

    private static void Validate(IEnumerable<object> entities)
    {
        ValidationResults[] invalidResults = (
            from entity in entities
            let type = entity.GetType()
            let validator = ValidationFactory.CreateValidator(type)
            let results = validator.Validate(entity)
            where !results.IsValid
            select results).ToArray();

        if (invalidResults.Length > 0)
            throw new ValidationException(invalidResults);
    }    
} 

You can read more about it here.

Steven
  • 166,672
  • 24
  • 332
  • 435