Release Notes
- Fixed a bug in TypeInterceptor in which IsTargetEntity() was not being called before passing the handling down the inheritance chain.
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)
At Redacted Associates, we’ve been having a discussion about whether we should use the Generic Repository pattern on top of NHibernate. We have a simple IRepository interface as follows:
For my part, I like don’t like to spend a lot of time worrying about the way my ORM interacts with the database. I prefer to spend my design-energy around how to architect the application such that interacting with the database is a minor implementation detail, almost an afterthought even.
At least one of my co-workers disagrees, and has given a really good argument for a specific case when using direct features in NHibernate saved some work. This discussion has spurred me to ask myself “what are the most important features of an ORM?” and “at what level of abstraction should we work with an ORM?” There’s no way to answer these questions without identifying your purpose in using the ORM to begin with. With that in mind, I decided to categorize the features we all look for in an ORM and compare them to our Generic Repository implementation.
ORM features basically fall into one of 3 categories:
-
Queryability
-
Linq Provider
In .NET, Linq remains the most discoverable way to query an ORM. NHibernate has the QueryOver api, but I find it to be hopelessly unreadable anything but the simplest query.
-
Specification pattern
The easiest specification pattern in .NET relies on Linq. It’s a very nice way to allow api clients to construct their own queries without concerning themselves with database schema details. In an SOA architecture, it provides a flat contract to support complex query results. It minimizes the number of service methods because you don’t have to write GetCustomersByNameAndCityAndProduct.
-
Fully mapped relationships between objects.
I depend on having a fully expressed query model to use for linq queries. Getting the right data to work with often involves a number of database relationships and it’s impossible to predict when some new object or property will be needed to satisfy a query. It’s easiest to ensure that the model fully expresses the database and that all fields and relationships are present in the model. The model should fully and accurately express the database schema.
-
Id navigation properties.
Id navigation properties as a companion to the object relationship properties are really handy. They can reduce query-syntax clutter quite a bit. Employee.ManagerId is cleaner than Employee.Manager.Id. Some ORM’s will pull back the whole Manager to get the Id. I hate that.
-
Full support for all relationship types (one-to-one, one-to-many, many-to-many).
These relationships are standard in relational db’s. Any Object-Relational Mapper should support them.
-
Lazy Loading
-
-
Behavior
-
Cascade mappings.
This is not personally a value to me, but I recognize that in some cases it’s useful.
-
Trigger-behavior.
This sort of behavior is useful when you want the ORM to handle things like audit fields, soft deletes, or log table entries.
-
Sql-Efficiencies.
Sometimes pulling back large datasets and mapping them to in-memory objects can be very expensive. If performance is a concern, it’s nice to be able to have the ORM optimize the operation. NHibernate’s “Merge” operation is a good example of this.
-
-
Testability
-
In-memory testability
-
Mockable/Stubbable
-
I composed the following table listing the features we are seeking from an ORM and how each tool fares against our needs.
NHibernate |
Entity Framework |
Generic Repository |
Micro ORMs |
|
Linq Provider |
Not Fully Implemented | Fully Implemented | Depends on ORM | No |
Specification Pattern |
Easy to implement against partially implemented Linq provider. Hard otherwise. | Easy to implement. | Depends on ORM | No |
Can Fully Map Relationships |
Yes | Yes | Depends on ORM | No |
Id Navigation Properties |
Not without extreme (and not very useful) hacks | Yes | Depends on ORM | Yes |
Full support for relationship types |
One-to-one doesn’t work well. Results in N+1 queries on one side of the relationship | * Haven’t tested this. | Depends on ORM | Sort of |
Lazy Loading |
Yes | Yes | Depends on ORM | No |
Cascade Mappings |
Yes | Yes | Depends on ORM | No |
Trigger Behavior |
Yes | Yes | Depends on ORM | No |
Sql Efficiences |
Yes | Yes | Depends on ORM | No |
In-memory testability |
Yes, provided you use SqlLite. | Yes, provided you use SqlCompact edition—or you can interface your DbContext. | Yes | No |
Mockable-Stubbable |
Mostly. Some of the methods you use on ISession are in fact extension methods. .Query is an extension method which is problematic since that’s an obvious one I’d like to stub. | Mostly. Methods such as .Include() are extension methods with no in-memory counterpart. If I really need to use .Include() I’ll write an integration test instead. | Yes | No |
Notes |
|
|
|
Fantastic for quick and dirty CRUD on single tables. Not so great otherwise. |
Takeaways
My advice is to use Generic Repository except when you need to get close to the metal of your ORM for some fine-grained control over your data access.
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.
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.
Isg.EntityFramework.Interceptors was renamed to Isg.EntityFramework. The interceptors are still present, but I’ve added some work around EntityFramework Configuration.
Changes:
- InterceptorDbContext is obsolete. Please use Isg.EntityFramework.DbContextBase
- DbContextBase adds support for a ModelConfigurationProvider
- The default ModelConfigurationProvider scans the assembly that your DbContextBase subclass lives in and finds all instances of EntityTypeConfiguration<> and loads them.
- I added some extension methods to make specifying a non-identity primary key column and computed column a little more straightforward. The EntityFramework method works fine, but isn’t very discoverable. Now it’s Property(<expression>).IsIdentity(false) and Property(<expression>).Computed().
- Isg.EntityFramework is built against EntityFramework 4.3
Isg.EntityFramework is growing slowly but surely. I would love to hear your ideas about what features you would like to see.
Enjoy!
In my previous post I introduced the Isg.EntityFramework.Interceptors I created. In this post, I will demonstrate the usage of another package that builds on the first: Isg.EntityFramework.Interceptors.SoftDelete
The entire SoftDelete package consists of two classes. The first is an interface:
1: public interface ISoftDelete
2: {
3: bool IsDeleted { get; set; }
4: }
The second is the SoftDeleteChangeInterceptor class.
If your domain class implements ISoftDelete, the SoftDeleteChangeInterceptor will catch the delete operation, set the IsDeleted field to true, and repurpose the operation to an Update.
Here is a unit test that demonstrates this behavior.
1: [Test]
2: public void Delete()
3: {
4: var name = Guid.NewGuid().ToString();
5:
6: using (var db = new CustomerDbContext())
7: {
8: var customer = new Customer {IsDeleted = false, Name = name};
9: db.Customers.Add(customer);
10: db.SaveChanges();
11: }
12:
13: using (var db = new CustomerDbContext())
14: {
15: var customer = db.Customers.SingleOrDefault(i => i.Name == name);
16: db.Customers.Remove(customer);
17: db.SaveChanges();
18: }
19:
20: using (var db = new CustomerDbContext())
21: {
22: var customer = db.Customers.SingleOrDefault(i => i.Name == name);
23: Assert.That(customer, Is.Not.Null);
24: Assert.That(customer.IsDeleted, Is.True);
25: }
26: }
Happy Coding!
I’ve recently published two NuGet packages that may be of interest to Entity Framework Code First users. The first is Isg.EntityFramework.Interceptors which provides an interface for intercepting operations during a call to DbContext.SaveChanges() and taking some action before or after. The 2nd package is Isg.EntityFramework.Interceptors.SoftDelete which provides a concrete implementation of an interceptor.
Using the interceptors library involves three steps.
1) Implement an interceptor.
2) Register your interceptors.
3) Subclass InterceptorDbContext
Implementing an Interceptor
Implementing an interceptor is very easy. The IInterceptor has two methods—Before and After—each of which refer to the SaveChanges() method of DbContext.
1: public interface IInterceptor
2: {
3: void Before(InterceptionContext context);
4: void After(InterceptionContext context);
5: }
The InterceptionContext consists of information your interceptor may need. You get the InterceptorDbContext that is in the process of being saved, the ObjectContext, ObjectStateManager, DbChangeTracker, and list of DbEntityEntry objects that are participating in the operation.
A TypeInterceptor implementation is provided that filters for entities of a certain type. A ChangeInterceptor implementation is provided that filters entities being Added, Modified, or Deleted. The ChangeInterceptor provides Before and After methods that can be overridden for each of those operations.
Register Your Interceptors
Interceptor registration is easy. In the case of the SoftDeleteInterceptor, registration looks like this:
1: InterceptorProvider.SetInterceptorProvider(
2: new DefaultInterceptorProvider(
3: new SoftDeleteChangeInterceptor()));
InterceptorProvider is a global cache of Interceptor instances that relies on a provider to resolve the instances. In this case, I am manually providing the SoftDeleteInterceptor, but you can plug in a dependency injection framework or any other source of providers that you prefer. If you do this at application startup, any instance of InterceptorDbContext will automatically pull in the Interceptors and apply them during the SaveChanges operation.
Subclass InterceptorDbContext
This one really is as easy as it sounds. Instead of inheriting directly from DbContext, inherit from InterceptorDbContext. For example:
1: public class CustomerDbContext : InterceptorDbContext
2: {
3: public DbSet<Invoice> Invoices { get; set; }
4: public DbSet<Customer> Customers { get; set; }
5:
6: protected override void OnModelCreating(DbModelBuilder modelBuilder)
7: {
8: base.OnModelCreating(modelBuilder);
9:
10: modelBuilder.Entity<Invoice>()
11: .HasKey(i => i.InvoiceId)
12: ;
13: modelBuilder.Entity<Invoice>()
14: .HasRequired(invoice => invoice.Customer)
15: .WithMany(customer => customer.Invoices)
16: .HasForeignKey(i => i.CustomerId)
17: ;
18:
19: modelBuilder.Entity<Customer>()
20: .HasKey(customer => customer.CustomerId)
21: ;
22: }
23: }
InterceptorDbContext will build the InterceptionContext object and pass it to your interceptors Before and After the SaveChanges method is called.
Your Input Matters
The current version of the libraries are 0.2.1. If there are other commonly used interceptor implementations that should be implemented I’ll be happy to create packages for those. If you have other feature requests for the package I’d love to hear them.
Interceptors are a great way to handle some repetitive and predictable data management tasks. NHibernate has good support for Interceptors both at the change and query levels. I wondered how hard it would be to write interceptors for the new EF4 CTP and was surprised at how easy it actually was… well, for the change interceptors anyway. It looks like query interceptors would require a complete reimplementation of the Linq provider—not something I feel like undertaking right now.
On to the Code!
This is the first interface we’ll use to create a class that can respond to changes in the EF4 data context.
- namespace Yodelay.Data.Entity
- {
- /// <summary>
- /// Interface to support taking some action in response
- /// to some activity taking place on an ObjectStateEntry item.
- /// </summary>
- public interface IInterceptor
- {
- void Before(ObjectStateEntry item);
- void After(ObjectStateEntry item);
- }
- }
We’ll also use this interface to add some conditional execution support.
- namespace Yodelay.Data.Entity
- {
- /// <summary>
- /// Adds conditional execution to an IInterceptor.
- /// </summary>
- public interface IConditionalInterceptor : IInterceptor
- {
- bool IsTargetEntity(ObjectStateEntry item);
- }
- }
The first interceptor I want to write is one that manages four audit columns automatically. First I need an interface that provides the audit columns:
- public interface IAuditEntity
- {
- DateTime InsertDateTime { get; set; }
- DateTime UpdateDateTime { get; set; }
- string InsertUser { get; set; }
- string UpdateUser { get; set; }
- }
The EF4 DbContext class provides an override for SaveChanges() that I can use to start handling the events. I decided to subclass DbContext and add the interception capability to the new class. I snipped the constructors for brevity, but all of the constructors from the base class are bubbled.
- public class DataContext : DbContext
- {
- private readonly List<IInterceptor> _interceptors = new List<IInterceptor>();
- public List<IInterceptor> Interceptors
- {
- get { return this._interceptors; }
- }
- private void InterceptBefore(ObjectStateEntry item)
- {
- this.Interceptors.ForEach(intercept => intercept.Before(item));
- }
- private void InterceptAfter(ObjectStateEntry item)
- {
- this.Interceptors.ForEach(intercept => intercept.After(item));
- }
- public override int SaveChanges()
- {
- const EntityState entitiesToTrack = EntityState.Added |
- EntityState.Modified |
- EntityState.Deleted;
- var elementsToSave =
- this.ObjectContext
- .ObjectStateManager
- .GetObjectStateEntries(entitiesToTrack)
- .ToList();
- elementsToSave.ForEach(InterceptBefore);
- var result = base.SaveChanges();
- elementsToSave.ForEach(InterceptAfter);
- return result;
- }
I only want the AuditChangeInterceptor to fire if the object implements the IAuditEntity interface. I could have directly implemented IConditionalInterceptor, but I decided to extract the object-type criteria into a super-class.
- public abstract class TypeInterceptor : IConditionalInterceptor
- {
- private readonly System.Type _targetType;
- public Type TargetType { get { return _targetType; }}
- protected TypeInterceptor(System.Type targetType)
- {
- this._targetType = targetType;
- }
- public virtual bool IsTargetEntity(ObjectStateEntry item)
- {
- return item.State != EntityState.Detached &&
- this.TargetType.IsInstanceOfType(item.Entity);
- }
- public void Before(ObjectStateEntry item)
- {
- if (this.IsTargetEntity(item))
- this.OnBefore(item);
- }
- protected abstract void OnBefore(ObjectStateEntry item);
- public void After(ObjectStateEntry item)
- {
- if (this.IsTargetEntity(item))
- this.OnAfter(item);
- }
- protected abstract void OnAfter(ObjectStateEntry item);
- }
I also decided that the super-class should provide obvious method-overrides for BeforeInsert, AfterInsert, BeforeUpdate, etc.. For that I created a generic class that sub-classes TypeInterceptor and provides friendlier methods to work with.
- public class ChangeInterceptor<T> : TypeInterceptor
- {
- #region Overrides of Interceptor
- protected override void OnBefore(ObjectStateEntry item)
- {
- T tItem = (T) item.Entity;
- switch(item.State)
- {
- case EntityState.Added:
- this.OnBeforeInsert(item.ObjectStateManager, tItem);
- break;
- case EntityState.Deleted:
- this.OnBeforeDelete(item.ObjectStateManager, tItem);
- break;
- case EntityState.Modified:
- this.OnBeforeUpdate(item.ObjectStateManager, tItem);
- break;
- }
- }
- protected override void OnAfter(ObjectStateEntry item)
- {
- T tItem = (T)item.Entity;
- switch (item.State)
- {
- case EntityState.Added:
- this.OnAfterInsert(item.ObjectStateManager, tItem);
- break;
- case EntityState.Deleted:
- this.OnAfterDelete(item.ObjectStateManager, tItem);
- break;
- case EntityState.Modified:
- this.OnAfterUpdate(item.ObjectStateManager, tItem);
- break;
- }
- }
- #endregion
- public virtual void OnBeforeInsert(ObjectStateManager manager, T item)
- {
- return;
- }
- public virtual void OnAfterInsert(ObjectStateManager manager, T item)
- {
- return;
- }
- public virtual void OnBeforeUpdate(ObjectStateManager manager, T item)
- {
- return;
- }
- public virtual void OnAfterUpdate(ObjectStateManager manager, T item)
- {
- return;
- }
- public virtual void OnBeforeDelete(ObjectStateManager manager, T item)
- {
- return;
- }
- public virtual void OnAfterDelete(ObjectStateManager manager, T item)
- {
- return;
- }
- public ChangeInterceptor() : base(typeof(T))
- {
- }
- }
Finally, I created subclassed ChangeInterceptor<IAuditEntity>.
- public class AuditChangeInterceptor : ChangeInterceptor<IAuditEntity>
- {
- public override void OnBeforeInsert(ObjectStateManager manager, IAuditEntity item)
- {
- base.OnBeforeInsert(manager, item);
- item.InsertDateTime = DateTime.Now;
- item.InsertUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
- item.UpdateDateTime = DateTime.Now;
- item.UpdateUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
- }
- public override void OnBeforeUpdate(ObjectStateManager manager, IAuditEntity item)
- {
- base.OnBeforeUpdate(manager, item);
- item.UpdateDateTime = DateTime.Now;
- item.UpdateUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
- }
- }
I plugged this into my app, and it worked on the first go.
Another common scenario I encounter is “soft-deletes.” A “soft-delete” is a virtual record deletion that does not actual remove the record from the database. Instead it sets an IsDeleted flag on the record, and the record is then excluded from other queries. The problem with soft-deletes is that developers and report writers always have to remember to add the “IsDeleted == false” criteria to every query in the system that touches the affected records. It would be great to replace the standard delete functionality with a soft-delete, and to modify the IQueryable to return only records for which “IsDeleted == false.” Unfortunately, I was unable to find a clean way to add query-interceptors to the data model to keep deleted records from being returned. However, I was able to get the basic soft-delete ChangeInterceptor to work. Here is that code.
- public interface ISoftDelete
- {
- bool IsDeleted { get; set; }
- }
- public class SoftDeleteChangeInterceptor : ChangeInterceptor<ISoftDelete>
- {
- public override void OnBeforeInsert(ObjectStateManager manager, ISoftDelete item)
- {
- base.OnBeforeInsert(manager, item);
- item.IsDeleted = false;
- }
- public override void OnBeforeDelete(ObjectStateManager manager, ISoftDelete item)
- {
- if (item.IsDeleted)
- throw new InvalidOperationException("Item is already deleted.");
- base.OnBeforeDelete(manager, item);
- item.IsDeleted = true;
- manager.ChangeObjectState(item, EntityState.Modified);
- }
- }
Here’s the complete diagram of the code:
EF4 has come a long way with respect to supporting extensibility. It still needs query-interceptors to be feature-parable with other ORM tools such as NHibernate, but I suspect that it is just a matter of time before the MS developers get around to adding that functionality. For now, you can use the interceptor model I’ve demo’ed here to add functionality to your data models. Perhaps you could use them to add logging, validation, or security checks to your models. What can you come up with?