Поделиться через


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

Comments

  • Anonymous
    December 15, 2010
    Good,but I need more attributes when I create database with ctp. exp, description attributes ,default value attributes

  • Anonymous
    December 15, 2010
    > 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. Why not? I think it should be validated.

  • Anonymous
    December 15, 2010
    Do you have plans to implement similar API for database schema validation? Something like var result = ctx.GetSchemaValidationResult().

  • Anonymous
    December 15, 2010
    @Jeff: Are the attributes you are talking about for validation purposes? If so you can always create your own attributes by deriving from the ValidationAttribute class. They will be used for validation the same way as the built-in attributes are. @Vesel: Thanks for your feedback. This is one of the open issues we have not closed on yet. For CTP5 we decided to validate properties only using if validation attributes were explicitly set the user. We are considering enabling property validation using facets regardless of how the facets where set (i.e. using attributes or just by configuring the model in OnModelCreating).

  • Anonymous
    December 15, 2010
    Does validation work with any other EF4 code generation? Are there plans to make that happen?

  • Anonymous
    December 15, 2010
    @Speedbird186 Yes, we have plans to support validation on DbContext with all forms of EF code gen. We are evaluating whether we can get this into our next release.

  • Anonymous
    December 16, 2010
    This is already possible with T4 and EntLib, what advantage do this approach have over EntLib?

  • Anonymous
    December 16, 2010
    This is already possible with T4 and EntLib, what advantage does this approach have over EntLib?

  • Anonymous
    December 16, 2010
    The comment has been removed

  • Anonymous
    December 16, 2010
    My first impression is that this validation logic should be done by the domain layer, not the data layer. It seems that domain logic is coupled to EF again, or am I wrong? Is there a way to validate entities WITHOUT the need of referencing EF?

  • Anonymous
    December 17, 2010
    @Ludwig If you configure your model with validation attributes (i.e. Required, StringLength/MaxLength) validation will only validate whether entities meet these requirements. Note that if entities don't meet the requirements and there is no validation you will get an exception anyways but from the database. In this sense validation is just trying moving the point of failure from the database to the application giving much better experience at the same time (nice exception messages, ponters to entities/properties that caused the failure). If you decide to put some additional validation attributes or implement IValidatableObject interface you could treat it as domain layer validation. If you don't want this validation to be done by EF you can opt out and do validation on your own. In this case you could use either any of the available validation libraries (e.g. Validation from DataAnnotations or Enterprise Library) or you could write custom validation code.

  • Anonymous
    December 17, 2010
    @Ludwig If you configure your model with validation attributes (i.e. Required, StringLength/MaxLength) validation will only validate whether entities meet these requirements. Note that if entities don't meet the requirements and there is no validation you will get an exception anyways but from the database. In this sense validation is just trying moving the point of failure from the database to the application giving much better experience at the same time (nice exception messages, ponters to entities/properties that caused the failure). If you decide to put some additional validation attributes or implement IValidatableObject interface you could treat it as domain layer validation. If you don't want this validation to be done by EF you can opt out and do validation on your own. In this case you could use either any of the available validation libraries (e.g. Validation from DataAnnotations or Enterprise Library) or you could write custom validation code.

  • Anonymous
    December 17, 2010
    I see a lot of limitations to using attributes for validation.  How do I handle error messages in multiple languages? How do I let the user configure the phone number format?  I've got one client that wants the username is uppercase and the other client wants all lower case.  It just doesn't seem like a very flexible solution.

  • Anonymous
    December 17, 2010
    @Darren

  1. I believe you can localize error messages with localized resources by providing type and resource name when defining attributes e.g.: [Required(ErrorMessageResourceType = typeof(Resources.Validation), ErrorMessageResourceName = "ValueMustNotBeNull")]
  2. If you expect to have different validation requirements for the same property you could use CustomValidationAttribute (or implement IValidatableObject interface) that would read the format you want the value to be in and validate the value against this format. If you need even more control can override ValidateEntity method and do all the validation on your own.
  • Anonymous
    December 18, 2010
    @pawel: I understand that in the repository you have to do basic error handling too. But then I would call it data validation, not domain validation. What I mean is that you are mixing data validation logic by defining it on the model by means of attributes, while the domain model can look totally different than the data model; and domain rules are not always the same (mostly more complex) as data rules. I also think that using attributes is very limiting, because when you do validation, you can have validation rules that always apply on a domain entity; and you have context-sensitive validation that only happens when the domain entity is used in a specific business context. What I (personally) did is create some kind of 'registration-oriented' validation mechanism so that you have far more flexibility that you have with attributes; because an unlimited number of validation methods can be registered for a specific domain entity; and as such not EF-dependent. So my main problem is again: EF is hard to use in an N-tier architecture and I don't have the impression that this is going to change soon.

  • Anonymous
    December 21, 2010
    @Ludwig: I agree that in some advanced scenarios the validation we added may not meet all the required criteria and you will have to write custom validatation to meet all those criteria. Still I believe that what we added will be usefull for people whose models or the validation criteria are not that complicated.

  • Anonymous
    December 21, 2010
    This is great stuff.  Is there an example of how code first validation is used with a datagrid in Silverlight?

  • Anonymous
    December 21, 2010
    @Ludwig we are having similar problems with EF and would be interested to hear what you would propose as an alternative until EF's design become more usable.

  • Anonymous
    December 22, 2010
    @Rik: I have implemented a DDD architecture with EF 4 CTP 5 as the repository implementation in the infrastructure layer. De domain layer is completely independent, has its own tracking and validation mechanism. In my case, because I also own the client, the domain model itself is exposed in the service layer and as such, I can just reuse the domain model on the WPF client, where it is encapsulated by the view models (using MVVM). It also means I reuse every domain logic on back-end and client. Of course, in enterprise applications you would typically not expose the domain model, but instead create specific data contracts, according to the needs of the clients. So in this architecture, EF is only known in the infrastructure layer, where it belongs. Concerning EF: good practise is to have a context per unit of work, but unfortunately EF cannot deal with this very well; so I spent a number of weeks trying to find out a way to make this work, and eventually succeeded (basically I replay the changed recorded by the domain model), but there's room for improvement in this area - and when EF would actually work well in a disconnected scenario one day, I just need to adapt the repository implementation. (If you want to discuss this further, we could always exchange mail or something.)

  • Anonymous
    January 04, 2011
    @tmsmith: I answered your question on the forum. Here is the link: social.msdn.microsoft.com/.../2108079c-8160-460e-b703-1800bd910679

  • Anonymous
    January 07, 2011
    I've been trying CTP5 out and everything I've done works great locally.  When connecting to a remote database though, I had to specify the connectionString in the constructor for the DbContext as I couldn't get it to work using the connectionString name.

  • Anonymous
    March 23, 2011
    I really, really hope this feature will be extended for Model First and Database First.

  • Anonymous
    July 03, 2011
    Are you seriously suggesting that people should now start validating user input in the database model ? I think it is fine validating before saving to the database as an extra layer of protection securing that database rules are upheld, but these errors should NEVER be reported to the user, it should be logged and the developer should be notified. At best this should be something which is caught when running your tests. User input validation should happen before it reaches the database, for example in a view model.

  • Anonymous
    July 18, 2011
    Is there anyway to get hold of the current context whilst using a custom validation attribute.  I am trying to doa unique validation attribute, but cannot see how I get the current context.  Unfortunately it is no good creating a new context as this will not contain the 'local' list of items pending insert - which also need to be validated against.

  • Anonymous
    July 19, 2011
    @James: There is a way to pass your own objects to custom validation and this is the way to pass your context. You do it by overriding DbContext.ValidateEntity method and invoking the method from the base class with non-null dictionarry of custom objects. Take a look at this post: blogs.msdn.com/.../ef-4-1-validation.aspx There is a section called "Custom Validation Sample: Uniqueness" that does exactly that.

  • Anonymous
    September 12, 2011
    Why don't you add these attributes for us when doing database first? OR at least have an option to do so. I don't want to modify the generated code constantly, and perhaps get it wrong if the db changes.

  • Anonymous
    October 03, 2011
    The comment has been removed

  • Anonymous
    May 04, 2015
    Why not link to an UPDATED version of this article instead of redirecting to the EF general bit bucket?