Click here to Skip to main content
15,867,488 members
Articles / Database Development / SQL Server

Abstracting the ORM Framework

Rate me:
Please Sign up or sign in to vote.
4.45/5 (10 votes)
8 Jul 2013CPOL6 min read 27.8K   333   26   7
Abstracting the ORM Framework via Repository/Unit Of Work patterns

Introduction

With this tutorial I would like to explain how we can include an Object/Relational Mapping (ORM) framework into our project in such a way that generic situations are handled at the lowest possible level. This also ensures that developers who use this solution do not get bothered with too much details.

I am using the Entity Framework as the ORM framework in this tutorial. However with a few adaptations it is certainly possible to replace this by another framework (e.g., NHibernate).

Abstracting the framework

To abstract the chosen ORM framework the combination of the Repository and the Unit of Work pattern is chosen.

Martin Fowler has the following definitions:

  • A Repository mediates between the domain and data mapping layers. It allows us to work with the entities as a local collection on our end and carries out the necessary operations to persist/retrieve them behind the scenes.
  • The Unit of Work maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

In other words a Unit of Work contains mutations to one or more entities, it combines the mutations done on multiple repositories. These mutations together act as one to ensure that the underlying data source maintains a consistent state. Either all mutations succeed or all fail. For this to work we (re)use a database transaction.

The following image contains an overview of the used classes and their dependencies.

Image 1

UnitOfWork<T>

A generic implementation that ties together one or more sets of objects which have been added/removed/updated. It provides a function (‘Execute’) which allows multiple repositories (done via the reference to the object sets) to be managed as one. Successful execution will ensure that the enclosed transaction is committed.

It is possible to use a UnitOfWork recursively, the transaction within the UnitOfWork at the top will be responsible for either the commit or the rollback. In the end the result of the mutations is that they either all succeed or all fail.

Repository<T>

A generic implementation for a repository with functionality to manage a specified type of entity in an underlying data source. This data source is represented by the object set.

Methods are available that allow CRUD style actions to be executed on the underlying entities. If possible they provide a possibility to pass a LINQ expression for providing a way to customize these actions.

IObjectSetCreator<T>

This provides a possibility for the Repository to create an instance of the set of objects via the UnitOfWork without needing to specify the ObjectContext used.

ObjectContext

This provides us the connection with the data source which contains the entities which are used. The specific type DataModelObjectContext contains information regarding the data source and the definition of the entities used.

Preparation for running the solution

Prerequisites

  • SQL Express 2012, named instance 'SQLEXPRESS' with windows security enabled
  • Visual Studio 2010

I will describe the first few steps so that people with no or not much experience with the Entity Framework know which steps need to be taken to try it out their selves in a new solution.

It is possible to start at Step 4 when one is interested in just running the provided solution.

Step 1

In my example I started with a new solution and directly add the DataAccess project. This project will contain the functionality used to work with the Entity Framework.

Image 2

To add the Entity Framework to the solution we require NuGet. This can be installed via the Extension Manager within Visual Studio. Select the DataAccess project and right-click the References. Select ‘Manage NuGet Packages’ and install the Entity Framework.

Step 2

Continue by adding a new item to the DataAccess project, namely the ‘ADO.NET Entity Data Model’ and name it accordingly.

Image 3

After pressing ‘Add’ we have to decide if we are going to use model-first, code-first or database-first. For this example we will use the model-first approach and select ‘Empty model’.

Step 3

In this empty model we will add the following 2 entities.

Image 4

I added an extra scalar property to each entity of type string (Title and Name). I also added an association from Author to Book. This will directly configure the multiplicity as shown. The Navigation Properties are automatically added and these will allow us to easily reach any referenced entities.

Step 4

Right-click somewhere in the model and select 'Generate Database from model'.

Image 5

Select 'New Connection …’

Image 6

Select the data source that should be used, use ‘DataModel’ as the name of the database. Keep the rest of the properties unchanged and press ‘OK’.

Image 7

Copy the selected entity connection string so that we can use it in our specific DataModelObjectContext class. This no longer requires saving the connection string in the app.config file, so we can disable that option. The specific ObjectContext class is used to maintain the correct connection string to make it easier to have more than one model in the solution. However in this tutorial we will have only one model.

Continue by clicking 'Next' and execute the generated sql script within Visual Studio to have the two tables created.

Step 5

The constructor of the DataModelObjectContext is looking like this:

C#
/// <summary>
/// Default constructor for class
/// </summary>
public DataModelObjectContext()
    : base(@"metadata=res://*/DataModel.csdl|res://*/DataModel.ssdl|" + 
           @"res://*/DataModel.msl;provider=System.Data.SqlClient;" + 
           @"provider connection string=""Data Source=localhost\SQLEXPRESS;" + 
           @"Initial Catalog=DataModel;Integrated Security=True""")
{
}

Beware that this connection string contains the name of the model and also the location & name of the data source used. In your case this can be different.

Using the Unit of Work / Repository as a developer

As a developer it is quite easy to work with the entities by using the provided UnitOfWork and Repository classes. To give an idea how easy I added some snippets that indicate how to use them accordingly. The attached example solution has buttons which allow each of these snippets to be executed.

Image 8

Storing

The following snippet shows the actions required to store a new author in the data source.

C#
using (UnitOfWork<datamodelobjectcontext> unitOfWork = 
    new UnitOfWork<datamodelobjectcontext>())
{
    bool result = unitOfWork.Execute(() =>
    {
        Repository<author> authorRepository = 
            new Repository<author>(unitOfWork);

        Author author = new Author()
        {
            Name = "Stephen Hunt"
        };

        authorRepository.Add(author);
                    
        return true;
    });
}

Working with two repositories

The following snippet shows how to create two books with Stephen Hunt as the author. It continues based on the result of the previous snippet, so a Find is done to get the added Author.

As one can see it is as easy as creating a book, setting the reference to the author and finally adding it to the repository. Finishing the execution flow normally will automatically trigger the saving of the changes and to trigger the commit of the enclosed transaction.

C#
using (UnitOfWork<DataModelObjectContext> unitOfWork =
    new UnitOfWork<DataModelObjectContext>())
{
    bool result = unitOfWork.Execute(() =>
    {
        Repository<author> authorRepository =
            new Repository<author>(unitOfWork);

        // Find the author with the name "Stephen Hunt".
        Author author = authorRepository.Find(
            item => item.Name.Equals("Stephen Hunt")).
                SingleOrDefault<author>();

        if (author != null)
        {
            Repository<book> bookRepository =
                new Repository<book>(unitOfWork);

            Book book1 = new Book()
            {
                Title = "Sliding Void",
                Author = author
            };

            Book book2 = new Book()
            {
                Title = "In the Company of Ghosts",
                Author = author
            };

            bookRepository.Add(book1);
            bookRepository.Add(book2);
        }

        return true;
    });
}

Retrieving

The next snippet shows the actions required to get the author and the referred books. This requires an extra action to ensure that the required navigation properties are retrieved from the data source. This way it is possible to have more control over the retrieval of the navigation properties and this directly impacts the query which is done on the underlying data source.

C#
using (UnitOfWork<DataModelObjectContext> unitOfWork =
    new UnitOfWork<DataModelObjectContext>())
{    
    bool result = unitOfWork.Execute(() =>
    {
        Repository<author> authorRepository =
            new Repository<author>(unitOfWork);

        // Find the author with the name "Stephen Hunt".
        Author author = authorRepository.Find(
            item => item.Name.Equals("Stephen Hunt")).
                SingleOrDefault<author>();

        if (author != null)
        {
            // Request that the navigation property 'Books' is 
            // filled.
            unitOfWork.ObjectContext.LoadProperty(
                author, item => item.Books);
        }

        return true;
    });
}

Removing

The next snippet shows the actions required to remove an entity, in this case one of the books of the author.

C#
using (UnitOfWork<DataModelObjectContext> unitOfWork =
    new UnitOfWork<DataModelObjectContext>())
{
    bool result = unitOfWork.Execute(() =>
    {
        Repository<author> authorRepository =
            new Repository<author>(unitOfWork);

        // Find the author with the name "Stephen Hunt".
        Author author = authorRepository.Find(
            item => item.Name.Equals("Stephen Hunt")).
                SingleOrDefault<author>();

        if (author != null)
        {
            // Request that the navigation property 'Books' is 
            // filled.
            unitOfWork.ObjectContext.LoadProperty(
                author, item => item.Books);

            Book book = author.Books.FirstOrDefault<book>();

            if (book != null)
            {
                new Repository<book>(unitOfWork).Remove(removedBook);
        }

        return true;
    });
}

Data Consistency

Data consistency is very important when working with data sources. This is the reason that the Unit of Work internally uses a transaction to ensure that all mutations done within the Execute flow are atomic. To show how this works the following snippet is used.

Note that after removing the book from the author an exception is thrown. This will trigger a rollback of the underlying transaction and thus of all mutations done within the Unit of Work.

C#
// First we display the books for author 'Stephen Hunt'.
DisplayBooksForAuthor(("Stephen Hunt");

using (UnitOfWork<DataModelObjectContext> unitOfWork =
    new UnitOfWork<datamodelobjectcontext>())
{
    bool result = unitOfWork.Execute(() =>
    {
        Repository<author> authorRepository =
            new Repository<author>(unitOfWork);

        // Find the author with the name "Stephen Hunt".
        Author author = authorRepository.Find(
            item => item.Name.Equals("Stephen Hunt")).
                SingleOrDefault<author>();

        if (author != null)
        {
            // Request that the navigation property 'Books' is 
            // filled.
            unitOfWork.ObjectContext.LoadProperty(
                author, item => item.Books);

            Book book = author.Books.FirstOrDefault<book>();

            if (book != null)
            {
                new Repository<book>(unitOfWork).Remove(book);

                throw new InvalidOperationException();
            }
        }

        return true;
    });
}

// Now we display the books for author 'Stephen Hunt' once more.
DisplayBooksForAuthor("Stephen Hunt");

Points of Interest

The execution of the mutations on the repositories within the context of the Unit of Work are handled via the Func delegate. This enables us to easily keep the generic functionality within the Unit of Work.

C#
/// <summary>
/// Execute the passed function within the context of the unit of work.
/// </summary>
/// <param name="function" />The function to execute.
/// <returns>true when the function was successfully executed and 
/// all changes are committed, false otherwise.</returns>
public bool Execute(Func<bool> function)
{
    bool result = false;

    try
    {
        using (TransactionScope transactionScope = new TransactionScope())
        {
            try
            {
                if (function())
                {
                    // The function was successful, so we can save all 
                    // changes and complete the transaction.
                    result = SaveChanges();

                    if (result)
                    {
                        try
                        {
                            transactionScope.Complete();
                        }
                        catch (InvalidOperationException)
                        {
                            result = false;
                        }
                    }
                }
                else
                {
                    // Function failed.
                }
            }
            catch (EntityCommandExecutionException)
            {
                // A command (stored procedure) failed to execute.
            }
            catch (Exception)
            {
                // An unspecified exception occurred.
            }
        }
    }
    catch (TransactionAbortedException e)
    {
        // Transaction is aborted.
    }
    catch (TransactionInDoubtException e)
    {
        // Transaction is in doubt.
    }
}

The Enclosed Solution

The enclosed solution contains two projects:

  • AbstractingDataAccess: Contains the windows form
  • DataAccess: Contains the specific implementations and the model for the Entity Framework

History

  • 4 July 2013 - First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Altran
Netherlands Netherlands
I am a relaxed guy, born in '73, who likes to develop software professionally in both my work and in my spare time.

Keywords are C#, WCF, WPF.

Comments and Discussions

 
GeneralMy vote of 4 Pin
gore0112-Aug-13 9:15
gore0112-Aug-13 9:15 
QuestionAbstractize the abstraction Pin
Nicolas Dorier12-Jul-13 13:53
professionalNicolas Dorier12-Jul-13 13:53 
AnswerRe: Abstractize the abstraction Pin
cjb11024-Mar-15 22:09
cjb11024-Mar-15 22:09 
GeneralRe: Abstractize the abstraction Pin
Nicolas Dorier25-Mar-15 3:14
professionalNicolas Dorier25-Mar-15 3:14 
GeneralMy vote of 2 Pin
Member 1004701511-Jul-13 9:32
Member 1004701511-Jul-13 9:32 
QuestionIs the ORM really abstracted? Pin
Cristian Odea8-Jul-13 1:15
Cristian Odea8-Jul-13 1:15 
AnswerRe: Is the ORM really abstracted? Pin
JP van Mackelenbergh8-Jul-13 18:43
JP van Mackelenbergh8-Jul-13 18:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.