Partager via


Tip 27 – How to Implement BeforeSave Validation

It is common to want to validate that your entities are ‘valid’ before you save them to the database.

A naive form of validation might be to try to ensure that your entities are ‘valid’ before you add them to the context, but this doesn’t help you if you pass through ‘invalid’ states, i.e. as you build a graph, but much more importantly this naive approach fails completely for update.

What you really need is something to tell you what you are attempting to save when you call SaveChanges(), so you can perform validation at exactly the right time.

How do you do this?

The first thing we need is a Validate() method on our custom Context, that looks through the ObjectStateManager and validates all the entities.

You can do this by adding a Validate method to your context (in a partial class) that looks something like this:

public void Validate()
{
var stateEntries = ObjectStateManager.GetObjectStateEntries(
                              EntityState.Added |
                              EntityState.Modified |
EntityState.Deleted )
.Where(e => e.Entity is IValidingEntity);

    foreach (var stateEntry in stateEntries)
{
var entity = stateEntry.Entity as IValidingEntity;
entity.Validate(stateEntry.State);
}
}

As you can see this code looks for all ObjectState entries that are Added, Modified or Deleted. We ignore Detached and Unchanged because nothing interesing can happen!

Then if those entities implement IValidatingEntity we call the validate method on the entity passing in the entity state (the validation logic could be different in different states, as you will see in my sample below).

The IValidatingEntity definition is very simple it looks like this:

public interface IValidingEntity
{
void Validate(EntityState state);
}

Now all we need to do is make the classes we want validated implement this interface. That’s very easy just add a partial class.

This example provides validation logic for the Account Transfer entity:

public partial class AccountTransfer: IValidatingEntity
{
  public void Validate(EntityState state)
{
if (state == EntityState.Added)
{
         if (this.Amount <= 0)
throw new InvalidOperationException(“Transfers must have a non-zero positive value”);

if (this.SourceAccount == null)
throw new InvalidOperationException(“Transfers require a source account”);

         if (this.TargetAccount == null)
throw new InvalidOperationException(“Transfers require a target account”);

if (this.SourceAccount == this.TargetAccount)
throw new InvalidOperationException(“Transfers must be between two different accounts”);
}
else if (state == EntityState.Modified)
{
throw new InvalidOperationException(“Modifying a transfer is not allowed, make a correcting transfer instead”);
}
else if (state == EntityState.Deleted)
{
throw new InvalidOperationException(“Deleting a transfer is not allowed, make a correcting transfer instead”);

}
}
}

Now you can do you validation on the Context like this:

using (BankingContext ctx = new BankingContext())
{
… // Random activities
ctx.Validate();
ctx.SaveChanges();
}

But it would be easy to forget to call Validate() so it would be better if we could get the validation to happen as part of SaveChanges();

Well it turns out that isn’t hard either, all you need to do is override the partial OnContextCreated() method, and in it hook up an event handler for OnSavingChanges() and in there call the Validate() method, like this:

partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(BankingContext_SavingChanges);
}

void BankingContext_SavingChanges(object sender, EventArgs e)
{
Validate();
}

And now whenever you call SaveChanges() validation happens automatically.

Cool huh.

This code works just fine in either 3.5 or 4.0, but you can definitely make it ‘better’ in 4.0.

In fact Danny Simmons demonstrate this at TechEd by customizing a T4 templates so all this code gets built straight into the Context and Entities. He made the Validate method on the Entities call a partial method, so by default calling Validate does nothing, but if you need validation all you need to do is add an implementation of the partial method to a partial class.

You could even write a T4 template for 3.5 that does much the same thing too.

Anyway there are lots of options here, so have fun!

Caveats:

With things as complicated as validation, there are always Caveats!

So please be aware that if as part of your validation logic you make some additional changes or additions, those new changes won’t be validated. This is because the list of changed entities is ‘established’ before those changes.

Generally this is not a problem though because you would hope your validation logic puts things into valid state :)

But please be aware of this limitation.

Comments

  • Anonymous
    July 20, 2009
    Hi,this is more or less the same as I showed in http://blog.cincura.net/229056-onvalidate-like-validation-in-entity-framework/ .

  • Anonymous
    July 21, 2009
    Throwing errors instead of using proper validation techniques with IDataErrorInfo is just silly.  Errors have huge overhead and IDataErrorInfo does not. Why MS insists on throwing errors on validation stuff in their samples I don't know.  Using IDataErrorInfo also gives you the benefit of automatic error reporting in WPF, Winforms, and Silverlight which throwing an error does not. In addition to Validation, there is always cases where you want to do something after a successful save of an object (i.e. add more objects). there should be a Saved Event that can be handled to add additional logic.

  • Anonymous
    July 26, 2009
    Hopefully there will be hooks for this baked into EF 4.0 so validation can be more standardized.

  • Anonymous
    July 26, 2009
    @JohnGaltI hear you. IDataErrorInfo is something I personally wasn't aware of, but it seems like a pretty good option.As for a Saved event. I wish it could be done easily, but the fact remains, do you have a Saved event for things Saved in a handle for the Saved event...Alex