Chapter 6. Validating with the Validation API

Developers often spend a lot of time writing validation logic in their applications. Many of the rules for validation are built into their classes, but .NET can’t magically verify those rules. Code First allows you to apply some rules directly to properties using Data Annotations or the Fluent API. For example, you can specify the maximum length of a string or the fact that a particular property is required (i.e., can’t be null).

Another type of rule that your model describes is relationship constraints. For example, in our model, a Lodging is required to have a related Destination. Entity Framework has always checked that relationship constraint rules are met before it will push inserts, updates, or deletes to the database.

The DbContext adds to this existing validation with the new Validation API that is associated with the DbContext. Using the Validation API, the DbContext can automatically (or on demand) validate all of the rules that you have defined using mechanisms that the validation will recognize. The API takes advantage of features that already exist in .NET 4—ValidationAttributes and the IValidatableObject. This integration is a great benefit to developers. Not only does it mean that you can leverage existing experience if you’ve worked with the features already, but it also means that Entity Framework validation can flow into other tools that use this class or interface.

Validation in the data layer is an important element of data-focused applications. While you may have client-side or business layer validations, you may desire or prefer to have one last bastion of validation before data is pushed into the database. In scenarios where client-side validation is performed in a web application dependent on JavaScript being enabled in a browser, data layer validation plays an important role when the end user has disabled JavaScript.

In this chapter, you’ll learn how to take advantage of the built-in validation provided by the DbContext and Validation API using its default behaviors.

Defining and Triggering Validation: An Overview

The Validation API checks rules that you can apply in a number of ways:

  • Property attribute rules that you can specify using Data Annotations or the fluent API.

  • Custom validation attributes that can be defined for properties or types.

  • Rules defined in the Validate method of any model class that implements IValidatableObject. This interface is part of .NET 4, so it’s great to see that the DbContext was designed to take advantage of this.

  • Relationship constraints explicitly defined in the model.

  • Additionally, you can inject validations into the validation pipeline.

There are a number of ways to cause the DbContext to execute the validations:

  • By default, validation will be performed on all tracked Added and Modified objects when you call SaveChanges.

  • The DbEntityEntry.GetValidationResult method will perform validation on a single object.

  • DbEntityEntry has a path for validating an individual property.

  • DbContext.GetValidationErrors will iterate through each Added and Modified object being tracked by the DbContext and validate each object.

Note

In the next chapter you’ll learn how to override ValidateEntity as well as change the default that validates only Added or Modified objects.

At the root of all of these validation methods is the DbEntityEntry.GetValidationResult method, which validates the rules defined in property attributes and IValidatableObjects. GetValidationErrors calls ValidateEntity on each [Added or Modified] tracked object, which in turn calls GetValidationResult. SaveChanges calls GetValidationErrors, which means that the validation occurs automatically whenever SaveChanges is called. Figure 6-1 shows the waterfall path and different entry points to leverage the Validation API.

The “it just works” approach of having validation implicitly called by SaveChanges may be all that some developers need or are interested in. But rather than start with the appearance of magic, we’ll use a bottom-up approach to show you the explicit validation functionality so that you can use that to have more control over how and when validation occurs.

Three ways to execute GetValidationResult from your code
Figure 6-1. Three ways to execute GetValidationResult from your code

Validating a Single Object on Demand with GetValidationResult

Our sample classes already have some attributes that will be checked by the Validation API. For example, in the Destination class, you should already have a MaxLength annotation on the Description property shown here:

[MaxLength(500)]
public string Description { get; set; }

Note

Refer to the listing for Destination shown in Example 2-1.

Testing this rule won’t be very easy since breaking it would mean adding a string that is greater than 500 characters. I don’t feel like typing that much. Instead, I’ll add a new MaxLength annotation to another property—the LastName property in the Person class:

[MaxLength(10)]
public string LastName { get; set; }

Now let’s see what happens when we set the length to a string with more than ten characters. GetValidationResult allows you to explicitly validate a single entity. It returns a ValidationResult type that contains three important members. We’ll focus on just one of those for now, the IsValid property, which is a Boolean that indicates if the instance passed its validation rules. Let’s use that to validate a Person instance. The ValidateNewPerson method in Example 6-1 shows calling the GetValidationResult.IsValid method.

Example 6-1. Method to test validation of LastName.
private static void ValidateNewPerson()
{
  var person = new Person
  {
    FirstName = "Julie",
    LastName = "Lerman",
    Photo = new PersonPhoto { Photo = new Byte[] { 0 } }
  };

using (var context = new BreakAwayContext())
{
  if (context.Entry(person).GetValidationResult().IsValid)
  {
    Console.WriteLine("Person is Valid");
  }
  else
  {
    Console.WriteLine("Person is Invalid");
  }
}

}

If you run this method from the Main method, you will see the message “Person is Valid” in the console windows.

The GetValidationResult method calls the necessary logic to validate any ValidationAttributes defined on the object’s properties. It then looks to see if the type has a CustomValidationAttribute or implements the IValidatableObject interface and if it does, calls its Validate method. You’ll see this in action later in this chapter.

Note

While we strongly recommend against calling data access code directly in the user interface, these examples are solely for the purpose of demonstrating the Validation API features. We are not suggesting that you use the DbContext for performing client-side validation.

Now change the code so that it sets LastName to “Lerman-Flynn” instead of Lerman. Run the app again and you will see “Person is Invalid” in the console. The Validation API of the DbContext was able to detect that a rule was broken. This is just a high-level look at the method. Let’s explore more ways to define rules before we dig further into the result of the validation.

Specifying Property Rules with ValidationAttribute Data Annotations

The MaxLength Data Annotation is exposed via the MaxLengthAttribute class. MaxLengthAttribute is one of a group of attributes that inherit from a class called System.Data.Annotations.ValidationAttribute. GetValidationResult checked MaxLength because it’s designed to check any rule that is applied using a ValidationAttribute.

The Validation API will check any rule that is applied using a ValidationAttribute.

Following is a list of the attribute classes that derive from ValidationAttribute along with the annotation used to decorate a class property:

DataTypeAttribute

[DataType(DataType enum)]

RangeAttribute

[Range (low value, high value, error message string)]

RegularExpressionAttribute

[RegularExpression(@”expression”)]

RequiredAttribute

[Required]

StringLengthAttribute

[StringLength(max length value,

MinimumLength=min length value)]

CustomValidationAttribute

This attribute can be applied to a type as well as to a property.

For the sake of describing database schema mappings, the Entity Framework team added MaxLengthAttribute to the namespace and paired it with a new MinLengthAttribute. They both derive from ValidationAttribute, so these too will be checked by the Validation API.

Both MaxLength and Required are not just ways to define a class property but they are also used to describe a database column to which the properties map. Technically, these are referred to as facets. Therefore, in Entity Framework, these two facets play dual rules—they help Code First understand what the mapped database columns look like and they also participate in the class-level validation. An added benefit is that since you can also define these two facets—MaxLength and Required—with the Fluent API, Entity Framework will take advantage of the relevant ValidationAttribute types under the covers to make sure they get validated as if they had been configured with the Data Annotations.

Entity Framework has been taught to look for the StringLength annotation and use its MaximumLength parameter as a database column facet as well.

Validating Facets Configured with the Fluent API

If you have used the Fluent API to configure your Code First model, you may be familiar with specifying attributes fluently instead of using Data Annotations.

Note

The Programming Entity Framework: Code First book covers fluent API in detail.

Two of the Data Annotations that inherit from ValidationAttributeMaxLength and Required—have Fluent API counterparts. This is due to the fact that MaxLength and Required are attributes that impact the model’s comprehension of the database schema and therefore impact how Entity Framework maps the classes to the database.

The Validation API will check these two rules if you configure them with the Fluent API. For example, you could replace the [MaxLength] annotation on Person.LastName with this code added into the BreakAwayContext.OnModelCreating method:

modelBuilder.Entity<Person>()
  .Property(p => p.LastName).HasMaxLength(10)

If you return to the ValidateNewPerson method from Example 6-1, ensure the LastName property is set to “Lerman-Flynn,” and then rerun the method, it will result in a message from the console application: “Person is Invalid.”

Underneath the covers, Entity Framework is using the StringLengthAttribute (or in the case of a Required scalar, the RequiredAttribute) to validate the HasMaxLength facet of Person.FirstName. Although the example only checks the IsValid property, the details of the error are returned by GetValidationResult, and you’ll see how to read these shortly.

Validating Unmapped or “Transient” Properties

It is possible to have properties in your class that do not map to the database. By convention, properties that do not have both a setter and a getter will not be part of the model. These are also known as transient properties. You can also configure a property to be unmapped using the NotMapped data annotation or the Ignore fluent method. By default, unmapped properties will not get validated.

However, if you have applied a ValidationAttribute to a transient property (as long as that property is in a class that is part of the model), Entity Framework will validate those rules as well.

Validating Complex Types

Entity Framework’s conceptual model supports the use of complex types, also known as value objects. You can configure a complex type both in the Entity Data Model designer as well as with Code First. It is also possible (and feasible) to apply attributes to the properties of complex types. Entity Framework’s GetValidationResult will validate attributes placed on complex type properties.

Using Data Annotations with an EDMX Model

It’s easy to apply data annotations to your class and then use that class with Entity Framework thanks to Code First, but what if you are using the Entity Data Model designer to create your Database First or Code First model and then relying on code generation to create your classes? There’s no opportunity to apply Data Annotations to your properties. You might modify the T4 template to apply Data Annotations that follow very common patterns in your classes, but typically this is not the appropriate mechanism for applying property-by-property attributes.

The generated classes are partial classes, which does give you the ability to add more logic to the classes with additional partial classes. However, you cannot add attributes in one partial class to properties that are declared in another partial class.

But all is not lost. There is a feature in .NET called an associated metadata class that allows you to add metadata to classes in an external file. These classes are commonly referred to as “buddy classes,” although we are more fond of the term “ugly buddy classes” because they feel a little kludgy. However ugly, they are a great way to apply data annotations to generated code. So setting aside illusions of grandeur about our code, let’s take a look at a simple example of an associated metadata class.

Note

David Ebbo has an interesting blog post on other ways to use the Metadata attribute: http://blogs.msdn.com/b/davidebb/archive/2009/07/24/using-an-associated-metadata-class-outside-dynamic-data.aspx

If you are using the DbContext Generator template to generate classes from an EDMX, the Person class will be declared as a partial class:

  public partial class Person

and the scalar properties will be simple. Here is what the FirstName property will look like:

public string FirstName { get; set; }

You can create a new class where you can mimic the property declaration and apply the attribute:

class Person_Metadata {
    [MinLength(10)]
    public string FirstName { get; set; }
}

Then you need to let the Person class know to use the Person_Metadata class for the sole purpose of reading the attributes.

You do this by applying the Metadata attribute to the Person class:

[MetadataType(typeof(Person_Metadata))]
  public partial class Person

Warning

If you want to try this out in the Code First sample you’ve been working with, be sure to remove or comment out the MinLength annotation on the FirstName property in the Person class.

Inspecting Validation Result Details

Notice that GetValidationResult doesn’t simply throw an exception if the validation fails. Instead, it returns a System.Data.Entity.Validation.DbEntityValidationResult whether the rule is met or broken, setting IsValid to the appropriate value and providing detailed information on any broken rules.

DbEntityValidationResult also exposes a ValidationErrors property, which contains a collection of more detailed errors in the form of DbValidationError types. One final property of DbEntityValidationResult is a pointer. In this scenario, it seems redundant to have the Entry property when we started with the Entry to get the results. However, when one of the higher-level methods calls GetValidationResult on your behalf, you may not know which Entry is currently being validated; in that scenario, you’ll probably be grateful for the Entry property.

Figure 6-2 shows getting to the Entry, IsValid, and ValidationErrors properties in the debugger.

Entry, IsValid and ValidationErrors a validation result
Figure 6-2. Entry, IsValid and ValidationErrors a validation result

Inspecting Individual Validation Errors

Looking back at Figure 6-2, you’ll see that when we set the LastName to Lerman-Flynn, which exceeded the MaxLength(10) specification, the result’s ValidationErrors collection contains a single DbValidationError. DbValidationError exposes two properties, the name of the property and the actual error message.

Where did the error message come from? The internal validation logic has a formula that composes a message using the property name and the annotation that failed. This is default behavior.

You can specify your own error message in any ValidationAttribute. For example, if you were writing error messages for an application used by surfers, you might want to specify one like this:

[MaxLength(10,
           ErrorMessage= "Dude! Last name is too long! 10 is max.")]
public string LastName { get; set; }

Figure 6-3 shows the new ErrorMessage returned in a DbEntityValidationError.

ErrorMessage and PropertyName of a DbValidationError
Figure 6-3. ErrorMessage and PropertyName of a DbValidationError

Note

Because the ValidationAttribute type is part of .NET 4 and not specific to Entity Framework, we won’t spend a lot of time going into great detail about how to configure the ValidationAttribute types. Other .NET frameworks, such as Managed Extensibility Framework (MEF), ASP.NET MVC, and ASP.NET Dynamic Data, use this functionality, and there is a lot of information available. To start, here is the MSDN Topic on the ValidationAttribute class: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.ValidationAttribute.aspx.

As mentioned in the earlier note, MEF, MVC, and Dynamic Data are able to leverage the ValidationAttribute. Although the ValidateNewPerson method demonstrated using DbContext to perform the validation, it is also possible to validate ValidationAttributes directly using its Validate method. This is a great benefit for client-side development. However, since the focus of this book is DbContext, we’ll focus on how the DbContext works with the ValidationAttributes at the data layer. Remember that DbContext checks more than just ValidationAttributes, so you can benefit from these as well as other rules all at once on the server side with DbContext.

If you are calling GetValidationResults directly, you will have to write your own logic to interact with the DbEntityValidationResult, read the errors, and handle them, whether for logging or returning to the UI to present to the user.

In the case of the validation detected in ValidateNewPerson, there is a single error inside the ValidationErrors property. You could get to it by requesting that first error. For example:

var result = context.Entry(person).GetValidationResult();
if (!result.IsValid)
{
  Console.WriteLine(
    result.ValidationErrors.First().ErrorMessage);
}

That will work if you only expect or only care about the first error. However, if you have numerous ValidationAttributes defined on a type and more than one is broken, there will be more than one DbEntityValidationError in the ValidationErrors property.

What if the Person class also had a rule that the FirstName property must be at least three characters?

    [MinLength(3)]
    public string FirstName { get; set; }

If you were to update the ValidateNewPerson method from Example 6-1 to insert just the letter J as the FirstName, the validation will see two errors, as shown in Figure 6-4.

Two errors inside of the result’s ValidationErrors property
Figure 6-4. Two errors inside of the result’s ValidationErrors property

It might be wiser, therefore, to iterate through the errors. You’ll need to add a using statement for the System.Data.Entity.Validation namespace:

foreach (DbValidationError error in result.ValidationErrors)
{
  Console.WriteLine(error.ErrorMessage);
}

Exploring More ValidationAttributes

So far we’ve looked at the MaxLength property, which is not only a ValidationAttribute, but is an attribute that’s in the EntityFramework assembly. Let’s look at an attribute that is not specific to Entity Framework, the RegularExpressionAttribute, and verify that the DbContext.GetValidationResult will see that as well.

Following is a RegularExpression applied to the Destination.Country property. This expression specifies that the string can be up to 40 characters and will accept uppercase and lowercase letters:

  [RegularExpression(@"^[a-zA-Z''-'s]{1,40}$")]
  public string Country { get; set; }

The ValidateDestinationRegEx method in Example 6-2 creates a new Destination and asks the DbContext to validate the instance.

Note

You’ll notice that we’ve also refactored the method to move the call to GetValidationResult and Console.WriteLine into a separate method called ConsoleValidationResults. See the sidebar Simplifying the Console Test Methods to see this new method.

Example 6-2. The ValidateDestination method
public static void ValidateDestination()
{
  ConsoleValidationResults(
    new Destination
    {
      Name = "New York City",
      Country = "USA",
      Description = "Big city"
    });
}

With the Country set to “USA,” the property is valid and no errors are displayed. However, if you change the Country value to “U.S.A.,” GetValidationResult detects an error because the periods in between the letters do not follow the rule defined by the regular expression. Be aware that the default error message only reports that the value does not match the expression; it does not tell you which part of the expression was broken:

The field Country must match the regular expression '^[a-zA-Z''-'s]{1,40}$'.

This is not a problem with how Entity Framework handles the validation. It is simply how the RegularExpressionAttribute behaves by default. You can learn more about controlling this error message in the MSDN documentation referenced above.

Using CustomValidationAttributes

You can build custom validation logic that can be applied to a property using a CustomValidationAttribute. These too will get checked during Entity Framework validation. Example 6-3 shows an example of a static class, BusinessValidations, which contains a single validation, DescriptionRules, to be used on various description properties in the model. The rule checks for exclamation points and a few emoticons to ensure that trip descriptions or other descriptions don’t read as though they were text messages! ☺

Example 6-3. Static custom validations to be used by different classes
using System.ComponentModel.DataAnnotations;

namespace Model
{
  public static class BusinessValidations
  {
    public static ValidationResult DescriptionRules(string value)
    {
      var errors = new System.Text.StringBuilder();
      if (value != null)
      {
        var description = value as string;

        if (description.Contains("!"))
        {
          errors.AppendLine("Description should not contain '!'.");
        }
        if (description.Contains(":)") ||
             description.Contains(":("))
        {
          errors.AppendLine(
           "Description should not contain emoticons.");
        }
      }
      if (errors.Length > 0)
        return new ValidationResult(errors.ToString());
      else
        return ValidationResult.Success;
    }
  }
}

Note

The ValidationResult used here is a System.ComponentModel.DataAnnotations.ValidationResult, not to be confused with the System.Windows.Controls.ValidationResult.

You can apply the validation to properties using the CustomValidationAttribute, as shown in Example 6-4, where we’ve added the annotation to the Destination.Description property (which already has a MaxLength annotation). The attribute requires that you specify the class where the validation method exists and then the name of the method as a string.

Example 6-4. Applying the new DescriptionRules validation to a property
[MaxLength(500)]
[CustomValidation(typeof(BusinessValidations), "DescriptionRules")]
public string Description { get; set; }

If you’d like to test out the validation, you can modify the ValidateDestination method to insert some of the undesirable characters into the Description string, as we’ve done in Example 6-5.

Example 6-5. Creating a Destination that breaks multiple validation rules
public static void ValidateDestination()
{
  ConsoleValidationResults(
    new Destination {
      Name = "New York City",
      Country = "U.S.A",
      Description = "Big city! :) "
    });
}

Executing the method will cause the validation to return the following list of errors:

The field Country must match the regular expression '^[a-zA-Z''-'s]{1,40}$'.
Description should not contain '!'.
Description should not contain emoticons.

Both the RegularExpression validation on the Country property and the DescriptionRules custom validation on Description are reported.

Validating Individual Properties on Demand

In addition to providing the GetValidationResults method, DbEntityEntry lets you drill into individual properties, as you’ve already seen in Chapter 5:

context.Entry(trip).Property(t => t.Description);

This returns a DbPropertyEntry representing the Description property.

The DbPropertyEntry class has a method for explicitly validating that particular entry—GetValidationErrors—which will return an ICollection<DbValidationError>. This is the same DbValidationError class we’ve been exploring already in this chapter.

Example 6-6 displays a new method, ValidatePropertyOnDemand, which shows how to validate a property using DbPropertyEntry.GetValidationErrors. You’ll first need to apply the DescriptionRules custom attribute to the Trip.Description property, just as you did for Destination.Description in Example 6-4.

Example 6-6. Validating a property
private static void ValidatePropertyOnDemand()
{
  var trip=new Trip
           {
             EndDate = DateTime.Now,
             StartDate = DateTime.Now,
             CostUSD = 500.00M,
             Description = "Hope you won't be freezing :)"
      };
  using (var context = new BreakAwayContext())
  {
    var errors = context.Entry(trip)
                .Property(t => t.Description)
                .GetValidationErrors();
    Console.WriteLine("# Errors from Description validation: {0}",
                       errors.Count());
  }
}

The method creates a new Trip that has an emoticon in the Description. Based on the custom validation rule you created earlier in this chapter, the emoticon is invalid.

If you were to call this method from the Main method in the console application, the console window would report that there is one error in the Description. Keep this method around, because we’ll look at it again in the next section.

Specifying Type-Level Validation Rules

While there are more ways to trigger validations, let’s stick with the GetValidationResult method while we look at other ways to provide rules that the Validation API will validate. So far you’ve seen how to apply validation rules on individual properties. You can also define rules for a type that can take multiple properties into account. Two ways to create type-level validation that will be checked by the Entity Framework Validation API are by having your type implement the IValidatableObject interface or defining CustomValidationAttributes for type. This section will explore both of these options.

Using IValidatableObject for Type Validation

In addition to the ValidationAttribute, .NET 4 introduced another feature to help developers with validation logic—the IValidatableObject interface. IValidatableObject provides a Validate method to let developers (or frameworks) provide their own context from which to perform the validation.

If an entity that is being validated implements the IValidatableObject interface, the Validation API logic will recognize this, call the Validate method, and surface the results of the validation in a DbEntityValidationError.

What does IValidatableObject provide that is not satisfied with Data Annotations? The Data Annotations let you specify a limited number of rules for individual properties. With the additional Validate method, you can provide any type of logic that can be constrained to the class. What we mean by constrained is that the validation logic won’t rely on external objects since you can’t guarantee that they’ll be available when the validation is being performed. A typical example is comparing date properties in a class.

The Trip type has StartDate and EndDate fields. Let’s use IValidatableObject to define a rule that EndDate must be greater than StartDate.

Example 6-7 shows the Trip class after we’ve added the IValidatableObject implementation that includes the Validate method. Validate compares the dates and returns a ValidationResult if the rule is broken. Notice that we’ve also added the DescriptionRules attribute we created in Example 6-3 to the Description field.

Example 6-7. Validating dates in an IValidatableObject.Validate method
public class Trip : IValidatableObject
{
  [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  public Guid Identifier { get; set; }
  public DateTime StartDate { get; set; }
  public DateTime EndDate { get; set; }
  [CustomValidation(typeof(BusinessValidations), "DescriptionRules")]
  public string Description { get; set; }
  public decimal CostUSD { get; set; }
  [Timestamp]
  public byte[] RowVersion { get; set; }

  public int DestinationId { get; set; }
  [Required]
  public Destination Destination { get; set; }
  public List<Activity> Activities { get; set; }

  public IEnumerable<ValidationResult> Validate(
    ValidationContext validationContext)
  {
    if (StartDate.Date >= EndDate.Date)
    {
      yield return new ValidationResult(
        "Start Date must be earlier than End Date",
        new[] { "StartDate", "EndDate" });
    }
  }
}

Visual Basic (VB) does not have a yield keyword. Instead, you can create a List<ValidationResult>, add each ValidationResult into that list, and then return it. Example 6-8 shows the Validate method as you would write it in VB.

Example 6-8. The Validate method expressed in Visual Basic
Public Function Validate(
 ByVal validationContext As ValidationContext)
 As IEnumerable(Of ValidationResult)
 Implements IValidatableObject.Validate
  Dim results = New List(Of ValidationResult)
  If StartDate.Date >= EndDate.Date Then
    results.Add(New ValidationResult
                   ("Start Date must be earlier than End Date",
                    {"StartDate", "EndDate"}))
  End If
  Return result
End Function

We’ve added a new method to the console application called ValidateTrip, shown in Example 6-9.

Example 6-9. The ValidateTrip method to check the new rule
private static void ValidateTrip()
{
  ConsoleValidationResults(new Trip
  {
    EndDate = DateTime.Now,
    StartDate = DateTime.Now.AddDays(2),
    CostUSD = 500.00M,
    Destination = new Destination { Name = "Somewhere Fun" }
  });
}

When calling ValidateTrip, the application displays the error message, “Start Date must be earlier than End Date.” But it’s listed twice. That’s because the Validate method listed this as a problem for both StartDate and EndDate, so it created two separate errors. The DbValidationError.ErrorMessage is the same in both, but one has “EndDate” in its DbValidationError.PropertyName while the other has “StartDate.”

This is important for data binding with frameworks such as MVC or WPF where you can bind the errors to the displayed properties. If we modify the ValidateTrip method to ensure that EndDate is a later date than StartDate, the ValidateTrip method returns no error messages.

Note

Mapped complex types that implement IValidatableObject will be checked in the validation pipeline as well.

Validating Multiple Rules in IValidatableObject

You can add as many class validations as you like in your Validate method. With the C# yield keyword, all of the ValidationResult types created will be contained within the IEnumerable that’s returned by the method.

Example 6-10 shows the Trip.Validate method with a second validation added that checks against a list of words that are undesirable for describing trips. You could use a RegularExpression annotation with the word list, but this method gives you the opportunity to store the list of words in a resource file so that it’s not hard-coded into the application. The list is hard-coded into this example only for the simplicity of demonstrating the validation. You’ll need to add a using statement for the System.Linq namespace.

Example 6-10. Validate method for the Trip type
public IEnumerable<ValidationResult> Validate(
  ValidationContext validationContext)
{
  if (StartDate.Date >= EndDate.Date)
  {
    yield return new ValidationResult(
      "Start Date must be earlier than End Date",
      new[] { "StartDate", "EndDate" });
  }

  var unwantedWords = new List<string> 
  {
    "sad", 
    "worry", 
    "freezing", 
    "cold" 
  };

  var badwords = unwantedWords
    .Where(word => Description.Contains(word));

  if (badwords.Any())
  {
    yield return new ValidationResult(
      "Description has bad words: " + string.Join(";", badwords),
      new[] { "Description" });
  }
}

Now we’ll modify the ValidateTrip method to add a Description (which includes the undesirable words “freezing” and “worry”) to the new trip before the validation is performed (Example 6-11).

Example 6-11. ValidateTrip method modified to include Description
private static void ValidateTrip()
{
  ConsoleValidationResults(new Trip
   {
    EndDate = DateTime.Now,
    StartDate = DateTime.Now.AddDays(2),
    CostUSD = 500.00M,
    Description="Don't worry about freezing on this trip",
    Destination = new Destination { Name = "Somewhere Fun" }
  });
}

When running ValidateTrip with this trip that now breaks two rules, both error messages are displayed in the console window:

Start Date must be earlier than End Date
Start Date must be earlier than End Date
Description has bad words: worry;freezing

In the previous section, Validating Individual Properties on Demand, you created a method to explore the DbPropertyEntry.GetValidationErrors. Looking back at Example 6-6, notice that in addition to the emoticon in the description, there is what you now know to be an undesirable word—freezing. If you were to run the method again, the console window would still only report a single error, which is a result of the emoticon. It seems to ignore the problem with the word freezing. That’s because the validation that checks for the word freezing is defined for the class. DbPropertyEntry.GetValidationErrors can only check ValidationAttributes placed on properties.

Using CustomValidationAttributes for Type Validation

You can also use CustomValidationAttribute on a type rather than an individual property, allowing you to define a validation that takes into account more than a single property. We’ll show you how you can define the same validation in the IValidatableObject example above by using CustomValidationAttribute.

The signature of a CustomValidationAttribute has the target type specified in the parameter along with a ValidationContext, which is used in the same way as the ValidationContext parameter of the Validate method. Example 6-12 shows two validation methods that are added into the Trip class. Notice that these methods are both public and static. Also notice that we’re using separate methods for each validation. That’s because a ValidationAttribute can only return a single ValidationResult.

Example 6-12. Two validation methods to be used as Trip type attributes
public static ValidationResult TripDateValidator(
  Trip trip, 
  ValidationContext validationContext)
{
  if (trip.StartDate.Date >= trip.EndDate.Date)
  {
    return new ValidationResult(
      "Start Date must be earlier than End Date",
      new[] { "StartDate", "EndDate" });
  }

  return ValidationResult.Success;
}

public static ValidationResult TripCostInDescriptionValidator(
  Trip trip, 
  ValidationContext validationContext)
{
  if (trip.CostUSD > 0)
  {
    if (trip.Description
      .Contains(Convert.ToInt32(trip.CostUSD).ToString()))
    {
      return new ValidationResult(
        "Description cannot contain trip cost",
        new[] { "Description" });
    }
  }

  return ValidationResult.Success;
}

The first method, TripDateValidator, mimics a validation you used earlier—checking that the StartDate is earlier than the EndDate. The second method, TripCostInDescriptionValidator, checks to make sure that a user hasn’t written the trip cost into the description. The logic in that method could be fine-tuned for a production application, but it should suffice for this demonstration.

There’s a notable difference with these methods when comparing them to the Validate method you saw earlier. The Validate method has access to private methods, properties, and fields. But because of the way the ValidationAttribute is handled under the covers triggering those methods (which is also why they must be public and static), it will not have this access.

To have both validations executed, you need to add them as separate attributes on the Trip class, as shown in Example 6-13.

Example 6-13. Applying a type-level CustomValidationAttribute
[CustomValidation(typeof(Trip), "TripDateValidator")]
[CustomValidation(typeof(Trip), "TripCostInDescriptionValidator")]
public class Trip: IValidatableObject

If you were to run the ValidateTrip method, the console window would display this:

Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Description has bad words: worry;freezing

As a reminder, because the validator creates the ValidationResult specifying that it’s for both the StartDate field and for the EndDate property, the error is listed once for each property. If you were to inspect the ValidationResult more closely, you would see that the errors are differentiated by their PropertyName. You’re also seeing the errors generated by the Validate method. The Validate method is also checking the date range as well as checking the Description for unwanted words.

Modify the ValidateTrip method to break the TripCostInDescriptionValidator rule as follows, changing the value of the Description:

private static void ValidateTrip()
{
  ConsoleValidationResults(new Trip
  {
    EndDate = DateTime.Now,
    StartDate = DateTime.Now.AddDays(2),
    CostUSD = 500.00M,
    Description = "You should enjoy this 500 dollar trip",
    Destination = new Destination { Name = "Somewhere Fun" }
  });
}

Running the application again would result in the two errors from the problem with the date properties as well as the error message from the failed TripCostInDescriptionValidator validation:

Description cannot contain trip cost
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date

Understanding How EF Combines Validations

Remember the DescriptionRules method that we added to the BusinessValidations class to validate the Description property of Destination and Trip? Those contain overall rules for writing any description for the company, not just those for destinations.

Now we’ll modify the Description in the ValidateTrip method to include the dreaded smiley face emoticon and exclamation point:

Description="Hope you won't be freezing on this trip! :)"

Before running the ValidateTrip method again, keep in mind that the values of this Trip instance break four rules:

  1. The StartDate is not at least a full calendar day before the EndDate.

  2. The word freezing is in the description.

  3. There is an emoticon in the Description.

  4. There is an exclamation point in the Description.

Here is the list of messages returned by the method:

Description should not contain '!'.
Description should not contain emoticons.

The problems about the dates and the word freezing are missing from the messages.

To be sure, let’s revert the Description, removing the exclamation and emoticon so that it passes the DescriptionRules but fails the others. This brings us back to the date problem listed for both the date fields and the message about the word freezing:

Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Description has bad words: freezing

While this looks like there’s a problem with the validation, the validation is indeed working as designed. We’ve defined both property validation and type validation. The property validations check for the emoticons and exclamation point, while the type validations check the dates and look for bad words. The failure of a property validation is short-circuiting the type validations. In other words, the type validation is never performed because problems were found when validating the properties.

Let’s update the ValidateTrip method so that it no longer supplied a value for the Destination property—which is marked with a Required attribute:

// Destination = new Destination { Name = "Somewhere Fun" }

If you rerun the ValidateTrip method, which no longer provides a value for the required Destination property, the only error message is this:

The Destination field is required.

The Required validation failure is reported, but the type validations are still missing, so the failure of this validation also prevented the type validation. If we added the “! :)” back into the Description, you’d see all of the property validation problems listed (Required, “!”, and the emoticon) but still no report of the type validation problems.

What’s happening is that there are rules that the validation engine follows that prevent it from erroneously reporting errors that might be caused by other validation errors. If the property validation fails, it’s possible that the bad attributes might cause the type validation to fail as well.

Borrowing from the Entity Framework team blog post at http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx, here is a description of the order in which validations are performed:

  1. Property-level validation on the entity and the base classes. If a property is complex its validation would also include the following:

  2. Property-level validation on the complex type and its base types

  3. Type-level validation on the complex type and its base types, including IValidatableObject validation on the complex type

  4. Type-level validation on the entity and the base entity types, including IValidatableObject validation

The key point is that type-level validation will not be run if property validation returns an error. In addition to the IValidatableObject validations, relationship constraints are validated. Since it’s possible that one of the property failures was due to a missing required property, that null value could very easily cause a relationship to be invalid. The Validation API does not allow constraint checking to occur if the properties cannot be validated.

Validating Multiple Objects

In addition to explicitly validating a single object with GetValidationResult, you can force the context to validate all of the necessary objects it is tracking with a single command: DbContext.GetValidationErrors. I’ve emphasized the word “necessary” because, by default, GetValidationErrors only validates Added and Modified objects since it typically wouldn’t be necessary to validate objects that are Unchanged or are marked to be deleted from the database.

When you call this method, the context will internally call DetectChanges to ensure that all of the change tracking information is up-to-date. Then it will iterate through all of the Added and Modified objects that it’s tracking and call DbContext.ValidateEntity on each object. ValidateEntity, in turn, will call GetValidationResult on the target object. When all of the objects have been validated, GetValidationErrors returns a collection of DbEntityValidationResult types for every failed object. The collection is returned as an IEnumerable< DbEntityValidationResult > and it only contains DbEntityValidationResult instances for the failed objects.

Note

In addition to calling GetValidationResult, ValidateEntity can also call custom logic that you specify. The next chapter will focus on customizing ValidateEntity. You’ll also learn to modify how Entity Framework decides which entities to validate by overriding the default, which only validates Added and Modified entities.

Example 6-14 displays a method that results in a context tracking

  • two new Trips,

  • one new Destination,

  • and one modified Trip.

If you look closely at the code, you’ll see that one of the new trips will be valid while the other three objects are not valid.

Example 6-14. Validating multiple tracked objects
private static void ValidateEverything()
{
  using (var context = new BreakAwayContext())
  {
    var station = new Destination
    {
      Name = "Antartica Research Station",
      Country = "Antartica",
      Description = "You will be freezing!"
    };

    context.Destinations.Add(station);

    context.Trips.Add(new Trip
    {
      EndDate = new DateTime(2012, 4, 7),
      StartDate = new DateTime(2012, 4, 1),
      CostUSD = 500.00M,
      Description = "A valid trip.",
      Destination = station
    });

    context.Trips.Add(new Trip
    {
      EndDate = new DateTime(2012, 4, 7),
      StartDate = new DateTime(2012, 4, 15),
      CostUSD = 500.00M,
      Description = "There were sad deaths last time.",
      Destination = station
    });

    var dbTrip = context.Trips.First();
    dbTrip.Destination = station;
    dbTrip.Description = "don't worry, this one's from the database";

    DisplayErrors(context.GetValidationErrors());
  }
}

Along with the ValidateEverything method in Example 6-14, add the DisplayErrors custom method (Example 6-15) to the Program class. This will iterate through the DbEntityValidationResult objects returned by the GetValidationErrors method and display them in a console window.

Example 6-15. DisplayErrors method called from Example 5-12
private static void DisplayErrors(
  IEnumerable<DbEntityValidationResult> results)
{
  int counter = 0;
  foreach (DbEntityValidationResult result in results)
  {
    counter++;
    Console.WriteLine(
      "Failed Object #{0}: Type is {1}",
      counter, 
      result.Entry.Entity.GetType().Name);

    Console.WriteLine(
      " Number of Problems: {0}",
      result.ValidationErrors.Count);

    foreach (DbValidationError error in result.ValidationErrors)
    {
      Console.WriteLine(" - {0}", error.ErrorMessage);
    }
  }
}

Modify the Main method to call ValidateEverything, which will execute and display the validation results, as shown in Example 6-16.

Example 6-16. Output from ValidateEverything method
Failed Object #1: Type is Destination
 Number of Problems: 1
 - Description should not contain '!'.

Failed Object #2: Type is Trip
 Number of Problems: 5
 - Start Date must be earlier than End Date
 - Start Date must be earlier than End Date
 - Start Date must be earlier than End Date
 - Start Date must be earlier than End Date
 - Description has bad words: sad

Failed Object #3: Type is Trip
 Number of Problems: 1
 - Description has bad words: worry

GetValidationErrors does not check relationship constraints unless they are explicitly configured. For example, by default, the Reservation.Traveler property is nullable. There are two ways to force the Reservation to require that a Person type be attached. One is to add an int TravelerId property and configure that to be the foreign key for Traveler. Int is non-nullable by default. ValidateEntity will not check that constraint and therefore GetValidationErrors won’t either.

Note

SaveChanges will detect relationship constraint problems even if they are not defined in a way that ValidateEntity will trap them.

Another way to require that a Person be attached is to configure the Traveler property as required. With a ValidationAttribute (even if you’ve configured with the Fluent API), ValidateEntity will check this rule and GetValidationErrors will detect the problem.

Warning

In Chapter 3, you learned about DetectChanges, the events that call it by default, and how to disable automatic change detection. If you have disabled change detection, that means GetValidationErrors won’t call it either and you should make an explicit call to DetectChanges before calling GetValidationErrors.

Validating When Saving Changes

While you may prefer to have explicit control over when GetValidationResults is called, Entity Framework can automatically perform the validations when you call SaveChanges. By default, when you call SaveChanges, each Added and Modified entity that is being tracked by the context will be validated because SaveChanges calls GetValidationErrors.

Reviewing ObjectContext. SaveChanges Workflow

Later in this section, you’ll learn how to disable the automatic validation that occurs during SaveChanges. You may already be familiar with how ObjectContext.SaveChanges works in the Entity Framework. For a brief overview, it follows this workflow (note that this is not taking DbContext into account yet —only the internal workflow):

  1. SaveChanges, by default, calls DetectChanges to update its tracking information on POCO objects.

  2. SaveChanges iterates through each tracked entity that requires some modification (those with states Added, Modified, or Deleted).

  3. For each of these entities, it checks that their relationship constraints are in a proper state. If not, it will throw an EntityUpdateException for that entity and stop further processing.

  4. If all of the entities pass the relationship validation, EF constructs and executes the necessary SQL command(s) to perform the correct action in the database.

  5. If the database command fails, the context responds by throwing an EntityUpdateException and stops further processing.

Because SaveChanges uses a DbTransaction by default, in either of the circumstances that causes the routine to throw an exception, any of the commands that succeeded up until that point are rolled back.

Understanding DbContext.SaveChanges Workflow

When you use DbContext to call SaveChanges, one additional step is performed prior to the first step in the ObjectContext.Savechanges workflow. DbContext.SaveChanges calls GetValidationErrors, which runs through the ValidateEntity process. If no errors are found, it then calls ObjectContext.SaveChanges. Because GetValidationErrors has already called DetectChanges, ObjectContext.SaveChanges skips its own call to DetectChanges.

Figure 6-5 shows the execution path when your code calls DbContext.SaveChanges.

Database persistence workflow beginning with DbContext.SaveChanges
Figure 6-5. Database persistence workflow beginning with DbContext.SaveChanges

What this means to you is that, by default, Entity Framework will validate all of the rules specified with ValidationAttributes and IValidatableObject automatically when you call SaveChanges from a DbContext.

If errors are detected during GetValidationErrors, SaveChanges will throw a DbEntityValidationException with the results of GetValidationErrors in its EntityValidationErrors property. In this case, the context will never make the call to ObjectContext.SaveChanges.

In the previous section, you learned that ValidationEntity, called by GetValidationErrors, will not check relationship constraints unless they are specified in configuration. However, ObjectContext.SaveChanges has always checked relationship constraints and continues to do so. Therefore, any relationship constraints that were not validated by GetValidationErrors will be checked in the next stage of the save. The same applies to null complex properties. Since null complex properties are not supported, ObjectContext always checks if a complex property is not null. Having the Required attribute on a complex property makes sense only for consistency reasons (that is, a null complex property violation will be reported the same way as other validation violations).

If you’d like to see this in action, you can modify the ValidateEverything method so that rather than explicitly calling GetValidationErrors, it will call SaveChanges. Replace the final line of the ValidateEverything method from Example 6-14 (i.e., the code line that calls into DisplayErrors) with the code in Example 6-17. You’ll call SaveChanges instead and then display information about any validation exceptions.

Example 6-17. ValidateEverything modified to call SaveChanges instead of GetValidationErrors
try
{
  context.SaveChanges();
  Console.WriteLine("Save Succeeded.");
}
catch (DbEntityValidationException ex)
{
  Console.WriteLine(
    "Validation failed for {0} objects",
    ex.EntityValidationErrors.Count());
}

Because this example contains intentional problems that will be detected during the internal call to GetValidationErrors, ObjectContext.SaveChanges will never be executed and your data will not be persisted to the database. If the validations were to pass, there’s still a chance of an UpdateException when the lower-level ObjectContext.SaveChanges is called internally, but we’re ignoring that possibility in this example.

If you were to run this method, you would find is that a DbEntityValidationException is thrown. DbEntityValidationException has a property called EntityValidationErrors which returns an IEnumerable of something you are already familiar with—EntityValidationResults that were created for each failed entity.

Figure 6-6 shows the DbValidationException in the debug window (with private fields removed for clarity).

Inspecting a DbValidationException
Figure 6-6. Inspecting a DbValidationException

The exception handler in Example 6-17 displays how many EntityValidationResult instances are contained in the exception, in other words, how many entities failed validation when SaveChanges was called.

Because you already know how to iterate through EntityValidationResult objects, you can dig further into the exception if you want to relay the details of the validation errors.

Disabling Validate Before Save

You may want to exert more control over when validation occurs by calling the various validation methods explicitly in your application. You can prevent Entity Framework from triggering the validation during SaveChanges thanks to the DbContext.Configuration property. One of the settings you can configure on DbContext is ValidateOnSaveEnabled. This is set to true by an internal method when you instantiate a new DbContext, which means that it’s true by default on any DbContext class.

You can disable it in the constructor of your context class so that it’s always false whenever you instantiate a new instance of the context.

For example, in BreakAwayContext you could add the following constructor:

public class BreakAwayContext : DbContext
{
  public BreakAwayContext()
  {
    Configuration.ValidateOnSaveEnabled = false;
  }
  ... rest of class logic
}

You can also enable or disable this feature as needed throughout your application by modifying the configuration setting on your context instance.

One benefit of disabling the validation on SaveChanges and calling the validation methods explicitly is that it allows you to avoid having an exception thrown. When validations fail inside of the SaveChanges call, SaveChanges throws the DbEntityValidationException. However, as you’ve seen through this chapter, calling GetValidationResult or GetValidationErrors explicitly returns something whether the validations pass or fail. GetValidationResult returns a ValidationResult that will indicate whether or not the validation passed. GetValidationErrors returns an IEnumerable of ValidationResults for failed validations and if there were none, the IEnumerable will be empty. When application performance is an important factor in your development process, the expense of exceptions might be the deciding factor for choosing the automatic validation during SaveChanges or disabling that and taking control over how and when validation occurs. You’ll learn more about taking advantage of this configuration in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset