The ObjectContext of Your Desire, Part The First

In Which We Take Care of Business with the Entity Framework

There are few phases of application development that I want to have done with faster than running plumbing from the database. Some of us, bless the cockles of their tiny hearts, live in ORM-land and love it. As for me, I’ll take the path of least resistance every time with regard to this drudgery, because I have features to code and a user interface to build. And at the moment, the path of least resistance means using the ADO.NET Entity Framework.

The Entity Framework does a bang-up job of creating a usable data layer in a matter of seconds, once directed toward a database. But though I was no longer spending hours hacking out properties and mapping them to DataRows, it dawned on me that I was not sure where my business logic was suppose to live, now that my data access was practically codeless.

This is one task for which we are not handed a silver platter containing a wizard composed entirely of Next buttons. And you and I both know that if this whole Entity Framework thing is to catch on at all, we’ll need a clean and reusable solution to this dilemma.

I have one here.

I, Entity

The individual entity types generated for a standard Entity Data Model have plenty of hooks for when specific properties have changed, but none for when the entity itself is being saved or deleted, which is often the best place for our custom logic to set up camp. We can bridge this gap by creating a dead simple interface that any interested types could implement:

public interface IEntity
{
    void OnSaving();
    void OnDeleting();
}

Out of Context

Now, the entities themselves may not give any notice when they are saved, but the ObjectContext, who does the actual saving, certainly does. Ergo, our next step is to create a partial class for the generated ObjectContext (in this case, built using our friend the AdventureWorks database), and handle its SavingChanges event thusly:

public partial class AdventureWorksEntities
{
    private static void context_SavingChanges(object sender, EventArgs e)
    {
        // Get the list of changes we are interested in
        var changes = (sender as ObjectContext).ObjectStateManager.GetObjectStateEntries(
            EntityState.Added | EntityState.Modified | EntityState.Deleted);

        // for each change involving an IEntity,
        foreach (ObjectStateEntry change in changes)
        {
            if (!change.IsRelationship && change.Entity is IEntity)
            {
                // trigger custom logic as appropriate
                switch (change.State)
                {
                    case EntityState.Added:
                    case EntityState.Modified:
                        (change.Entity as IEntity).OnSaving();
                        break;
                    case EntityState.Deleted:
                        (change.Entity as IEntity).OnDeleting();
                        break;
                }
            }
        }
    }

    partial void OnContextCreated()
    {
        // Register the handler for the SavingChanges event.
        this.SavingChanges
            += new EventHandler(context_SavingChanges);
    }
}

For those of you who may have seen this MSDN article, that code should appear familiar–I used it as a starting point. The key difference between that code and this listing is that we have used an interface to loosely connect the logic code to the context, rather than having embedded our custom rules directly in the ObjectContext, as might a barbarian or a physicist.

To clarify, I don’t mean to slight physicists, or even barbarians; but while we as developers don’t necessarily excel at tearing someone’s arm off and beating them with it, or at edging closer to drafting the Theory of Everything, we are likely to know that business logic probably ought not to live down in the ObjectContext.

Anyway

Here’s how to apply what we’ve done to add some custom logic to the AdventureWorks Employee entity:

partial class Employee : IEntity
{
    #region IEntity Members

    public void OnSaving()
    {
        // make sure the employee has a LoginID
        if (string.IsNullOrEmpty(this.LoginID))
            throw new ArgumentException("You must give the employee a LoginID.");

        // if this is a new employee,
        if (this.EmployeeID == 0)    
        {
            // make sure they don't already have a record
            if (AlreadyExists(this.LoginID))
                throw new ArgumentException("This employee already exists.");
        }
    }

    public void OnDeleting()
    {
        throw new InvalidOperationException(
            "Employee records may not be deleted.  Please set the Current Flag instead.");
    }
    
    #endregion
}

And here’s what using this newly-adorned Employee entity might look like:

var awc = new AdventureWorksEntities();
var e = (from p in awc.Employees
where p.EmployeeID == eid
select p);

// a clear abuse of my power as 
// example-writer, to be sure.
// but trust me, this guy deserves it.
e.LoginID = string.Empty;
 
awc.SaveChanges(); // throws ArgumentException ("You must give the employee a LoginID.") 

// curses!  foiled again.

Coming Up! Part The Second, in which we painlessly scope the ObjectContext‘s lifespan.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s