Partilhar via


EF 4.1 Validation

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.

For Validation, see msdn.com/data/gg193959.


 

Validation is a feature added in CTP5 and we already have a blog post describing it. But since then we received a lot of feedback and made a number of changes for 4.1 RTW. Before proceeding please read the CTP5 validation blog post, as it introduces a number of useful concepts.

 

Validation changes in 4.1 RTW since CTP5:

Here is a list of the design changes we made. We will look into each of them in detail in this post:

  • Database First and Model First are now supported in addition to Code First
  • MaxLength and Required validation is now performed automatically on scalar properties if the corresponding facets are set.
  • LazyLoading is turned off during validation.
  • Validation of transient properties is now supported.
  • Validation attributes on overridden properties are now discovered.
  • DetectChanges is no longer called twice in SaveChanges.
  • Exceptions thrown during validation are now wrapped in a DbUnexpectedValidationException.
  • All type-level validators are treated equally and all are invoked even if one or more fail.

 

Database First and Model First

We were planning to release this in CTP5, but there were still design issues that needed to be resolved.

To use validation with the more traditional Database First and Model First approaches you will need to create a class derived from DbContext for your model, you can read how to do this in the Model & Database First Walkthrough.

You can enable validation in two ways – for Required and MaxLength validation you can modify corresponding facets accordingly (read on for more details). Another option is to decorate properties on the generated entity or complex type classes with validation attributes. Unfortunately this approach does not really work for generated code – the changes will be overwritten every time the code is regenerated. You can avoid this by adding validation attributes by using an Associated Metadata class as described in this blog post.

 

Facet validation

There are two property facets in EDM that contain information that can be validated: IsNullable and MaxLength. These facets are now validated by default:

  • IsNullable = false is equivalent to [Required(AllowEmptyStrings = true)]
  • MaxLength = 10 is equivalent to [MaxLength(10)]

If you need different validation behavior you can place one of the above attributes (or StringLength) on the property to override the default.

Since the MaxLength facet is now more important you need to ensure that it’s set to an appropriate value in your model. Also read about the EF 4.1 RTW Change to Default MaxLength in Code First

There are however certain properties that are excluded from facet validation:

  • Store generated properties, as the value might not be set at the moment when validation runs
  • Complex properties, as they should never be null
  • Navigation properties, as you could set the associated FK value and the navigation property would be set on SaveChanges()
  • Foreign key properties, for the inverse of the above

 

Lazy Loading

Lazy loading is now disabled during validation. The reason this change was made is because when validating a lot of entities at once the lazily loaded properties would get loaded one by one potentially causing a lot of unexpected database round-trips and crippling performance.

To avoid receiving validation errors due to navigation properties not being loaded you need to explicitly load all properties to be validated by using .Include(), you can read more on how to do this here: Using DbContext in EF 4.1 Part 6: Loading Related Entities.

 

Transient Properties

Transient properties are properties on your CLR types that aren’t mapped to a property in the corresponding entity type in the model. Entity Framework ignores these properties for persistence purposes. Now validation attributes placed on them will be picked up. However unlike complex properties we don’t drill down into the transient properties that are not of scalar type.

There are some requirements that the transient properties must meet in order to be validated – they must not be static or an indexer and should have a public getter.

You could use transient properties for some simple custom validation:

 

public class Client

{

    public int Id { get; set; }

    public string HomePhone { get; set; }

    public string WorkPhone { get; set; }

    public string CellPhone { get; set; }

 

    [MinLength(1)]

    public string[] Phones

    {

        get

        {

            var phones = new List<string>();

            if (!string.IsNullOrEmpty(HomePhone)) phones.Add(HomePhone);

            if (!string.IsNullOrEmpty(WorkPhone)) phones.Add(WorkPhone);

            if (!string.IsNullOrEmpty(CellPhone)) phones.Add(CellPhone);

            return phones.ToArray();

        }

    }

}

 

This will validate that at least one phone number was supplied.

 

Overridden Properties

Validation attributes defined on properties of a parent class that were overridden or hidden are now preserved as well as the validation attributes on the parent class itself, even if they aren’t mapped in the model.

Note: the validation attributes on properties defined in interfaces aren’t preserved as that would lead to conflicts that can’t be reliably resolved.

 

SaveChanges

In CTP5 by default SaveChanges called DetectChanges, then validated the modified entities and then called DetectChanges again to detect any changes to entities made during validation. DetectChanges is potentially an expensive operation and the built-in validation attributes will not make any changes. For this reason we decided to change validation to stop invoking DetectChanges for a second time after validation.

If you have custom validation logic that can cause property values to change, you can still invoke DetectChanges explicitly as part of your validation logic.

 

Exceptions

By contract validators indicate validation error by returning a ValidationResult and not by throwing an exception. The built-in validation attributes will not throw exceptions unless they can’t validate a property because either the attribute was not used correctly (vide InvalidCastException when MaxLength attribute is put on DateTime property) or potentially because there is a bug in the attribute implementation. If it does happen it may become difficult to find out which validation attribute is causing the exception from the stack trace. To alleviate this we are now wrapping exceptions thrown during validation in a DbUnexpectedValidationException that contains additional information in the error message to help you find the root cause.

 

Validator Execution Order

An often overseen behavior is that some validators will short-circuit on a failure to avoid exceptions or redundant error messages. In general property level validation is performed before type level validation. But type-level validation doesn’t run if property validation returns errors as commonly type-level validation checks the relationship between properties and assumes that property-level validation succeeded. If the property-level validation for a complex property returns errors complex type validation won’t run as usually this means that the property value is null.

This is the order in which validators are run. In each step all matching validators are executed, but the execution stops at the first step that returns errors:

  • Property-level validation on the entity and the base classes. If a property is complex its validation would also include:
    • Property-level validation on the complex type and its base types
    • Type level validation on the complex type and its base types, including IValidatableObject validation on the complex type
  • Type-level validation on the entity and the base entity types, including IValidatableObject validation

Note: you shouldn’t assume any particular order for same-level validation. For example if a property has a Required attribute and a CustomValidation attribute you still need to handle the null case in the custom validation implementation as it may be the first to be invoked:

 

public class Person

{

    public int ID { get; set; }

 

    [Required]

    [CustomValidation(typeof(Person), "ValidateName")]

    public string FirstName { get; set; }

 

    [Required]

    [CustomValidation(typeof(Person), "ValidateName")]

    public string FirstName { get; set; }

 

    public static ValidationResult ValidateName(string value)

    {

        if (value == null)

        {

            return ValidationResult.Success;

        }

 

        if (value.Contains(" "))

        {

            return new ValidationResult("Names shouldn't contain spaces.");

        }

 

        return ValidationResult.Success;

    }

}

 

Custom validation sample: Uniqueness

One of the most requested validation features is uniqueness validation. In the general case to verify that a property value is unique the store needs to be queried. EF validation avoids store queries by design that’s why we didn’t ship a uniqueness validation attribute. However the upcoming Unique Constraints feature will allow you to enforce uniqueness in the store. But before it is shipped you could just use this simple custom validator that might be enough depending on your scenario.

You need the context to validate uniqueness, so it should be added to the “items” custom dictionary used by validators:

 

public class StoreContext : DbContext

{

    public DbSet<Category> Categories { get; set; }

    public DbSet<Product> Products { get; set; }

 

    protected override DbEntityValidationResult ValidateEntity(

        DbEntityEntry entityEntry, IDictionary<object, object> items)

    {

        var myItems = new Dictionary<object, object>();

 

        myItems.Add("Context", this);

 

        return base.ValidateEntity(entityEntry, myItems);

    }

}

 

Having the context the rest is fairly straightforward:

 

public class Category : IValidatableObject

{

    public int CategoryID { get; set; }

    public string CategoryName { get; set; }

    public string Description { get; set; }

 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)

    {

        var storeContext = (StoreContext)validationContext.Items["Context"];

 

        if (storeContext.Categories.Any(

            c => c.CategoryName == CategoryName && c.CategoryID != CategoryID))

        {

            yield return new ValidationResult("A category with the same name already exists!", new[] { "CategoryName" });

        }

 

        yield break;

    }

}

 

Summary

In this post we looked at the new validation features and changes introduced in EF 4.1 RTW. We explored some of the reasoning behind the changes and saw a few applications of validation.

 

Feedback and support

We appreciate any feedback you may have on validation or EF 4.1 in general.

For support please use the Entity Framework Forum.

 

 

Andriy Svyryd

Developer, Entity Framework Team

Comments

  • Anonymous
    June 03, 2011
    Hi there, I've been waiting for the language packs for EF 4.1.  The default error message for validation attributes come with EF 4.1 can't be localized, I have to set every attribute with a custome error message when I think the default one is ok for me......

  • Anonymous
    June 03, 2011
    Looks great. Model first support is great but to what degree? Defining the validation in the model itself? No difference in flexibility with coded annotations I guess. And are you thinking about -standarising- rules for interfield (entity levelt) and inter entity validation without programming custom validattions?

  • Anonymous
    June 08, 2011
    Can you please clarify the exception handling process.  In the CTP5 blog post you state that DbEntityValidationException will be thrown when calling SaveChanges.  However, in this post you are stating that DbUnexpectedValidationException will be thrown.  Is the DbUnexpectedValidationException only thrown when calling GetValidationErrors()? And finally, can you point me in the right direction on how this is used with WPF applications that databind to entity collections, such as with a datagrid?

  • Anonymous
    June 10, 2011
    The comment has been removed

  • Anonymous
    June 17, 2011
    Is there any estimation, when the migration functions will appear in ef?

  • Anonymous
    June 28, 2011
    The comment has been removed

  • Anonymous
    July 03, 2011
    Are you seriously suggesting that people should start validating user input in the database model ? Validating the model before saving seems fine, but it should not be reported to the user. It is more a security feature for yourself and your business logic, which at best is caught in your tests. @Michael: I Dont see any reason why you would want to localize the validation errors. It is for internal use only and should just be logged and the developer notified.

  • Anonymous
    October 03, 2011
    The comment has been removed

  • Anonymous
    August 23, 2013
    I'm desperately looking for an explanation + example for Validation with Entity Framework 4.3, using EntityObjects, Database First, VB.NET, Winforms. NOT POCO, NOT C#, NOT Code First, NOT ASP.NET... Please Help! I've never used Facets and all explanations are more than confusing. I simply want to check String max FieldLength and NOT NULL (Required) Fields. Searching for hours and days I haven't found something useful

  • Anonymous
    January 13, 2015
    This does however tie your model (and the rest of EF) to your entities as they need to know what the context is. So I'm not sure that this is better than just doing this type of validation in ValidateEntity in the first place.

  • Anonymous
    January 15, 2015
    @Chris - For the most part EF is just delegating to the existing validation infrastructure (Data Annotations, ValidateEntity, etc.). The one time it's really useful to make use of the context etc. is when you are doing validation that requires reasoning about the model or data from the database (i.e. making sure there isn't existing data in the database with the same value). Obviously that does tie you validation to EF though.