Chapter 7. Customizing Validations

In the previous chapter you learned many ways that you can apply validation rules so that the DbContext Validation API can find and check them either on demand or automatically. While you can explicitly validate individual classes and properties directly from the DbEntityEntry method, you can also have the context validate all of its tracked entities as a group, either by calling GetValidationErrors or letting SaveChanges call that method for you. GetValidationErrors then calls ValidateEntity on each of the Added and Modified entities in the context. ValidateEntity then triggers logic that checks the ValidationAttribute and IValidatableObject rules you’ve specified in your classes.

You’ve seen how ValidateEntity works in Chapter 6. In this chapter, you’ll learn how to customize the ValidateEntity method not only by overriding the logic of the method, but also by overriding the method that determines which entities should be validated.

Overriding ValidateEntity in the DbContext

ValidateEntity is a virtual method, meaning that you can override it and add your own custom logic. Like any virtual method, after executing your logic, you can control whether or not it performs the validations it’s designed to execute (for example, validating the ValidationAttributes and IValidatableObject rules).

Example 7-1 shows the ValidateEntity method added into the BreakAwayContext class after using the Visual Studio IDE shortcut to add the overridden method.

Example 7-1. Signature of ValidateEntity override
protected override
 System.Data.Entity.Validation.DbEntityValidationResult
 ValidateEntity(
  System.Data.Entity.Infrastructure.DbEntityEntry entityEntry,
  System.Collections.Generic.IDictionary<object, object> items)
{
  return base.ValidateEntity(entityEntry, items);
}

Note

If you are new to overriding methods in Visual Studio, the IDE has a shortcut to help you insert the method in C# and in VB. In the BreakAwayContext class, type the word override (Overrides for VB) followed by a space. Visual Studio will then show you a list of virtual methods. Select ValidateEntity from the list and the method code will be automatically added to the class.

By ensuring that the System.Data.Entity.Infrastructure, System.Data.Entity.Validation, and System.Collections.Generic namespaces are all added to the using statements at the top of the class file, the method signature becomes a little easier to read:

protected override DbEntityValidationResult ValidateEntity
  (DbEntityEntry entityEntry,IDictionary<object, object> items)
{
  return base.ValidateEntity(entityEntry, items);
}

You can add logic to ValidateEntity that performs additional validations on all types or on a subset of types (for example, a particular type or a particular set of types that inherit from another class or implement from an interface).

Another benefit of inserting logic here is that you have access to the DbContext and therefore can perform validation that depends on other tracked entities or even checks against data in the database. That’s something you can’t do in a ValidationAttribute or in the IValidatableObject.Validate method unless you were to pass a DbContext instance into the type. This would, however, force the type to be aware of the data layer which, if you care about keeping your POCO classes persistence ignorant, is undesirable.

Note

You can read more about persistence ignorance (PI) from the perspective of Entity Framework in Chapter 24 of Programming Entity Framework, 2e, or in any number of resources on the Internet. Here, for example, is a discussion of PI in the scope of an article on the Unit of Work Pattern and Persistence Ignorance by Jeremy Miller in MSDN Magazine: http://msdn.microsoft.com/en-us/magazine/dd882510.aspx#id0420053.

An example of a validation that involves multiple entities is a rule for BreakAway Geek Adventures that a payment must made along with a new reservation. You’ll find a Payment class in the model of the sample download along with a Payments navigation property in the Reservation class. The two classes are listed in Example 7-2.

Example 7-2. Payment class
public class Payment
{
  public Payment()
  {
    PaymentDate = DateTime.Now;
  }

  public int PaymentId { get; set; }
  public int ReservationId { get; set; }
  public DateTime PaymentDate { get; set; }
  public decimal Amount { get; set; }
}

public class Reservation
{
  public Reservation()
  {
    Payments = new List<Payment>();
  }

  public int ReservationId { get; set; }
  public DateTime DateTimeMade { get; set; }
  public Person Traveler { get; set; }
  public Trip Trip { get; set; }
  public Nullable<DateTime> PaidInFull { get; set; }

  public List<Payment> Payments { get; set; }
}

You may want your custom context logic to take precedence over the other validations that would be performed. In other words if the custom logic added in ValidateEntity fails, then don’t bother validating the rules that are specified in ValidationAttributes or IValidatableObject. If no errors are detected in the custom context logic, then the base.ValidateEntity method will get called to check rules defined with ValidationAttributes and IValidatableObject. Figure 7-1 helps you visualize this workflow. You’ll explore a number of other possible workflows later in the chapter.

Calling base validation only if custom validation finds no errors
Figure 7-1. Calling base validation only if custom validation finds no errors

The ValidateEntity signature contains an entityEntry parameter. This represents the DbEntityEntry for the object currently being processed by the SaveChanges method. DbEntityEntry allows you to navigate to the actual object instance that it represents. You cast with the as operator to ensure you are working with the correct type:

var reservation = entityEntry.Entity as Reservation;
if (reservation !=null)
{
  //logic on reservation goes here
}

From here you can use the reservation instance or work directly against the change tracker through entityEntry.

Example 7-3 shows code that validates the new rule for Reservation. The code instantiates a new DbEntityValidationResult for this particular entry. Then, if the entry is for a Reservation and is new (Added) but has no Payments, a new error is added to the DbEntityValidationResult. If the reservation validation results in errors (in which case, result.IsValid will be false), those results are returned from ValidateEntity and the base validation is not called. If the result is valid, the base method is called instead.

Note

Remember from Chapter 6 that ValidateEntity temporarily disables lazy loading, so the context will not be looking for any payments in the database.

Example 7-3. ValidateEntity calling base validation only if custom validation passes
protected override DbEntityValidationResult ValidateEntity
    (DbEntityEntry entityEntry, IDictionary<object, object> items)
{
  var result = new DbEntityValidationResult(entityEntry,
                    new List<DbValidationError>());
  var reservation = entityEntry.Entity as Reservation;
  if (reservation != null)
  {
    if (entityEntry.State == EntityState.Added &&
         reservation.Payments.Count == 0)
    {
      result.ValidationErrors.Add(
        new DbValidationError(
          "Reservation",
          "New reservation must have a payment.")
        );
    }
  }
  if (!result.IsValid)
  {
    return result;
  }
  return base.ValidateEntity(entityEntry, items);
}

Warning

Keep in mind an important detail of the processing steps described earlier in Chapter 6. GetValidationErrors (called by SaveChanges) will execute ValidateEntity on all of the tracked entities before it begins constructing commands for the database. When designing custom logic for ValidateEntity, don’t expect entities that have already been validated to be in the database by the time you reach the next entity.

If you have multiple validations to perform in ValidateEntity, it could get cluttered up pretty quickly. Example 7-4 shows the same logic as Example 7-3, but with the validation specific to the Reservation split out to a separate method.

Example 7-4. ValidateEntity calling base validation only if custom validation passes
protected override DbEntityValidationResult ValidateEntity
    (DbEntityEntry entityEntry, IDictionary<object, object> items)
{
  var result = new DbEntityValidationResult(entityEntry,
                    new List<DbValidationError>());

  ValidateReservation(result);

  if (!result.IsValid)
  {
    return result;
  }

  //call base validation
  return base.ValidateEntity(entityEntry, items);
}

private void ValidateReservation(DbEntityValidationResult result)
{
  var reservation = result.Entry.Entity as Reservation;
  if (reservation != null)
  {
    if (result.Entry.State == EntityState.Added &&
                              reservation.Payments.Count == 0)
    {
      result.ValidationErrors.Add(
        new DbValidationError(
          "Reservation",
          "New reservation must have a payment.")
        );
    }
  }
}

Considering Different Ways to Leverage ValidateEntity

In the previous example, ValidateEntity executes our context-based business validations. If no errors are found, it continues on to execute the base ValidateEntity method, which checks any rules defined with type validation (IValidatableObject rules) and property validation (ValidateAttribute rules). That’s just one execution path you could set up in ValidateEntity.

Note

Throughout this chapter, we’ll present different forms of the ValidateEntity method. If you are following along with the code samples, you might want to retain each version of ValidateEntity in the BreakAwayContext class. What we did while developing our samples was to wrap a complier directive around the methods that we don’t want to use anymore. This is cleaner than commenting out code. In C# you can add #if false before the beginning of the method and then #endif after the end of the method.

#if false
protected override DbEntityValidationResult
 ValidateEntity(DbEntityEntry entityEntry,
                IDictionary<object, object> items)
{
   ...method code
}
#endif

The code inside the directive will be grayed out and ignored by the compiler. Change the directive to #if true to reengage it.

In Visual Basic the directive looks like this:

#If False Then
#End If

You could reverse this logic, returning the base ValidateEntity results first and, if there are none, executing your custom logic as visualized in Figure 7-2.

Calling custom validation only if base validation finds on errors
Figure 7-2. Calling custom validation only if base validation finds on errors

As an example, you might want to check a value for uniqueness in the database, perhaps to ensure that new Lodgings have a unique Name and Destination combination. You can do this in ValidateEntity because you have access to the context and therefore can execute a query such as

Lodgings.Any(l => l.Name == lodging.Name && 
  l.DestinationId == lodging.DestinationId);

But Lodging.Name already has a number of ValidationAttribute rules applied: Required, MinLength, and MaxLength. You might prefer to ensure that these three attributes are satisfied before wasting the trip to the database to check for a duplicate lodging. You could run the base ValidateEntity method first and return its errors if there are any. If there are no errors found in the base validation, continue on to the new validation logic, which checks the database for an existing lodging with the name and destination of the one about to be added. Example 7-5 demonstrates this logic. First, base.ValidateEntity is called. If its results are valid, a custom validation method, ValidateLodging, is called and its errors, if any, are added to the results collection, which is returned at the end.

Example 7-5. Executing context validation only if property and type validation pass
protected override DbEntityValidationResult ValidateEntity
 (DbEntityEntry entityEntry, IDictionary<object, object> items)
{
  var result = base.ValidateEntity(entityEntry, items);

  if (result.IsValid)
  {
    ValidateLodging(result);
  }
  return result;
}

private void ValidateLodging(DbEntityValidationResult result)
{
  var lodging = result.Entry.Entity as Lodging;
  if (lodging != null && lodging.DestinationId != 0)
  {
    if (Lodgings.Any(l => l.Name == lodging.Name &&
                          l.DestinationId == lodging.DestinationId))
    {
      result.ValidationErrors.Add(
        new DbValidationError(
          "Lodging",
          "There is already a lodging named " + lodging.Name +
          " at this destination.")
        );
    }
  }
}

Note

Checking for uniqueness in Example 7-5 may have made you wonder about a simpler way to define unique validations. The Entity Framework team is working on a feature that would allow you to define Unique Constraints directly in the model. You can read more details about this in their March 2011 blog post at http://blogs.msdn.com/b/efdesign/archive/2011/03/09/unique-constraints-in-the-entity-framework.aspx.

We created a method in the console app called CreateDuplicateLodging to test this validation, shown in Example 7-6.

Example 7-6. Inserting Lodgings to test validations
private static void CreateDuplicateLodging()
{
  using (var context = new BreakAwayContext())
  {
    var destination = context.Destinations
      .FirstOrDefault(d => d.Name == "Grand Canyon");

    try
    {
      context.Lodgings.Add(new Lodging
      {
        Destination = destination,
        Name = "Grand Hotel"
      });

      context.SaveChanges();
      Console.WriteLine("Save Successful");
    }
    catch (DbEntityValidationException ex)
    {
      Console.WriteLine("Save Failed: ");
      foreach (var error in ex.EntityValidationErrors)
      {
        Console.WriteLine(
          string.Join(Environment.NewLine,
          error.ValidationErrors.Select(v => v.ErrorMessage)));
      }

      return;
    }
  }
}

The critical part of this method inserts Grand Hotel at the Grand Canyon while the bulk of the method is code to display errors for this demonstration. Our seed data includes a Lodging called Grand Hotel at the Destination Grand Canyon. So our new Lodging will be a duplicate. If you run this method from the console application’s main method, ValidateEntity will call ValidateLodging and discover the duplication. The console will report the error:

Save Failed:
There is already a lodging named Grand Hotel at this destination.

Now let’s add in a validation that will fail the base.ValidateEntity check. Modify the Lodging to add a data annotation to the MilesFromNearestAirport property. The RangeAttribute specifies a valid value range for the property. Here we’ll say that anything from .5 to 150 miles will be valid:

[Range(.5,150)]
public decimal MilesFromNearestAirport { get; set; }

If you run the application again, you’ll see this message in the console window:

Save Failed:
The field MilesFromNearestAirport must be between 0.5 and 150.

There’s no mention of the duplication. That’s because the ValidateEntity method is designed to check the property and type rules first and return the exception right away if any are found—before it has called ValidateLodging.

Let’s return to the ValidateEntity method and force it to return the combination of validation errors checked in the custom logic and in the logic check by base.ValidateEntity, as visualized in Figure 7-3.

Combining errors from custom and base validation
Figure 7-3. Combining errors from custom and base validation

Example 7-7 demonstrates code that will allow you to collect the results of the base validation and then add any additional errors found in the custom logic to that result before returning the combined errors from ValidateEntity.

Example 7-7. Combing type and property validation results with context results
protected override DbEntityValidationResult ValidateEntity
 (DbEntityEntry entityEntry, IDictionary<object, object> items)
{
  var result = base.ValidateEntity(entityEntry, items);
  ValidateLodging(result);
  return result;
}

Running the CreateDuplicateLodging method one last time will now display both errors:

Save Failed:
The field MilesFromNearestAirport must be between 0.5 and 150.
There is already a lodging named Grand Hotel at this destination.

Note

You can include multiple validation checks in ValidateEntity. These examples only contain one at a time for the sake of brevity.

Now that you’ve seen a few possible workflows for executing validations in ValidateEntity, you can mimic these or define your own workflow when customizing ValidateEntity.

Updating Data During SaveChanges

Quite often, there are last-minute modifications that you want to make to data before it’s sent to the database. One example is setting DateAdded and DateModified values in your classes. While there are a number of ways to achieve this in .NET code, you may wish to perform this logic in the data layer. Because the context is already iterating through all of its Added and Modified entities when it calls ValidateEntity, it’s tempting to add this logic into ValidateEntity rather than perform an additional enumeration in the SaveChanges method.

It’s possible to do this, but it is not recommended. The following are some downsides to putting this type of logic inside of ValidateEntity:

  • The ValidateEntity method is designed for performing validations. Using it for other purposes infringes on the principle of Singular Responsibility—a coding principle that exists to help you in the quest for maintainable code.

  • You might not want to bother with modifying data before you know it’s valid and headed for the database. You could call base.ValidateEntity prior to the update logic as shown in Example 7-5, but a later entity might be invalid, rendering all modifications moot.

  • By the time you’re in the ValidateEntity method, DetectChanges has already been called and you need to be careful about how you update values.

One alternative is to override SaveChanges and iterate through entities to apply the dates before base.SaveChanges does a second iteration to validate the entities. Keep in mind that after this, there is a third iteration—the one that creates and executes the commands for each Added, Modified, and Deleted entity to the database.

If you override SaveChanges to apply the date values, ValidateEntity will be called afterwards, during base.SaveChanges. If invalid data is found, the effort and processing time taken to update the date properties was wasted.

In the next section, we’ll look at pros and cons of the options you have to perform the date modifications during SaveChanges and then show you an efficient example of modifying ModifiedDate and AddedDate properties during SaveChanges.

Overriding SaveChanges When Validation Occurs

If you want to set values inside of SaveChanges and you are leveraging the Validation API, you have a number of choices:

  • Update the data values in SaveChanges and let base.SaveChanges perform the validation (through ValidateEntity) as it normally would.

  • Turn off ValidateOnSaveEnabled and iterate through entities, calling GetValidationResult and then the date fix-up for each entity.

  • Turn off ValidateOnSaveEnabled and iterate through entities, fixing up the dates for each entity and then calling GetValidationResult.

  • Turn off ValidateOnSaveEnabled. Call GetValidationErrors (which will iterate through entities) and then iterate again performing the date fix-ups.

  • Turn off ValidateOnSaveEnabled. Iterate through entities to perform the date fix-ups, and then call GetValidationErrors (which will iterate through entities). This would be no different than the first option in this list.

There are pros and cons to each approach. We’ll walk through one of them and present the pros and cons of the others. In the end, you should be able to choose the approach that best fits your application.

For this example, we’ll perform validations by calling GetValidationErrors and then update two date fields for any entity that inherits a new base class, Logger. In this scenario, we have confidence that updating the date fields won’t break any validation rules, so it is safe to perform this task after the validations have been checked by the API. In a real-world application, you should have automated tests in place to ensure that future modifications to the application don’t break this assertion.

Example 7-8 shows a new abstract class called Logger, which exposes two new date fields. It also has a public method, UpdateModificationLogValues, for updating those fields. This method may be used by any number of business logic methods that access your classes.

Example 7-8. Logger base class
  public abstract class Logger
  {
    public DateTime LastModifiedDate { get; set; }
    public DateTime AddedDate { get; set; }

    public void UpdateModificationLogValues(bool isAdded)
    {
      if (isAdded)
      {
        AddedDate = DateTime.Now;
      }
      LastModifiedDate = DateTime.Now;
    }
  }

Modify the Activity class to inherit from Logger:

public class Activity : Logger

Now you can override SaveChanges, as shown in Example 7-9. Back in Chapter 5 you overrode SaveChanges to perform logging; you should replace the logging implementation with this new implementation. Notice that the first action in the method is to store the current setting of AutoDetectChangesEnabled. That’s because we’re going to temporarily set it to false and want to reset it before exiting the method. The reason we’re setting it to false is to control exactly when DetectChanges is called.

Example 7-9. SaveChanges overridden to perform validation and updates
public override int SaveChanges()
{
  var autoDetectChanges = Configuration.AutoDetectChangesEnabled;

  try
  {
    Configuration.AutoDetectChangesEnabled = false;
    ChangeTracker.DetectChanges();
    var errors = GetValidationErrors().ToList();
    if (errors.Any())
    {
      throw new DbEntityValidationException
       ("Validation errors found during save.", errors);
    }

    foreach (var entity in this.ChangeTracker.Entries()
                            .Where(e =>
                                   e.State ==EntityState.Added ||
                                   e.State == EntityState.Modified))
    {
      ApplyLoggingData(entity);
    }
    ChangeTracker.DetectChanges();

    Configuration.ValidateOnSaveEnabled = false;
    return base.SaveChanges();
  }
  finally
  {
    Configuration.AutoDetectChangesEnabled = autoDetectChanges;
  }
}

The ApplyLoggingData method shown in Example 7-10 will call UpdateModificationLogValues on any entities that inherit from Logger, passing in a Boolean to signal whether or not the AddedDate needs to be set. Once all the dates have been updated, we call DetectChanges to ensure that Entity Framework is aware of any changes that were made.

Example 7-10. ApplyLoggingData method in BreakAwayContext class
private static void ApplyLoggingData(DbEntityEntry entityEntry)
{
  var logger = entityEntry.Entity as Logger;
  if (logger == null) return;
  logger.UpdateModificationLogValues
   (entityEntry.State == EntityState.Added);
}

Now we’ll test that the date fields get changed by adding a new, valid Activity using the InsertActivity method (added into the console application) shown in Example 7-11. The code displays the dates after SaveChanges has been called. After the first call to SaveChanges, the code makes a modification and saves again, displaying the dates a second time.

Example 7-11. Console method to observe Logger properties updated in SaveChanges
private static void InsertActivity()
{
  var activity = new Activity { Name = "X-C Skiing" };
  using (var context = new BreakAwayContext())
  {
    context.Activities.Add(activity);
    try
    {
      context.SaveChanges();
      Console.WriteLine("After Insert:   Added={0}, Modified={1}",
                         activity.AddedDate, activity.LastModifiedDate);
      //pause 2 seconds
      System.Threading.Thread.Sleep(2000);
      activity.Name = ("X-C Skating");
      context.SaveChanges();
      Console.WriteLine("After Modified: Added={0}, Modified={1}",
                         activity.AddedDate, activity.LastModifiedDate);
    }
    catch (DbEntityValidationException ex)
    {
      Console.WriteLine("Save Test Failed: " +
                         ex.EntityValidationErrors.FirstOrDefault()
                           .ValidationErrors.First().ErrorMessage);
    }
  }
}

When you run this, you’ll see that the newly inserted activity has its AddedDate and LastModifiedDate values populated after being saved. Then when the activity is edited and saved again, you can see that its LastModifiedDate value has been updated again, thanks to the combined logic in SaveChanges and the Logger class:

After Insert:   Added=12/9/2011 12:16:27 PM, Modified=12/9/2011 12:16:27 PM
After Modified: Added=12/9/2011 12:16:27 PM, Modified=12/9/2011 12:16:33 PM

This works because by the time we’re calling ApplyLoggingData, the context is already aware that these are either Added or Modified entities and is already planning to persist them to the database.

You can avoid the need to call DetectChanges by changing properties directly through the change tracker. That’s logic that you won’t be able to (or want to) embed into your domain classes (which we prefer to not have any knowledge of Entity Framework). So you’ll have to do that within the context. Example 7-12 shows what the ApplyLoggingData would look like if you were to set the properties through the change tracker.

Example 7-12. Using the change tracker to update scalar properties
private static void ApplyLoggingData(DbEntityEntry entityEntry)
{
  var logger = entityEntry.Entity as Logger;
  if (logger == null) return;
  entityEntry.Cast<Logger>()
   .Property(l => l.ModifiedDate).CurrentValue = DateTime.Now;
  if (entityEntry.State==EntityState.Added)
  {
    entityEntry.Cast<Logger>()
     .Property(l => l.AddedDate).CurrentValue = DateTime.Now;
  }
}

In Chapter 5, you learned how to work with scalars as well as collection and reference navigation properties through the change tracker. If you want to make changes to navigation properties at the data layer, you should do so using the change tracker, as shown with the scalar property changes made in Example 7-12. If you used the navigation properties of the class directly, whether you do that in the context code or call into code in the type you are modifying (for example, Logger.UpdateModificationLogValues), you run a substantial risk of those changes not being persisted to the database. Again, this is dependent on where in the workflow DetectChanges is being called. If you are in the habit of using the change tracker to make the changes, you don’t have to worry about DetectChanges.

Comparing ValidateEntity to SaveChanges for Custom Logic

If you’ve been using Entity Framework for a few years, you might be familiar with various options we’ve had for applying validation logic and wondering how ValidateEntity fits into the picture. The first version of Entity Framework gave us the ObjectContext.SavingChanges event, which let developers execute validation or other logic when SaveChanges was called. Your logic added into SavingChanges would be executed and then Entity Framework would execute its internal logic. Entity Framework 4 brought us the added benefit of a virtual SaveChanges method so we could not only have Entity Framework execute our custom logic when calling SaveChanges, but we had the option of completely halting the internal code.

You can also override SaveChanges when it is called from DbContext. DbContext doesn’t have a SavingChanges event because with the virtual SaveChanges, the former approach is redundant. The only reason SavingChanges still exists as a method of ObjectContext is for backward compatibility. But if you need to, you can get to ObjectContext from DbContext by first dropping down to the ObjectContext using the IObjectContextAdapter, as you’ve seen previously in this book.

ValidateEntity is yet another extensibility point that is available during SaveChanges. But as you’ve seen in this chapter, you should be considerate of when your code makes use of ValidateEntity or SaveChanges to insert your logic.

ValidateEntity by default is executed on every Added or Modified object being tracked. It is a good replacement for code that you may have put in SaveChanges where you iterate through each tracked object and perform some validation logic on it.

A big caveat with the ValidateEntity method, however, is that it is executed after DetectChanges has been called, so you have to be careful about how you go about setting properties. You can safely set properties using the DbEntityEntry, but our preference is to avoid adding nonvalidation logic into a method that is designated for performing validations.

The SaveChanges method is a good place to execute logic where you want to do something with a group of objects. For example, you might want to log how many reservations are added in a particular update. While you do have access to this in the ValidateEntity method, this is something you want to execute only once during a save.

Microsoft’s guidance is to use ValidateEntity to perform validation logic (rule checking) only. Their primary reason for this guidance is concern over incorrectly coded property modifications that won’t get picked up by the context if the developer is unaware of the fact that DetectChanges was already called—and will not be called again. Another is that in ValidateEntity, the team has ensured that lazy loading won’t have unexpected effects on the validation.

From a perspective of architectural guidance, yet another reason is that by not forcing ValidateEntity to perform non-validation logic, you follow the principle of Single Responsibility. ValidateEntity is for validating. Single Responsibility helps to keep in mind the fact that if you introduce other features into that method, you’ll increase the difficulty of maintaining your application as it grows and evolves.

Using the IDictionary Parameter of ValidateEntity

So far we’ve focused on the entityEntry parameter of ValidateEntity. There is also an IDictionary<object, object> parameter available:

protected override DbEntityValidationResult ValidateEntity
    (DbEntityEntry entityEntry, IDictionary<object, object> items)

By default, the value of this parameter is null, but you can use the parameter to pass additional values to custom implementations of IValidatableObject.Validate or ValidationAttributes.

Note

Watch for a change to the IDictionary parameter in a future version of Entity Framework: it may be changed to default to an empty dictionary rather than null. That would make coding against it simpler. As of Entity Framework 4.3, the parameter is still null.

For example, recall the signature of IValidatableObject.Validate:

public IEnumerable<ValidationResult>
 Validate(ValidationContext validationContext)

ValidationContext implements iDictionary. Entity Framework passes the items defined in ValidateEntity to this validationContext parameter. It’s also possible to use a ValidationContext when creating overrides of the ValidationAttribute class. (See the MSDN Library documentation topic ValidationAttribute Class for more information about this feature: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute.aspx)

You can create a dictionary of objects in the ValidateEntity method and pass them along in the base ValidateEntity call by assigning the dictionary to the items variable. Those objects would then be available for you to use in validations that accept a ValidationContext.

For example, you may want to be sure that a newly added or modified payment does not cause the saved payments to exceed the cost of the trip on which the reservation is booked. To validate this rule, you would need the data layer to access the database when it’s validating new or modified payment objects. But rather than performing all of the calculations in the data layer, you could have the data layer provide the necessary information to the payment so that the rule can be included in the business logic for the Payment itself.

Note

I’ve seen examples where the DbContext itself is passed back into the ValidationContext of IValidatable.Validate or ValidationAttributes from ValidateEntity. Neither of us are fans of this pattern because it forces the object to be aware of the context, of the data layer, and of Entity Framework. Not only is the class no longer POCO, but it also removes another quality that I have learned to admire, persistence ignorance.

Example 7-13 shows the Validate method for Payment after we’ve modified Payment to implement the IValidatableObject interface. There are two validations in the method. The method first checks to see if there are PaymentSum and TripCost items in the validationContext. The method expects that the method that has triggered Validate will have created these items in the dictionary passed in as the validationContext parameter. If they are there, the method will use those to compare the payments to the trip cost.

Example 7-13. IValidatableObject.Validate method using ValidationResult
public IEnumerable<ValidationResult> Validate(
  ValidationContext validationContext)
{
  var vc = validationContext; //for book readability

  if (vc.Items.ContainsKey("DbPaymentTotal")
    && vc.Items.ContainsKey("TripCost"))
  {
    if (Convert.ToDecimal(vc.Items["DbPaymentTotal"]) + Amount >
      Convert.ToDecimal(vc.Items["TripCost"]))
    {
      yield return new ValidationResult(
        "Oh horrors! The client has overpaid!",
        new[] { "Reservation" });
    }
  }
}

Warning

This example is to demonstrate the use of the IDictionary and not meant as the de facto pattern for checking for overpayments in a production application. There are many more factors to take into consideration for this particular use case regardless of whether you are using Entity Framework validation or any other validation pattern.

Given that the Payment class validation expects to be provided with a ValidationContext that supplies the sum of Payments for a single Reservation that are already in the database and the cost of the trip for which the payment and reservation are made, the ValidateEntity method needs to add those values into the IDictionary.

Example 7-14 does just that—retrieving the total of payments for the Reservation and the TripCost from the database, and then adding them to the _items IDictionary. This example also places the particular validation logic, FillPaymentValidationItems, in a separate method in order to keep the ValidateEntity method cleaner.

Example 7-14. ValidateEntity passing values using the IDictionary
protected override DbEntityValidationResult ValidateEntity
    (DbEntityEntry entityEntry, IDictionary<object, object> items)
{
  var _items = new Dictionary<object, object>();
  FillPaymentValidationItems(entityEntry.Entity as Payment, _items);
  return base.ValidateEntity(entityEntry, _items);
}

private void FillPaymentValidationItems(Payment payment, Dictionary<object, object> _items)
{
  if (payment == null)
  {
    return;
  }
  //calculate payments already in the database
  if (payment.ReservationId > 0)
  {
    var paymentData = Reservations
      .Where(r => r.ReservationId == payment.ReservationId)
      .Select(r => new
      {
        DbPaymentTotal = r.Payments.Sum(p => p.Amount),
        TripCost = r.Trip.CostUSD
      }).FirstOrDefault();
    _items.Add("DbPaymentTotal", paymentData.DbPaymentTotal);
    _items.Add("TripCost", paymentData.TripCost);
  }
}

Notice that ValidateEntity doesn’t check the type of the entityEntry.Entity. Instead it leverages the performance benefit of casting with as, which will return a null if the entity is not a Payment. Then the helper method does a quick check for a null before bothering with the inner logic. This is simply a design decision made on our part.

The method first ensures that the ReservationId has been set. If the user is adding a new Reservation and Payment together, then the Reservation won’t have a ReservationId yet and therefore it won’t have been set on the Payment.

Controlling Which Entities Are Validated in ValidateEntity

As we’ve pointed out earlier, by default, Entity Framework will only send Added and Modified entities to the ValidateEntity method. Internal code checks the state before calling ValidateEntity in a virtual (Overridable in VB) method of DbContext called ShouldValidateEntity.

After some internal evaluation, ShouldValidateEntity returns a Boolean based on this line of code which checks to see if the state is either Modified or Added:

return ((entityEntry.State &
(EntityState.Modified | EntityState.Added)) != 0);

Because ShouldValidateEntity is virtual, you can override the default logic and specify your own rules for which entities are validated. You may want only certain types to be validated or you may want validation performed on Deleted objects.

As an example, it wouldn’t make sense to delete Reservations for Trips that are in the past. If you want to capture deleted Reservations in the data layer and check to make sure they aren’t for past Trips, you’ll have to make sure the deleted Reservation makes it to the ValidateEntity method. You don’t have to send all deleted objects. Instead you can “open up” the pipeline only for deleted Reservations.

Add the ShouldValidateEntity method to the BreakAwayContext. You can use the same steps to override the method explained in the earlier note when adding the ValidateEntity method.

Example 7-15 shows the ShouldValidateEntity method after we’ve added additional logic to allow deleted Reservation objects to be validated as well. If the entity being evaluated is a deleted Reservation, ShouldValidateEntity will return true. If not, it will perform its default logic to determine whether or not to validate the entity.

Example 7-15. Overriding ShouldValidateEntity
protected override bool ShouldValidateEntity(DbEntityEntry entityEntry)
{
  return base.ShouldValidateEntity(entityEntry)
       || ( entityEntry.State == EntityState.Deleted
             && entityEntry.Entity is Reservation);
}

Once you’ve allowed the entry to pass through to ValidateEntity, you’ll need to add logic to ValidateEntity to perform the new validation.

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

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