Software Development & Web Development

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.

  • Facebook
  • Twitter
  • DZone It!
  • Digg It!
  • StumbleUpon
  • Technorati
  • Del.icio.us
  • NewsVine
  • Reddit
  • Blinklist
  • Add diigo bookmark
  • client quotes
  • Physicians's Health Plan
    We have always been impressed with the level of technical expertise provided by Aptera. In fact, when we needed additional on-site resources, we called you first.
  • Steel Dynamics
    Aptera's performance while working with SDI has been outstanding! I was most impressed with your professionalism and quality of work.
  • Messenger Corporation
    The application you created for Messenger Corporation is now being used by over 1,500 of our clients across the United States and Canada and has had a very positive impact on our business.
  • STAR Financial Bank
    We asked for your best and we got it! Our .NET projects are a roaring success because of your dedication to helping us make this quantum leap. Thanks again!