InterceptionContext is now passed to TypeInterceptor methods and ChangeInterceptor methods. This may result in breaking changes depending on if and how you have inherited those classes, but I’ve done my best to preserve existing behavior. I marked the obsolete methods as such.
The purpose of this change is enable the scenario where you want to write a log-record back to the database when a record is saved or deleted.
Update:
0.8.0 did not contain the updated assemblies.
0.8.1 does.
What happened?
My build server is configured so that it only creates and publishes packages from the last pinned build. I forgot to pin the build that has the changes. I’ve pinned the build and republished 0.8.1. I’ve created a workitem for myself to separate package creation from package publication so that I can inspect the package before it’s sent to Nuget.
Issues Fixed
The cause of the bug is that EntityFramework resets the EntityState to UnChanged after writing the object to the database. This means that I cannot tell which operation was performed on the entity unless I memoize the state before the operation. The effect of this change is that I can no longer get the EntityState of the item before the operation from the DbEntityEntry.
Breaking Changes
If you are inheriting from TypeInterceptor or ChangeInterceptor the signature of OnBefore and OnAfter has changed.
The new signatures are
protected override void OnBefore(DbEntityEntry item, EntityState state) protected override void OnAfter(DbEntityEntry item, EntityState state)
Breaking Changes
- The ISoftDelete interface was moved out of the Isg.EntityFramework.Interceptors.SoftDelete assembly into a new package called Isg.Domain
New Features
- A new package was added for auditing fields on data models. The new package is Isg.EntityFramework.Interceptors.Auditable.
Isg.EntityFramework.Interceptors.Auditable
If you have a class that implements IAuditable the AuditableChangeInterceptor will use IPrincipal and IClock to assign audit fields. If you are inserting a new record, CreateUser and UpdateUser will be set to IPrincipal.Identity.Name, and CreateDate and UpdateDate will be set to IClock.Now. If you are updating an existing record, only the Update fields will be modified.
public interface IAuditable { DateTime CreateDate { get; set; } string CreateUser { get; set; } DateTime UpdateDate { get; set; } string UpdateUser { get; set; } }
The Problem
I’ve been struggling for awhile to figure out how to get Entity Framework to set and unset application roles in Sql Server when opening and before closing a connection. The problem is that ConnectionState does not provide a Closing state that fires before a connection is closed.
It was suggested to me to turn of connection pooling. Errrr, no. We want connection pooling for our applications. I also don’t want to have to manually open and close the connection every time I create a DbContext. That’s just messy and irritating.
The next obvious thing to do would be to create a DbConnectionDecorator to wrap the existing database connection and expose the Closing event. This proved to be very difficult because Entity Framework does not expose when and how it opens connections.
Grrrrrr.
The Solution
What’s that you say? I can implement my own EntityFramework Provider? There’s a provider wrapper toolkit I can plug into to do this? Awesome!
Oh wait—that looks really, really, REALLY complicated? You mean I can’t just decorate a single object? I have to decorate a whole family of objects?
Hmmmm.
Alright, tell you what I’ll do. I’ll implement the provider wrapper using the toolkit as best I can—but then I’m going to strip away everything I don’t actually need. Besides, if I just make the various data related events observable, it’s nothing to Trace the output. And Cacheing can easily be added as a IDbContext Decorator anyway. I don’t really get why that should be done at the Provider level.
Configuring Your Application to use the Provider
To use the new provider, first install the package. At application startup, you’ll need to register the provider and tell it which other provider you are wrapping. The registration process will set the ObservableConnectionFactory as the default connection factory used by EF unless you pass the optional setAsDefault:false.
Consuming the Provider Events
ObservableConnection exposes several new events, including Opening, Opened, Failed, Closing, and Closed. However, to subscribe to those events directly requires that you cast the DbConnection exposed by EF to ObservableDbConnection, which strikes me as a little cumbersome (not to mention a violation of the LSP). My first use case demands that I handle the Opening and Closing events the same way for every DbConnection application-wide. To that end, ObservableDbConnection (and ObservableDbCommand) pushes its event messages onto a static class called Hub.
Guarantees
This code is brand-spanking new and it hasn’t had time to bake yet. I’m using it, but it’s entirely possible that there are unforeseen problems. Feel free to report issues to and/or contribute to the open-source project on BitBucket. Until then, know that it has been rigorously test and that it works on my machine.
What’s new?
Testability has been kind of an issue for us with code that relies on the static Validator class. To improve the situation, I added IValidationEngine as an alternative. Validator is still fully backwards compatible
- Addition of the IValidationEngine interface
- The DefaultValidationEngine requires an instance of IValidatorProvider for its constructor
- Validator.SetValidatorProvider() has been marked obsolete. You can still use it, but it will have the side-effect of over-setting Validator.ValidationEngine to a new instance of DefaultValidationEngine.
- Added a NinjectModule to Simple.Validation.Ninject that configures the DefaultValidationEngine and DefaultValidatorProvider automatically.
New Feature
Message() method has been overloaded to accept a function to build the validation message while the object/property is being validated.
Example:
var validator = Properties<Employee> .For(e => e.FirstName) .Required() .Message((context, value) => { customMessage = string.Format("Custom Message format '{0}'", value); return customMessage; });
I’ve added a LookUp property to the InterceptionContext to make it easier to see which rows were affected during the After() interception phase.
public class InterceptionContext { public DbContextBase DbContext { get; internal set; } public ObjectContext ObjectContext { get; internal set; } public ObjectStateManager ObjectStateManager { get; internal set; } public DbChangeTracker ChangeTracker { get; internal set; } public IEnumerable<DbEntityEntry> Entries { get; internal set; } public ILookup<EntityState, DbEntityEntry> EntriesByState { get; internal set; } ... snipped for brevity
EntriesByState is populated prior to the call to Before(). Added and Modified entities will have their EntityState reverted to UnChanged after SaveChanges() is called. EntriesByState preserves the original state of the entities so that After() interceptors can make use of new Id’s and such.
New Features
Added an assertion api to the PropertyValidator so that the Properties<T> api can be extended by clients.
The order of operations for the Properties api is as follows: If(), Required(), Assertions().
As an example, customized support for boolean properties was added to the library using the following code:
public static class BooleanPropertyExtensions { public static PropertyValidator<TContext, bool> IsTrue<TContext>(this PropertyValidator<TContext, bool> self) { self.Assert((t, p) => p); return self; } public static PropertyValidator<TContext, bool> IsFalse<TContext>(this PropertyValidator<TContext, bool> self) { self.Assert((t, p) => !p); return self; } public static PropertyValidator<TContext, bool?> IsTrue<TContext>(this PropertyValidator<TContext, bool?> self) { self.Assert((t, p) => p.GetValueOrDefault()); return self; } public static PropertyValidator<TContext, bool?> IsFalse<TContext>(this PropertyValidator<TContext, bool?> self) { self.Assert((t, p) => p.HasValue && !p.Value); return self; } }
Breaking Changes
RangePropertyValidator has been removed. It’s functionality is still available, but it has been entirely replaced by extension methods that use the new Assert() method on the PropertyValidator.
ValidationResultTypes have been completely removed. Then intent for ValidationResult.Type is that it is custom per project.
Silverlight 4
The entire Simple.Validation library has been compiled for Silverlight and included in the NuGet package.
Conditional Property Validators
An If() method has been applied to the following property validators:
- EnumerablePropertyValidator
- RangePropertyValidator
- ReferencePropertyValidator
- StringPropertyValidator
When If() is called with the required Predicate, the validator will only apply when the condition specified by the Predicate is met.
[Test] public void If_PredicateIsFalse_ShouldNotValidate() { // Arrange var validator = Properties<Employee> .For(e => e.Age) .GreaterThanOrEqualTo(18) .If(e => e.Age != -1) ; // Act var employee = new Employee() { Age = -1 }; var results = validator.Validate(employee); // Assert Assert.That(results, Is.Empty); }
A new release of Simple.Validation is available on NuGet.
The changes include:
- Fluent Properties API
- AncestorTypeValidator
- CompositeValidator
- EnumerablePropertyValidator
- ReferencePropertyValidator
While Simple.Data is not intended to focus on property-level validation so much that more complex validation scenarios are difficult, it must be recognized that property-level validation accounts for a large part of validation scenarios. In keeping with the goal of simplicity, Simple.Validation provides some mechanisms for wiring up property-level validations quickly. The Fluent Properties API provides factory methods for creating validators for common property types. When used in conjunction with the CompositeValidator<T> it is a trivial task to get simple property-level validation implemented quickly.
This code is from the Personnel.Sample project included in the source code.
public class SaveAddressValidator : CompositeValidator<Address> { public override bool AppliesTo(string rulesSet) { return rulesSet == RulesSets.Crud.Save; } protected override IEnumerable<IValidator<Address>> GetInternalValidators() { yield return Properties<Address>. For(a => a.Line1) .Length(0, 50) .Required() .Message("Address Line 1 is required."); yield return Properties<Address> .For(a => a.Line2) .Length(0, 50) .NotRequired() .Message("Line 2 must be between 0 and 50 characters in length."); yield return Properties<Address> .For(a => a.Line3) .Length(0, 50) .NotRequired() .Message("Line 3 must be between 0 and 50 characters in length."); yield return Properties<Address> .For(a => a.PostalCode) .Length(0, 20) .NotRequired() .Message("Postal Code must be between 0 and 20 characters in length."); yield return Properties<Address> .For(a => a.Country) .Length(0, 3) .NotRequired() .Message("Country must be between 0 and 3 characters in length."); yield return Properties<Address> .For(a => a.StateOrProvince) .Length(0, 50) .NotRequired() .Message("StateOrProvince must be between 0 and 50 characters in length."); } }
In this sample, each call to Properties<T>.For() returns an instance of StringPropertyValidator. CompositeValidator will accumulate the results from each of the property-level validators and return them all as a single result set. What about non-string properties?
public class SaveEmployeeValidator : CompositeValidator<Employee> { public override bool AppliesTo(string rulesSet) { return rulesSet == RulesSets.Crud.Save; } protected override IEnumerable<IValidator<Employee>> GetInternalValidators() { yield return Properties<Employee> .For(e => e.FirstName) .Length(3, 50) .Required() .IgnoreWhiteSpace(); yield return Properties<Employee> .For(e => e.LastName) .Length(3, 50) .Required() .IgnoreWhiteSpace() ; yield return Properties<Employee> .For(e => e.Age) .MinValue(18) .MaxValue(65) ; yield return Properties<Employee> .For(e => e.Address) .Required() .Cascade("Save") ; yield return Properties<Employee> .For(e => e.ContactInfo) .Required() .Count(1) .Unique<ContactInfo>(c => c.Type) .Cascade("Save"); } }
Employee.Age is of type Int32. Properties<T>.For() any property that is convertible to IComparable will return an instance of RangePropertyValidator. If the property is a reference type, it will return an instance of ReferencePropertyValidator, and if it’s a collection type it will return an instance of EnumerablePropertyValidator.
ReferencePropertyValidator and EnumerablePropertyValidator are similar in that they support Cascading validation onto the reference or collection property itself. This means that Valdiator.Validate<T> will be called for the value of the reference property, or for each element of the collection property. The results will be accumulated into the overall validator results.
When performing cascade validation, there is some question about the type that should be passed to Validator.Validate<T>. Should it be the declared property type (or declared type of the elements of the collection), or should Simple.Validation use the actual type of the property value (or collection element). By default, Cascade() uses the actual type of the property value or collection element, but you can specify the usage of a base type or interface by using the Cascade<T> overload.
In the Personnel.Sample project EmailAddress inherits ContactInfo. There is an EmailAddressValidator that tests the email address against a regular expression. If an instance of EmailAddress is added to the Employee.ContactInfo collection, should it be validated using the EmailAddressValidator or the ContactInfoValidator? Calling the non-generic Cascade() method will cause it to be validated using EmailAddressValdiator. Calling Cascade<ContactInfo>() will cause it to be validated only by the SaveContactInfoValidator.