Application Architecture Design Using Microsoft's Entity Framework

Use ADO.NET Entity Framework's ObjectContext as the basis for a database-access app

Brian Mains

February 16, 2012

14 Min Read
ITPro Today logo in a gray background | ITPro Today

Any time a new technology comes on the market, most developers go through the same thought process: Will this technology improve my coding productivityand the applications I develop? We're all used to examining new technologies to determine whether products like Microsoft's Entity Framework providetangible benefits for developers so that they enhance applications while requiring as little additional work as possible. The challenges come when adeveloper has to incorporate a new technology into an application, developing an architecture to achieve the following key features:

  • Easy to consume: Although the architecture may be more complicated because of the new technology, using it shouldn't be.

  • Reduced coding: The architecture should not require developers to write a lot more code than normal.

  • Abstraction: Consumers of the architecture shouldn't know it's working with Entity Framework-specific components. Loosely coupling componentstogether as much as possible is also a plus.

  • Dependency injection: The process of creating a data-access component should use advanced techniques such as the Service Locator pattern anddependency injection.

  • Test support: As agile development processes and test-driven development with coded unit tests become more commonplace, embedding theObjectContext directly in an application has less value for this methodology than for others. It should be easy to write test fakes and test within theapplication.

In this article, I'll discuss points to guide you in using Microsoft's ADO.NET Entity Framework to develop an architecture for an application, startingoff with a few preliminaries about working with Entity Framework objects.

Entity Framework Overview

To understand how you can use Entity Framework to develop your architecture, we first must understand some of the objects that Entity Framework usesand how we can extract these objects out of the framework. To begin, Entity Framework employs the ObjectContext as the workhorse for the application.It handles the tasks of state tracking, database reading/manipulation, and much more. Within an ObjectContext is the ObjectSet, a component thatfocuses on the management of a single logical entity. LINQ queries executed against an ObjectSet are represented by the ObjectQuery class, and theObjectResult class represents the results from a stored procedure execution.

When setting up an Entity Framework model, the designer generates a new class that inherits from ObjectContext. For each entity defined in the model,this custom ObjectContext contains a property of type ObjectSet for each of the logical entities as well as a custom class that inherits fromEntityObject. Any stored procedures are mapped to an equivalent function made available through the ObjectContext. This function returns anObjectResult with the result of the stored procedure, either a scalar value, one of the existing entities, or a new complex type representing theresults of the stored procedure.

The IQueryable interface represents the result of an actual query against the database, but this interface is misleading. It makes you think that theactual query has been executed against the database, when it may not have been. Behind the scenes, the ObjectQuery class represents a query createdagainst the ObjectContext. This class does not execute against the database and become a collection of objects until it is iterated through, or theToList method is called. This means the IQueryable resulting query may or may not be executed yet.

Although this seems a minor point, it has important consequences. If the query hasn't been executed against the database, any LINQ extension method orsecondary LINQ query will modify the original query result. This can have beneficial or undesirable consequences. It can be beneficial by allowing youto only add the query parameters that you actually need, instead of inner conditional statements to flesh out the parameters. However, difficulties canarise during application maintenance when you're trying to figure out why that additional query condition is causing the query to perform poorly orreturn incorrect results. There is no direct way to determine the difference between the two issues at design time, except for examining the code andchecking for any foreach statements or ToList() calls.

Entity Framework Building Blocks

When working with an ObjectContext, there are a few techniques that we'll use to make it easier to get data from the API. I refer to these techniquesas "building blocks" because they let us refer to objects in a generic fashion.

We'll start with the technique shown in Figure 1, which illustrates how we can create an ObjectSet on the fly. The CreateObjectSet method generates anew ObjectSet class for a given entity, which is actually what is used in the designer-generated context created for you.

Figure 1: Creating an ObjectSet

var ctx = new SamplesObjectContext();
var sets = ctx.CreateObjectSet();

Unfortunately, a non-generic implementation of this method does not yet exist. However, with some additional work and a whole lot of reflection, theObjectContext can be made to dynamically refer to an object by type, without explicitly specifying the type as a generic, as shown in Figure 2.

Figure 2: Using reflection to create the ObjectSet

var ctx = new SamplesObjectContext();
var creationMethod = ctx.GetType().GetMethod("CreateObjectSet", new Type[] { });
var os = creationMethod.MakeGenericMethod(typeof(User)).Invoke(ctx, new object[] { });

This is important to understand because application frameworks, at times, need to use reflection to simplify the code and avoid having to writerepetitive code, per the Don't Repeat Yourself (DRY) principle. You may not understand why now, but as you navigate the sample code, you'll see severalexamples that show why reflection is needed. Although I hard-code the User type that's in Figure 2, you can easily replace this value with a Typevariable in your own code (you can also do so for the hard-coded value in Figure 3).

Figure 3: Querying an ObjectSet using expressions

var ctx = ObjectContextCreator.Create();
            var os = ctx.Users;

            var param = Expression.Parameter(typeof(User), "u");
            var right = Expression.Constant(3);
            var left = Expression.Property(param, typeof(User).GetProperty("UserID"));

          var query = Expression.Lambda(BinaryExpression.Equal(left, right), param);
            var call = Expression.Call(typeof(Queryable), "Where", new Type[] { typeof(User) }, os.AsQueryable().Expression, query);

            return View(new { Data = os.AsQueryable().Provider.CreateQuery(call) });


Additionally, at times you may find that you need to perform a query dynamically against the ObjectSet. This can happen in cases when an object issought by its primary key. When you dynamically assemble a lambda expression, the Where clause -- or any other extension -- can be invoked dynamicallyon an ObjectSet. The following statement is the equivalent of getting the lambda expression, which is illustrated in Figure 3:

ctx.Users.Where(u => u.UserID == 3)

 You may not always use these methodologies in your architecture, but at times these tricks come in handy and avoid a lot of manual, repetitive code.

Unit of Work Pattern

According to Martin Fowler, the Unit of Work pattern "maintains a list of objects affected by a business transaction and coordinates the writing out ofchanges and the resolution of concurrency problems." Simply put, the Unit of Work pattern uses a container to manage the state of a collection ofobjects; tracks the changes made to each object, writing these changes to an external store like a database; and resolves problems with concurrency inthe database.

Think of a unit of work as a warehouse. Packages within the warehouse are tracked. As soon as a change is made (a package is moved to a new location orshipped), the change is noted in the system and the new state of the package is made available to the respective parties (e.g., shipper, buyer, truckdriver). New packages are not in the system right away, but as soon as they are registered, they, too, are tracked. Defective or damaged packages arediscarded and removed from the system.

This is essentially what the ObjectContext is: It implements the Unit of Work pattern and manages the additions, deletions, and changes of objectsthrough a change set and pushes or pulls them to and from the database server. It's also a feature we want to provide in our application, somethingthat Entity Framework will apply natively.

Since the Unit of Work pattern has been implemented, the framework I'll present here already provides a layer of components on top of theObjectContext, to create an abstraction that keeps Entity Framework entirely within the black box. Figure 4 shows the core interface, which will giveyou an idea of what kind of interface our component will implement.

Figure 4: Unit of work interface

public interface IEntityUnitOfWork : IUnitOfWork where TEntity : class
{
   TEntity CreateNew();
   TEntity Get(object identifier);
   void QueueDelete(TEntity entity);
   void QueueInsert(TEntity entity);
   void QueueUpdate(TEntity entity);
}

 

The goal of this interface is to expose a core set of services to the application, without having to touch the ObjectContext or ObjectSet directly. Theunit of work that implements this interface takes the ObjectSet via the constructor and wraps it with a commonly used set of interfaces, as shown inFigure 5.

By wrapping the ObjectSet, we now have a unit of work class that serves many purposes:

  • It doesn't expose Entity Framework directly.

  • It implements an interface, making the component more easily unit tested.

  • It provides a common utility point; the "queuing" methods can easily perform auditing or validation, or serve any other purpose required.

  • It provides a common control point; the application can perform any restriction it needs to when the data is read, created, updated, or deleted.

In regard to reading data, there are many ways to accomplish this task. We could expose the ObjectSet or ObjectContext directly, we could create onecommon method like the previously discussed repository approach, or we could use some reference to a query provider to provide those features for us.I've chosen the latter option for the sake of this article, where the provider is internal to the repository. The example in Figure 6 demonstrates howthis works.

Figure 6: Sample repository for querying

public class RoleRepository : BaseQueryableRepository, IRoleRepository
{
    public IQueryable GetAll()
    {
        return this.QueryProvider.Query(os => from r in os
                                                orderby r.RoleName
                                                select r);
    }
}


The query provider interface is basically added to the unit of work; the QueryProvider property is essentially a back-handed reference to the unit ofwork by a given interface. Using a separate interface contract for querying allows a few features: The contract can be shifted around, or multiplecontracts can be implemented at one given time, depending on your needs. At any rate, the unit of work chose the IQueryableGenericExtender interface toadd some generic-specific Query methods for extracting data, as shown in Figure 7.

Note that these methods, in debug mode, could use the special ToTraceString() method on the ObjectQuery object as a means of auditing all databasequeries that are executed against the database. Also note that the lambda expression passes in the object set internally and also passes theObjectContext in as well as a specific type of unit of work (for the overload). Lambda expressions are handy because you can control the execution, oryou could even force execution of the query by calling ToList().

Data Access

There are a variety of patterns that can be used for accessing data from the database. One of the more widely used patterns is the repository pattern.This is one of the more generally accepted patterns for data access, and the one I'll use in this article. Its simplicity and efficiency make it verypowerful. You can find myriad examples of implementations via an Internet search. The kinds of implementations I'll show here are, of course, not theonly way to surmount the technical hurdle for your business.

The first type of repository pattern offers a free-form data-access capability. The example in Figure 8 provides some automation of common tasks andgives the developer free range over the type of querying a consumer may need.

Ignoring the Entity Framework features for now, the power of this implementation lies in its simplicity. From a read perspective, the developer simplycrafts his or her own LINQ query or chain of extension methods. The downside of allowing this can be the difficulties of standardization pertaining toquery logic. If the method of determining the active state of a database record changes because of changing business needs, implementing those businesschanges means changing each and every LINQ query statement created to meet that need, which could result in a lot of additional work. Additionally, thequery executed then resides in the consumer (e.g., an ASP code-behind class, MVC controller), not necessarily in the repository class itself, which canmake tracking down those changes a lot harder.

Rather than giving the consumer the control, the repository retains control of querying the data by using a query provider, as illustrated in Figure 6.

Linking Repository to Entity Framework

We now have a repository that uses the unit of work, and a unit of work that wraps Entity Framework. How do we establish the linkage between therepository and our abstract unit of work? There are many design patterns that can solve this problem; simply put, the unit of work can be instantiatedand passed in to each created repository instance.

However, that technique repeats over and over the unit of work creation code. An alternative to this would be to extract the creation of the unit ofwork to a factory or factory method approach. The repository itself could also be created by using a factory or by using the builder pattern. There aremany approaches to creating the relationship between repository and unit of work; it depends how tight or loose you want the implementation.

From the unit of work aspect, it could be easy enough to implement something like the code in Figure 9 to handle the unit of work's creation. Note thatthe framework also takes into effect the ability to cache the created context for later use.

There may be some cases where the CreateQueryable method needs to be generic. In such cases, we could instead use our reflection technique here, takingthe type and creating the ObjectSet dynamically. Additionally, note how the ObjectContext could be cached very easily within the factory.

Querying Considerations

A question may arise about whether to use a list-based interface or a queryable-based interface. A queryable-based interface is naturally exposed tothe caller from an Entity Framework query, so it seems to be the natural choice. This logically makes sense, as a queryable-based interface is alreadysuited for the framework and is supported by other object-relational mapping (ORM) frameworks besides Entity Framework. Using a queryable-basedinterface has other great benefits, but it also has caveats. For instance, it allows developers to modify the query without even realizing it, asmentioned earlier. A developer can add an additional Where() extension method statement and potentially cause the database query to take two to threemore seconds to execute. This, in turn, makes application maintenance more time consuming.

Using a list-based approach also has its benefits and is supported in other ORMs such as NHibernate. Lists ensure that the query has been executedagainst the database and returns a direct result to the caller. Any query modifications after the fact become an in-memory operation performed as aLINQ to Objects query. Keep in mind that lists can be problematic when it comes to creating "views" of data.

We all have scenarios where it's awfully convenient to create an anonymous class, so that we can easily bind the results to the UI. For instance,suppose we wanted to bind a denormalized view of data by using an anonymous class, as shown in Figure 10, to create a logical "view" of the originalsubset of users.

Figure 10: Querying with anonymous classes

var results = userRepos.GetAll().Select(i => new
{
    i.UserID,
    i.LastName,
    i.FirstName,
    i.Role.RoleName
});


"Flattening" the query results in this way enables them to be easily bound to a grid or other tabular-formatted controls. Doing so is easy using thequeryable interface. However, using a list interface, doing this is more difficult; this task must be done within the repository itself. With thequeryable interface, this task becomes a lot more free-form and offers less control over where the query logic resides, something that the listinterface approach offers natively.

Entity Framework is a pretty complex piece of architecture. It has many facets that developers need to know to use the framework effectively, or theycan easily be burned. The ObjectContext makes available the ObjectSet through a variety of techniques, which can then serve up ObjectQuery results inthe form of an IQueryable interface contract. The ObjectContext and ObjectSet can be served up to our framework with a factory, injected into arepository, and made available through a generic interface contract. The examples I've provided can help you navigate Entity Framework's architecturesuccessfully and use it in your applications. For more background on Entity Framework, check out the list of articles below.

Brian Mains([email protected]) is a Microsoft MVP and consultant with Computer Aid, where he works with nonprofit andstate government organizations.

More Articles on Entity Framework

Read more about:

Microsoft
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like