Skip navigation

Application Architecture Design Using Microsoft's Entity Framework

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

Any time a new technology comes on the market, most developers go through the same thought process: Will this technology improve my coding productivity and the applications I develop? We're all used to examining new technologies to determine whether products like Microsoft's Entity Framework provide tangible benefits for developers so that they enhance applications while requiring as little additional work as possible. The challenges come when a developer 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 components together 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 and dependency injection.
  • Test support: As agile development processes and test-driven development with coded unit tests become more commonplace, embedding the ObjectContext directly in an application has less value for this methodology than for others. It should be easy to write test fakes and test within the application.

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, starting off 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 uses and 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 that focuses on the management of a single logical entity. LINQ queries executed against an ObjectSet are represented by the ObjectQuery class, and the ObjectResult 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<T> for each of the logical entities as well as a custom class that inherits from EntityObject. Any stored procedures are mapped to an equivalent function made available through the ObjectContext. This function returns an ObjectResult with the result of the stored procedure, either a scalar value, one of the existing entities, or a new complex type representing the results 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 the actual query has been executed against the database, when it may not have been. Behind the scenes, the ObjectQuery class represents a query created against the ObjectContext. This class does not execute against the database and become a collection of objects until it is iterated through, or the ToList 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 or secondary LINQ query will modify the original query result. This can have beneficial or undesirable consequences. It can be beneficial by allowing you to only add the query parameters that you actually need, instead of inner conditional statements to flesh out the parameters. However, difficulties can arise during application maintenance when you're trying to figure out why that additional query condition is causing the query to perform poorly or return incorrect results. There is no direct way to determine the difference between the two issues at design time, except for examining the code and checking 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 techniques as "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 a new 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<User>();

Unfortunately, a non-generic implementation of this method does not yet exist. However, with some additional work and a whole lot of reflection, the ObjectContext 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 write repetitive 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 several examples 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 Type variable 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 is sought by its primary key. When you dynamically assemble a lambda expression, the Where clause -- or any other extension -- can be invoked dynamically on 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 of changes and the resolution of concurrency problems." Simply put, the Unit of Work pattern uses a container to manage the state of a collection of objects; tracks the changes made to each object, writing these changes to an external store like a database; and resolves problems with concurrency in the 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 or shipped), 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, truck driver). New packages are not in the system right away, but as soon as they are registered, they, too, are tracked. Defective or damaged packages are discarded 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 objects through 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, something that 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 the ObjectContext, to create an abstraction that keeps Entity Framework entirely within the black box. Figure 4 shows the core interface, which will give you an idea of what kind of interface our component will implement.

Figure 4: Unit of work interface

public interface IEntityUnitOfWork<TEntity> : 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. The unit of work that implements this interface takes the ObjectSet via the constructor and wraps it with a commonly used set of interfaces, as shown in Figure 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 one common 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 how this works.

Figure 6: Sample repository for querying

public class RoleRepository : BaseQueryableRepository<Role>, IRoleRepository
{
    public IQueryable<Role> 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 of work by a given interface. Using a separate interface contract for querying allows a few features: The contract can be shifted around, or multiple contracts can be implemented at one given time, depending on your needs. At any rate, the unit of work chose the IQueryableGenericExtender interface to add 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 database queries that are executed against the database. Also note that the lambda expression passes in the object set internally and also passes the ObjectContext in as well as a specific type of unit of work (for the overload). Lambda expressions are handy because you can control the execution, or you 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 very powerful. You can find myriad examples of implementations via an Internet search. The kinds of implementations I'll show here are, of course, not the only 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 and gives 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 simply crafts his or her own LINQ query or chain of extension methods. The downside of allowing this can be the difficulties of standardization pertaining to query logic. If the method of determining the active state of a database record changes because of changing business needs, implementing those business changes means changing each and every LINQ query statement created to meet that need, which could result in a lot of additional work. Additionally, the query executed then resides in the consumer (e.g., an ASP code-behind class, MVC controller), not necessarily in the repository class itself, which can make 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 the repository and our abstract unit of work? There are many design patterns that can solve this problem; simply put, the unit of work can be instantiated and 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 of work 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 are many 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 that the 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, taking the 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 to the caller from an Entity Framework query, so it seems to be the natural choice. This logically makes sense, as a queryable-based interface is already suited for the framework and is supported by other object-relational mapping (ORM) frameworks besides Entity Framework. Using a queryable-based interface has other great benefits, but it also has caveats. For instance, it allows developers to modify the query without even realizing it, as mentioned earlier. A developer can add an additional Where() extension method statement and potentially cause the database query to take two to three more 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 executed against the database and returns a direct result to the caller. Any query modifications after the fact become an in-memory operation performed as a LINQ 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 original subset 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 the queryable interface. However, using a list interface, doing this is more difficult; this task must be done within the repository itself. With the queryable interface, this task becomes a lot more free-form and offers less control over where the query logic resides, something that the list interface approach offers natively.

Navigating Entity Framework's Complexity

Entity Framework is a pretty complex piece of architecture. It has many facets that developers need to know to use the framework effectively, or they can easily be burned. The ObjectContext makes available the ObjectSet through a variety of techniques, which can then serve up ObjectQuery results in the form of an IQueryable interface contract. The ObjectContext and ObjectSet can be served up to our framework with a factory, injected into a repository, and made available through a generic interface contract. The examples I've provided can help you navigate Entity Framework's architecture successfully 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 and state government organizations.

More Articles on Entity Framework

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish