Making the Entity Framework Fit Your Domain – Part 1

Writing

I’m assuming that like myself, many of you out there work for companies that base much of their IT infrastructure (or at least software development tools) around Microsoft products. So, when a new tool like the Entity Framework comes out, even if you are not a fan, you still need to have a solid knowledge of it because you are going to have to use it at some point. At this point most of my ORM experiences have been with NHibernate, but I still feel the need to explore the Entity Framework to see if I can make it palatable for me to use. I say “palatable” because of the fact that the Entity Framework is designed almost entirely around database first design, which is not the way that I like to design my applications.

My goal with this post is not to trash talk the entity framework, but instead to take it as far as I can toward a usable solution that I would be okay with putting into a production application. This post is going to be written as I explore, so please let me know if you see anything that is wrong or missing.

Let’s first talk about the domain that we are getting ready to look at. It is going to be a very simple domain, because otherwise it would just overwhelm the blog post by introducing too much complexity. I do want to have enough entities though so that you can see where each technology differs. What we are going to do is start off with a scenario that everyone is familiar with… a user with groups and roles. The user will also have a list of addresses associated with it.

Database Schema

We have 4 main tables along with two join tables. I would explain this schema to you, but if you don’t get the schema then this article might be confusing anyway so I’m not going to waste everybody else’s time with it. Basically we have already started designing this application in a way that would bother most people who are using DDD. Normally I wouldn’t start with the database, but since the Entity Framework essentially forces you into starting with the database first, we are going to take this approach. The reason that I say that the Entity Framework forces you into database first design is because the primary method of generating an EF model is to generate it off the database. And then later on as you make changes, you can then update your model to reflect those changes.

At this point in the process the Entity Framework allows us to get up and running very quickly. We simply add a new Entity Data Model:

image

Then we get a wizard that lets us connect the model to our database and generate our entities. So, in just a few seconds we are looking at this:

Entity Data Model

Kinda cool actually. It knows about our join tables and generates many to many relationships automatically. It doesn’t however know about join tables with payloads, but then again there is ambiguity about how we might want that sort of data modeled in our app. So now we have our entities in our Entity Data Model, but where are we really at this point? Well, we are actually already at the point where we can create new entities and save them off to the database.

var user = new User();
user.Username = "TestUser";
user.EmailAddress = "test@test.com";

var address1 = new Address();
address1.Street = "111 Test Street";
address1.City = "Test City";
address1.State = "Virginia";
address1.PostalCode = "22055";

var address2 = new Address();
address2.Street = "222 Test Street";
address2.City = "Test City";
address2.State = "Virginia";
address2.PostalCode = "23000";

user.Addresses.Add(address1);
user.Addresses.Add(address2);

var entities = new TestEFAppEntities();
entities.AddToUserSet(user);
entities.SaveChanges(true);

That was painless, wasn’t it? But where did the “User” and “Address” classes come from? I don’t remember creating any classes… But that is because we didn’t. The Entity framework spit out all of these classes into a file that is hidden under our Entity Model called “Domain.Designer.cs”. Here is the user class that was generated for the model (I removed all the comments so that it would only be kinda huge) 😉

[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="TestEFAppModel", Name="User")]
[global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]
[global::System.Serializable()]
public partial class User : global::System.Data.Objects.DataClasses.EntityObject
{
    public static User CreateUser(int id, string username, string emailAddress)
    {
        User user = new User();
        user.Id = id;
        user.Username = username;
        user.EmailAddress = emailAddress;
        return user;
    }

    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public int Id
    {
        get
        {
            return this._Id;
        }
        set
        {
            this.OnIdChanging(value);
            this.ReportPropertyChanging("Id");
            this._Id = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
            this.ReportPropertyChanged("Id");
            this.OnIdChanged();
        }
    }
    private int _Id;
    partial void OnIdChanging(int value);
    partial void OnIdChanged();

    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public string Username
    {
        get
        {
            return this._Username;
        }
        set
        {
            this.OnUsernameChanging(value);
            this.ReportPropertyChanging("Username");
            this._Username = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
            this.ReportPropertyChanged("Username");
            this.OnUsernameChanged();
        }
    }
    private string _Username;
    partial void OnUsernameChanging(string value);
    partial void OnUsernameChanged();

    [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public string EmailAddress
    {
        get
        {
            return this._EmailAddress;
        }
        set
        {
            this.OnEmailAddressChanging(value);
            this.ReportPropertyChanging("EmailAddress");
            this._EmailAddress = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
            this.ReportPropertyChanged("EmailAddress");
            this.OnEmailAddressChanged();
        }
    }
    private string _EmailAddress;
    partial void OnEmailAddressChanging(string value);
    partial void OnEmailAddressChanged();

    [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("TestEFAppModel", "FK_Addresses_Users", "Addresses")]
    [global::System.Xml.Serialization.XmlIgnoreAttribute()]
    [global::System.Xml.Serialization.SoapIgnoreAttribute()]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public global::System.Data.Objects.DataClasses.EntityCollection<Address> Addresses
    {
        get
        {
            return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection<Address>("TestEFAppModel.FK_Addresses_Users", "Addresses");
        }
        set
        {
            if ((value != null))
            {
                ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection<Address>("TestEFAppModel.FK_Addresses_Users", "Addresses", value);
            }
        }
    }

    [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("TestEFAppModel", "UserXGroups", "Groups")]
    [global::System.Xml.Serialization.XmlIgnoreAttribute()]
    [global::System.Xml.Serialization.SoapIgnoreAttribute()]
    [global::System.Runtime.Serialization.DataMemberAttribute()]
    public global::System.Data.Objects.DataClasses.EntityCollection<Group> Groups
    {
        get
        {
            return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection<Group>("TestEFAppModel.UserXGroups", "Groups");
        }
        set
        {
            if ((value != null))
            {
                ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection<Group>("TestEFAppModel.UserXGroups", "Groups", value);
            }
        }
    }
}

Hmmmm…. so where is my domain object in there? In fact, all of the domain objects are generated into a single file like this. You get to extend your domain objects by using partial classes. The partial classes that we implement allow us to take advantage of some partial methods that are in the generated classes. As you can see from this code in one of the above setters:

set
{
    this.OnUsernameChanging(value);
    this.ReportPropertyChanging("Username");
    this._Username = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
    this.ReportPropertyChanged("Username");
    this.OnUsernameChanged();
}

We have two partial methods “OnUsernameChanging” and “OnUsernameChanged” along with two events “ReportPropertyChanging” and “ReportPropertyChanged”. So when we create our partial classes we can tap into these methods. This way if we wanted to intercept the setting of our Username property we could implement a partial class like this (if you haven’t ever used partial classes, then go here):

public partial class User
{
    partial void OnUsernameChanging(string value)
    {            
        // check value and do something here
    }
}

But what if we want to do something when the property is retrieved? We don’t really have a lot of options, the getter in the user partial class looks like this:

get
{
    return this._Username;
}

Hmmm. So I guess they don’t want us to hook into the getter! Having user code hooked into the getter might have caused issues at some infrastructure level for them, so I’m going to give them the benefit of the doubt! But not too much. At first I thought that maybe I could go to the EDM (Entity Data Model) and make the getter and setter on the property as private or protected. Then rename the property to something like “UsernameInternal”. Then I could introduce a completely new property in the partial class to expose this property:

public string Username
{
    get
    {
        return this.UsernameInternal;
    }
    set
    {
        this.UsernameInternal = value;
    }
}

The only problem is that because we have wrapped the EDM generated property, we can no longer query against it! How would I write this query?

var userQuery = from u in entities.UserSet 
    where u.Username == "TestUser" 
    select u;

Sure we can put “Username” in the query because we have exposed our own property, but since this property is not in the EDM, the Entity Framework has no knowledge of this property so we can’t query against it! Dang. And to think I was going to try and use this same technique to implement some lazy loading. Hmmmmmmm. So if we want to be able to query against any property then we are going to have to directly expose the property that has the “EdmScalarPropertyAttribute” on it. This is the attribute that signifies to the Entity Framework that this property is a mapped scalar property.

This property has to share the name of the property tag in our CSDL file. Sadly, in order to get any control over my classes, it looks like I am going to have to ditch the Entity Designer altogether. Which isn’t necessarily a bad thing considering that all of the xml and class files are generated into only two files which creates an interesting situation for teams that are editing these files independently. Definitely a merge nightmare. What blows my mind is that they also actually store the metadata for the EDM diagram right in with the mapping xml.

Before you start thinking that I ditched the Entity Designer too soon, another issue here is that all of the generated entities descend from a base EntityObject class that keeps us from forming our own object heirarchy. And we can’t edit any of the generated classes or else all of our changes will be overridden whenever we make a change to it. Another issue is that we don’t really have any control over our object lifetime. The Entity Designer spits out default public constructors and a default factory method containing parameters for all properties of the object. Why? I don’t understand why they just didn’t leave off the constructor and factory method and let me define them in my partial classes. I can’t remove them from the generated classes, but I could easily add them if they weren’t there. Geeeez.

Alright, so I mentioned that we need to ditch the EDM designer, so what do we use now? Thankfully Microsoft provided us a tool called EdmGen.exe. This is a command-line tool that we can use to generate the mapping files for our database:

 image

EdmGen actually spits out different files for everything. And when I say “everything” I mean only the different file types. So we get these files:

image

What we are going to do here is ditch the ObjectLayer.cs and Views.cs files and create our own Entities. Hopefully we can isolate most of the EF specific code into a base entity class. But since we are going to remove generated entities we are going to have to keep the behavior that the previously generated classes had. This is where that IPOCO stuff comes in. IPOCO allows you to implement a few interfaces (in our case three) instead of using the base EntityObject class. Our base entity class’ definition will end up looking like this:

public class Entity : IEntityWithKey, IEntityWithChangeTracker, IEntityWithRelationships

These are the three interfaces that we must implement in order to use this with our EDM. The first just gives the entity framework something to identify your entities by, the second provides a change tracking mechanism, and the third provides a way for your entity to hold its relationships. These are fairly self explanatory, and luckily we can isolate most of the behavior for these into our base class. So our goal is to have business entities that lack Entity Framework specific details, and which expose no Entity Framework specific types. This post is getting  a bit long, so I am going to leave it off right there for now…

In future parts of this series we will break down the base entity class that I have created and take a look at the different entities and how we can create them. We will also take a look at creating an ObjectContext and show you how we can use and query these entities just like we could if we created them through the EDM designer. I will also provide you with the full source to the project that I am using in this series, so stay tuned!

More Insights

View All