EF Feature CTP5: 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.


Validation is a new feature introduced in Entity Framework Feature Community Technology Preview 5 (CTP5) which enables to automatically validate entities before trying to save them to the database as well as to validate entities or their properties "on demand".

Why?

Validating entities before trying to save changes can save trips to the database which are considered to be costly operations. Not only can they make the application look sluggish due to latency but they also can cost real money if the application is using SQL Azure where each transaction costs. Also using the database as a tool for validating entities is not really a good idea. The database will throw an exception only in most severe cases where the value violates database schema/constraints. Therefore the validation that can be performed by the database is not really rich (unless you start using some advanced mechanisms like triggers but then – is this kind of validation really the responsibility of the database? Should not this be part of business logic layer which – with CodeFirst - may already have all the information needed to actually perform validation).

In addition, figuring out the real cause of the failure may not be easy. While the application developer can unwrap all the nested exceptions and get to the actual exception message thrown by the database to see what went wrong the application user will not usually be able to (and should not even be expected to be able to) do so. Ideally the user would rather see a meaningful error message and a pointer to the value that caused the failure so it is easy for him to fix the value and retry saving data.

How?

Automatically! In the CTP5 validation is turned on by default. It is using validation attributes (i.e. attributes derived from the System.ComponentModel.DataAnnotations.ValidationAttribute class) in order to validate entities. “Accidentaly” J one of the ways to configure a model when working with Code First is to use validation attributes. As a result when the model is configured with validation attributes validation can kick in and validate whether entities are valid according to the model. But there is more to the story.

CodeFirst uses just a subset of validation attributes to configure the model, while validation can use any attribute derived from the ValidationAttribute class (this includes CustomValidationAttribute) giving even more control over what is really saved to the database.

Validation will also respect validation attributes put on types and will drill into complex properties to validate their child properties. In addition, if an entity or a complex type implements IValidatableObject interface the IValidatableObject.Validate method will be invoked when validating the given entity or complex property.

Finally, it is also possible to decorate navigation or collection property with validation attributes. In this case only the property itself will be validated but not the related entity or entities.

By default entities will be validated automatically when saving changes. It is also possible to validate all entities, a single entity or a single property (be it complex property or primitive property) “on demand”. Each of these scenarios is described in more details below.

One important thing to mention is that in some cases validation will enforce detecting changes. This is especially visible in case of automatic validation invoked from DbContext.SaveChanges(). In this case DetectChanges() will be actually called twice. Once by validation and once by the “real” SaveChanges(). The reason for detecting changes before validation happens is obvious – the latest data should be validated because this is what will be sent to the database. The reason for detecting changes after validation is that the entities could have been changed either during validation (CustomValidationAttributes, IValidatableObject.Validate(), validation attributes created by the user have full access to entities and/or properties) or by the user in one of the validation customization points.

Another thing worth noting is that validation currently works only when doing Code First development. We are considering extending it to also work for Model First and Database First.

What’s the big deal here?

Since CodeFirst uses validation attributes you could potentially use Validator from System.ComponentModel.DataAnnotations to validate entities even before CTP5 was released. Unfortunately Validator can validate only properties that are only direct child properties of the validated object. This means that you need to do extra work to be able to validate complex types that wouldn’t be validated otherwise. Even if you do it you need to make sure that you are able to actually access the invalid value and still know which entity it belongs to – looks like a few more lines. By the. Way, is your solution generic enough to work with different models and databases? Hopefully, but making it generic must have cost another dozen of lines. Now the custom validation is probably pretty big. Hey, someone has added a few lines in OnModelCreatingMethod and that property that was attributed with [Required] validation attribute is no longer required. Now what? Validation prevents from saving a valid value to the database since the Validator is using the attribute that is no longer valid. This is kind of a problem…

Fortunately the built-in validation is able to solve all the above problems without having you add any additional code.

First, validation leverages the model so it knows which properties are complex properties and should be drilled into and which are navigation properties that should not be drilled into. Second, since it uses the model it is not specific for any model or database. Third, it respects configuration overrides made in OnModelCreating method. And you don’t really have to do the whole lot to use it.

Validation in action

Let’s take a look at some code showing validation in action. All the examples will use the following model:

    public class FlightSegment

    {

        public int FlightSegmentId { get; set; }

        [Required]

        [RegularExpression(@"^[A-Z]{2}\d{4}$")]

        public string FlightNumber { get; set; }

        public DepartureArrivalInfo Departure { get; set; }

        public DepartureArrivalInfo Arrival { get; set; }

        [StringLength(3, MinimumLength = 3)]

        public string AircraftTypeCode { get; set; }

    }

    [ComplexType]

    public class DepartureArrivalInfo

    {

        [StringLength(3)]

        [Required]

        [RegularExpression("^[A-Z]{3}$")]

        public string AirportCode { get; set; }

        [StringLength(10)]

        public string Terminal { get; set; }

       public DateTime Time { get; set; }

    }

Scenario 1: Validating entities when saving changes

In this scenario validation is the “last line of defense” before saving changes to the database. When SaveChanges() is invoked all Added or Modified entities are validated. The “Added or Modified” is the default criterion and can be changed – see the section about customization of validation for more details. If there is any entity that is not valid an exception of the DbEntityValidationException type is thrown. The exception contains a list of DbEntityValidationResult instances. Each instance of the DbEntityValidationResult class corresponds to a single invalid entity and contains a back pointer (DbEntityEntry) to the entity and the list of actual validation violations represented by DbValidationError objects.

Each DbValidationError contains the name of the invalid property and an error message explaining what went wrong. For invalid nested properties (i.e. child properties of complex properties) the property name will be in the dotted form - e.g. Arrival.AirportCode. Passing this name to DbEntityEntry.Property() method allows to get the corresponding property entry.

The way the validation errors are reported makes it possible to show detailed information about validation failures in a readable way. For instance it is possible to show in the UI the error message next to the property that caused the error. Even if there are multiple entities and multiple validation errors the user should be able to easily find the erroneous entity using the pointer to the entity entry from DbEntityValidationResult object. Being able to get a DbPropertyEntry instance for the invalid property allows always showing the incorrect value which is handy in situations where UI has been refreshed and the value is no longer displayed or when the validation errors are displayed in a different format (e.g. as a report).

In the code snippet below the FlightNumber is commented out what makes the entity invalid as the property is decorated with the [Required] attribute. In addition the arrival airport code is not valid according to the regular expression attribute the Arrival.AirportCode property is decorated with.

        using (Context ctx = new Context())

        {

            ctx.Configuration.ValidateOnSaveEnabled = true;

            var flightSegment = new FlightSegment()

            {

                // FlightNumber = "LO0365",

                Departure = new DepartureArrivalInfo() {

                    AirportCode = "WRO",

                    Terminal = "1",

                    Time = new DateTime(2010, 12, 12, 13, 05, 00)

                },

                Arrival = new DepartureArrivalInfo()

                {

                    AirportCode = "???",

                   Terminal = "2",

                    Time = new DateTime(2010, 12, 12, 14, 50, 00)

                },

                AircraftTypeCode = "AT7"

            };

            ctx.Segments.Add(flightSegment);

            ctx.SaveChanges();

When trying to save the entity to the database validation will discover that the entity is not really valid and will prevent it from being sent to the database. Rather than it will throw the DbEntityValidationException containing detailed information about invalid validation violations.

Were there not for the validation the result would be the following exception thrown by the database:

DbUpdateException: An error occurred while updating the entries. See the inner exception for details.

After unwrapping the exception we find what really caused the exception:

SqlException (0x80131904): Cannot insert the value NULL into column 'FlightNumber', table 'ValidationDemo.Context.dbo.FlightSegments'; column does not allow nulls. INSERT fails. The statement has been terminated.

We were lucky that there was only one entity to be saved and there was not any advanced mapping that would make it hard to figure out the property that corresponds to ‘FlightNumber’ column in the database. Note that the other problem caught by validation (invalid Arrival.AirportCode) was not be caught by the database since it did not violate the database schema or constraints.

Scenario 2: Getting validation errors "on demand"

Sometimes it may be more convenient to validate tracked entities before saving changes and therefore avoid the potential validation exception thrown from DbContext.SaveChanges() method. This can be accomplished by using DbContext.GetValidationErrors() method. The method will return a list of DbEntityValidationResult object that contains more detailed information about the validation violations, as described above (it should not be a surprise that under the hood SaveChanges() invokes GetValidationErrors() to get validation errors and if there are any throws DbEntityValidationException). In case where there were no validation violations the returned list will be empty. Here is the code that shows how to use the DbContext.GetValidationErrors() method:

            foreach (var entityValidationError in ctx.GetValidationErrors())

            {

                // handle validation errors...

            }

Note that DbContext.GetValidationErrors() enforces detecting changes.

Scenario 3: Validating single entity

With validation it is also possible to validate just a single entity. In this case the validation will be performed regardless of the state of the entity. Actually for this kind of validation the entity does not even need to be tracked! Calling DbEntityEntry.GetValidationResult() will cause the entity to be validated. The method returns DbEntityValidationResult instance containing details about validation errors (if any). For valid entities DbEntityValidationResult.IsValid flag will be set to true and the list of validation errors will be empty. The following code shows how to validate single entity:

        using (Context ctx = new Context())

        {

            var flightSegment = new FlightSegment()

            {

                FlightNumber = "LO365",

                Departure = new DepartureArrivalInfo()

                {

                    AirportCode = "WRO",

                    Terminal = "1",

                    Time = new DateTime(2010, 12, 12, 13, 05, 00)

                },

                Arrival = new DepartureArrivalInfo()

                {

     AirportCode = "???",

                    Terminal = "2",

                    Time = new DateTime(2010, 12, 12, 14, 50, 00)

                },

                AircraftTypeCode = "AT7"

            };

            var validationResult = ctx.Entry(flightSegment).GetValidationResult();

            if (!validationResult.IsValid)

            {

                // handle validation errors...

            }

        }

Scenario 4: Validating single property

In some cases it may be helpful to validate just a single property or a property and all its descendant properties in case of complex properties. Again, the validation introduced in the CTP5 allows for this by invoking DbMemberEntry.GetValidationErrors() method. The method returns a list of DbValidationErrors objects representing validation violations which will be empty for valid properties. The snippet below shows how to validate a single property:

        using (Context ctx = new Context())

        {

   var flightSegment = new FlightSegment()

            {

                FlightNumber = "QF6",

                Arrival = new DepartureArrivalInfo()

                {

                    AirportCode = "???",

                    Terminal = "2",

       },

            };

            foreach(var property in

                new DbPropertyEntry[] { (ctx.Entry(flightSegment).Property(p => p.FlightNumber)),

                                        ( ctx.Entry(flightSegment).Property(p => p.Arrival.AirportCode)) })

            {

                foreach (var validationError in property.GetValidationErrors())

                {

                    // handle validation errors...

                }

            }

        }

Model Configuration overrides and Validation

With CodeFirst it is possible to override the configuration of the model defined with validation attributes, e.g. in the OnModelCreating method. Reconfiguring model affects validation since validation should use the actual model configuration – blindly use of attributes would cause validation errors for values that could be valid according to the overrides made in OnModelCreating(). Here are the three special cases for overrides made in OnModelCreating:

- If a property was decorated with [Required] attribute and was reconfigured as optional (.IsOptional() method) the [Required] attribute will be removed and as a result ignored when validation happens

- If a property was decorated with [StringLength] or [MaxLength] attribute and then was configured with new length (.HasMaxLength() method) new maximum length will be used if possible

- If a property was decorated with [StringLength] or [MaxLength] attribute and then was defined to be allowed maximum length (.IsMaxLength) then the attribute will be removed (if possible) and the length of the property value will not be checked

Note that the above changes will be effective only if a property was decorated with some validation attributes. So, setting property as required (.IsRequired()) will not cause the property be validated against null value.

Customizing validation

There are a few ways to customize validation functionality. The first and the easiest one is to disable automatic validation invoked from DbContext.SaveChanges(). This can be done by setting DbContextConfiguration.ValidateOnSaveEnabled configuration setting to false (by default this value is set to true) like this:

ctx.Configuration.ValidateOnSaveEnabled = false;

Another default setting is to validate only added and modified entities when saving changes. While this should work in majority of cases sometimes it may be necessary to use different criteria to decide whether a given entity should be validated or not. Overriding the protected DbContext.ShouldValidateEntity() method allows to override the default behavior just by returning a boolean value indicating whether the given entity should or should not be validated.

DbContext.ValidateEntity() is arguably the most interesting validation customization point. This protected method is called whenever an entity is validated regardless of how the validation was actually invoked (so calling DbContext.SaveChanges(), DbContext.GetValidationErrors() or DbEntityEntry.Validate() will cause this method to be invoked). As a result overriding DbContext.ValidateEntity() allows to take full control of how entities are validated. Therefore it is possible to use custom validation logic that can completely replace built-in validation or filter out some validation errors returned by built-in validation.

One more little detail about DbContext.ValidateEntity() method is the second parameter of this method which looks like this:

IDictionary<object, object> items

By default its value is always null. However overriding DbContext.ValidateEntity() allows to pass a non-null value to this method by calling:

            return base.ValidateEntity(entityEntry, myItems);

This will result in passing myItems to the internally created ValidationContext object which is required by validation attributes or IValidatableObject interface. What this really means is that these custom items will be accessible during validation so it is possible to pass additional context that could be otherwise inaccessible. This is particularly useful for advanced validation scenarios where it is necessary to create either custom validation attributes that derive from System.ComponentModel.DataAnnotations.ValidationAttribute or use CustomValidationAttribute or implement IValidatableObject interface to validate entities. The built-in validation attributes (i.e. the attributes present in .Net Framework) are not able to use this additional context since they don’t know the semantic of the items passed by the application.

 

Summary

In this post we looked at one of the new features included in the EF Feature CTP5 - validation. We started from rationalizing why validation is important and went through a few main scenarios showing validation in action. Finally we looked at ways different ways of customizing the built-in validation.

Feedback and support

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

For support please use the Entity Framework Pre-Release Forum.

Pawel Kadluczka, Entity Framework Developer