Making the Entity Framework Fit Your Domain – Part 2

Writing

Wow, I didn’t realize when I started this series that it would take me this long to get to part 2. Sorry about that guys (and ladies)! If you have forgotten about the first part of this post, then you can go check it out.

In the first part I talked about getting up to the point where I realized that without going IPOCO I would not be able to use the Entity Framework with any sort of approximation of a real application domain. In this post we are going to go over the entity that I have created and talk about the issues that I had along the way.

In the previous post I showed that in order to create an entity that didn’t involve descending from a base class, I would need to implement a class like this:

public class Entity : IEntityWithKey, IEntityWithChangeTracker, IEntityWithRelationships

The first interface “IEntityWithKey” is actually optional, and according to the docs, will decrease performance and increase memory usage. Well, hmmmmm, that makes it sound not so optional anymore, so I went ahead and implemented it. This interface can be implemented entirely within the base entity class:

EntityKey _entityKey;        
EntityKey IEntityWithKey.EntityKey
{
    get
    {
        return _entityKey;
    }
    set
    {
        SetMemberChanging(StructuralObject.EntityKeyPropertyName);
        _entityKey = value;
        SetMemberChanged(StructuralObject.EntityKeyPropertyName);
    }
}

This is a property that the consumer doesn’t really need to worry about, the entity framework uses this property for its own internal purposes.

The second interface is required, and lets your object inform the entity framework when properties have changed. The entity framework obviously wants to do change tracking and requires the implementer to do this for themselves. In case you have never used NHibernate, you will know that it uses transparent proxies in order to do automatic change tracking. Since the entity framework doesn’t implement dynamic proxies and also doesn’t provide any compile time generated proxies, we are forced to implement this ourselves.

My first reaction was to just use DynamicProxy2 in order to generate proxies and do change tracking like that! This is the same library from the Castle Project that NHibernate uses to create its proxies. The issue immediately came up when I realized that the Entity Framework had no way to construct objects from factory methods. In order to create these proxies I would need to setup my entity like this:

protected User()
{            
}

public static User Create()
{
    var proxy = new ProxyGenerator();
    return proxy.CreateClassProxy<User>(new PropertyChangeInterceptor());
}

Not only would the entity framework not be able to construct these proxies, but it even blew up when I constructed one of these classes on my own and tried to pass it as a new entity to the Entity framework. Clearly the entity framework did not like working with runtime generated proxies. The reason for this is that DynamicProxy2 does its magic by creating a subclass of our “User” class at runtime and then passes that class back as a “User” class. So for the code in your application, it looks exactly like a “User” class. This requires that all of your properties and methods be virtual in order to intercept them though. If you look below you will see how the proxy wraps the calls to the underlying type and allows code to be inserted.

 image

But since the Entity Framework doesn’t like this kind of proxy, we are going to have to look elsewhere. So instead of runtime generated proxies, I decided to look into using PostSharp to do some compile time method interception. This is exactly what it sounds like, PostSharp actually modifies your assemblies post-compile and allows you to inject code around methods and field accesses.

What we will first need to do is put methods in our base entity class to report property changes:

public void SetMemberChanging(string member)
{
    if (_changeTracker != null)
    {
        _changeTracker.EntityMemberChanging(member);    
    }            
}

public void SetMemberChanged(string member)
{
    if (_changeTracker != null)
    {
        _changeTracker.EntityMemberChanged(member);    
    }            
}

The “_changeTracker” variable is actually passed into the entity on a method that is implemented by the IEntityWithChange tracker interface. If we were to implement this manually, then the “Username” property would look like this:

[EdmScalarProperty(IsNullable = false)]        
public string Username
{
    get
    {
        return this._Username;
    }
    set
    {                        
        SetMemberChanging("Username");
        this._Username = value;
        SetMemberChanged("Username");
    }
}

And we would have to do this with every single property that we were tracking with the entity framework. Instead with PostSharp we will need to create a class which inherits from an “OnMethodBoundaryAspect”. This aspect allows us to inject code around a method call. I would explain this further, but right now this isn’t a tutorial for PostSharp, I recommend that you go check it out. This aspect that we are going to create will be applied to our entity classes.

public class ChangeTrackingAspectAttribute: OnMethodBoundaryAspect
{
    public override bool CompileTimeValidate(MethodBase method)
    {            
        if (method.Name.StartsWith("set_"))
        {
            return true;
        }            
        return false;
    }

    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
        string propertyName = eventArgs.Method.Name.Substring(4);
        PropertyInfo pi = eventArgs.Instance.GetType().GetProperty(propertyName);
        if (pi.IsDefined(typeof(EdmScalarPropertyAttribute), false))
        {
            var changeTrackingEntity = eventArgs.Instance as Entity;
            if (changeTrackingEntity != null)
            {
                changeTrackingEntity.SetMemberChanging(propertyName);
            }    
        }                        
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        string propertyName = eventArgs.Method.Name.Substring(4);
        PropertyInfo pi = eventArgs.Instance.GetType().GetProperty(propertyName);
        if (pi.IsDefined(typeof(EdmScalarPropertyAttribute), false))
        {
            var changeTrackingEntity = eventArgs.Instance as Entity;
            if (changeTrackingEntity != null)
            {
                changeTrackingEntity.SetMemberChanged(propertyName);
            }
        }            
    }
}

This class looks for methods that start with “set_” and apply this attribute to them. Then at runtime we look for the “EdmScalarAttribute” in order to call the “SetMemberChanging” and “SetMemberChanged” attributes. There could certainly be some more caching and optimizations, but for now this will do. We have implemented some very basic “change tracking” on entities while only placing an attribute on our entity class.

The last interface “IEntityWithRelationships” requires a bit more work to get implemented. It also requires us to dirty up our domain entity a bit more than we have had to so far. In the next entry in this series, I’ll show you how I implemented “IEntityWithRelationships” and then provide the full source for the base entity. You’ll start to see that you can use the Entity Framework within your own domain, but is all of this work worth it? Well that all depends on why you are using the Entity Framework. If you remember, I started this series because I wanted to see if I could fit the Entity Framework into my own domain and be happy with it. So far the results haven’t been too bad, and we’ll see in the next post where this will go. Stay tuned!

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Comments (9)

  1. This is very informative, but you went along this path without explaining how the generated EF model didn’t work for your domain. I’d be interested in knowing more about what you want out of the classes that can’t be done with partial classes and interfaces.

  2. Hi Justin,

    I guess this did surprise me a bit, because you are building what I have already built. Please check out my blog for details and http://www.codeplex.com/efcontrib for the postsharp4ef project.

    Although I haven’t kept it up to date (I joined Microsoft), it is a quite complete implementation. Should be easy to get up and running with the latest EF drops.

    Contact me if you have any questions.

    Thanks,
    rj

  3. I just wanted to point out that the implementation here is starting to smell.

    A big part of wanting to move to POCOs is to move to behavioral objects as opposed to "objects seen as a shape". In order to get this stuff working it seems that we either

    a) need to put private/protected setters around all of our data
    b) throw "tell don’t ask" out the window
    c) manually register everything

    None of these seem like viable alternatives to me.

    If you are treating your objects as buckets of data there are easier ways to handle this (like prefering convention over configuration).

    Cheers,

    Greg

  4. @Ruurd I have seen the other implementations. I was merely trying to show what went into bending the Entity Framework to fit into a real domain.

    @Greg The whole point of this series is to go through and describe the pain involved in getting the Entity Framework setup in a domain. Originally I just wanted to understand what I was up against and I now see this more as an exercise in what needs to be changed in the Entity Framework in order to make it usable from a domain first perspective.

  5. The true way to get "Making the Entity Framework Fit Your Domain" is to just say no and use a mature ORMapper like nhibernate. S#arp makes it possible to no longer need XML files.

  6. Great article! I’m going through a similar process of breaking down what is really needed from the designer to allow EF to work. We are moving from LINQ-2-SQL to EF but we have our own code generation templates which we don’t want to scrap.

    Look forward to reading the rest of your series.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All