Agile Entity Framework 4 Repository: Part 2- The Repository

(11/21/09: Note I have changed my interface name to IBAGAContext, because it is really a contract for the context, not the repository).

The Series: so far

  1. Agile Entity Framework 4 Repository: Part 1- Model and POCO Classes
  2. Agile Entity Framework 4 Repository: Part 2- The Repository
  3. Agile EF4 Repository: Part 3 -Fine Tuning the Repository Takes the uber repository created in Part 2 and splits it up into class-specific repositories for a more Domain Driven design.
  4. Agile EF 4 Repositories Part 4: Compiled LINQ Queries
  5. Agile Entity Framework 4 Repository Part 5: IObjectSet and Include
  6. Agile Entity Framework 4 Repository: Part 6: Mocks & Unit Tests

Part 1 of this series, Agile Entity Framework 4 Repository- Part 1- Model and POCO Classes, covered the classes, the model and the context.

Now we will look at the repository. Since the model & context are also part of the repository, my working title for this part of the repository is the Query Repository. It houses all of the database interaction and some additional related logic. Query Repository is somewhat of a misnomer because there is also logic to persist data back to the data store in here.

A caveat. This is not the end-all-be-all implementation of the repository pattern. It is a look at how you can use EF and the ObjectContext in a testable way. The hope is that those of you who are expert agile developers, will be able to take this information and plug it into your patterns. For the rest of us, it is baby steps.

Normally in this layer of an app using EF, I would be instantiating an ObjectContext and using it to perform queries, updates and other functions. But the purpose of this repository is to be used by both a real application (e.g., the UI) and a series of unit tests. Therefore the repository needs to be pretty flexible and can’t be tightly bound to the Entity Framework’s ObjectContext.

So what will it use for queries and updates etc? It will require “something that can perform queries against my model, classes and data and also persist data to the store”. It should feel something like the ObjectContext but not truly be one. And notice the word “store” rather than “database”. Where the data comes from is not relevant. Doesn’t matter in the least. If it happens to come from a database – great. Somewhere else – that’s good, too. The context in Part 1 happens to hit a database. But my repository doesn’t care about those details.

If you look at the context class I built in Part 1 of this series, it has a number of public properties and methods: Customers, Reservations, ManagedReservations, etc. Additionally, my context inherits behavior from the ObjectContext class such as SaveChanges and DetectChanges.

The Repository Interface

The Context Interface

These are properties and methods that my application relies on so I will create an interface to act as a contract to ensure that I have these properties and methods. Then I’ll instruct my repository to use anything that implements that interface.

Note that if you were using a mocking framework, you wouldn’t need to do all of these steps. But I’m building this from scratch to better understand all of the working parts. It’s like studying classical music before trying to do jazz improvisation. 🙂

Note that BAGA is the name of the business in my book, Programming Entity Framework, which stands for Break Away Geek Adventures, so you’ll see that acronym a lot and it’s used in the namespaces and in the name of the repository.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using POCOBAGA.Classes;

namespace POCOBAGA.Repository.Interface
{
  public interface IBAGAContext 
  {
     IObjectSet<Contact> Contacts{get;}
     IObjectSet<Customer> Customers {get;}
     IObjectSet<Trip> Trips{get;}
     IObjectSet<Reservation> Reservations { get; }
     IObjectSet<Payment> Payments { get; }

     void DetectChanges(); //used for ObjectContext detect changes
//
or mock's fix-up mechanism int SaveChanges(); //used for ObjectContext.SaveChanges default behavior
// with no int return or parameters
IEnumerable<Reservation> ManagedReservations { get; } } }

This interface should look familiar. It returns a bunch of IObjectSets and the ManagedReservations that you saw in the context class (Part 1).

Also, you may recall seeing the interface referred to in that context class….

public class BAGAContext : ObjectContext, IBAGAContext

Just keep that little tidbit in the back of your mind while we look at the Query Repository

The Query Repository (or whatever I’ll be calling it…)

So now it’s time for the class where all the good stuff happens. This is where I run queries and persist data back to the database. It can be used by whatever class wants to get at my model’s data.

There are actually more methods in my repository. I’m only displaying the ones relevant for this discussion.

Discussion follows the code.
using System.Text;
using System.Data.Objects;
using POCOBAGA.Classes;
using POCOBAGA.Repository.Interface;
using POCOBAGA.Repository.Model;
using ExtensionMethods;

namespace POCOBAGA.Repository.Queries
{

    public class POCOQueryRepository
    {
        IBAGARepository _context;

        #region Constructors

        public POCOQueryRepository()
        {
            _context = new BAGAContext();
        }
        public POCOQueryRepository(IBAGAContext context)
        {
            _context = context;
        }

        #endregion

        #region Queries
      
        public IList<Reservation> GetReservationsForCustomer(int? CustomerID)
        {
            if (!CustomerID.HasValue || CustomerID.Value < 1)
            {
                throw new ArgumentOutOfRangeException();
            }
            return _context.Reservations.
              Where(r => r.ContactID == CustomerID).ToList();
        }
      #endregion

      #region Saving
      public string Save()
      {
            string validationErrors = "";
            if (PreSavingValidations(out validationErrors) == true)
            {
               //this Save is not taking n-tier issues into account
_context.SaveChanges(); return ""; } else return "Data Not Saved due to Validation Errors: " + validationErrors; }
        public bool PreSavingValidations(out string validationErrors)
        {
            bool isvalid = true;
            validationErrors = "";

            foreach (Reservation res in _context.ManagedReservations)
            {
                try
                {
                    bool isResValid;
                    string validationError;
                    isResValid = res.ValidateBeforeSave(out validationError);
                    if (!isResValid)
                    {
                        isvalid = false;
                        validationErrors += validationError;
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
            return isvalid;
        }

     }
}

This repository has two constructors. The first takes no arguments. If this constructor is called, then when the repository is instantiated, it will spin up a new context – specifically, the “real” context, the one which involves the EF APIs and interacts with the database. The overload to the constructor allows the calling code to pass in a context. What this will do for testing is allow us to pass in a fake context that won’t be so bound to EF or the database. As long as whatever is passed in implements IBAGARepository IBAGAContext, we’re good. That way we can safely use the context in this class and call methods such as _context.SaveChanges or _context.Reservations.

There are only two publicly exposed methods in this example. One is to perform a query and the other is to perform a save. The save has a helper method to ask entities to validate themselves. In this case, it only bothers to ask the Reservation objects to perform their validation. Note that this Save method does not take n-tier issues into account. It presumes that the context already has all of the necessary info for updating. Dealing with n-tier here would be out of scope of the discussion.

Thinking ahead about testing, these two methods provide a challenge for testing without hitting the database. The GetReservationsForCustoemr does some validation and then performs a query. There are a few things to test here. First I want to be sure the validation works. That will mean having a test that provides a bad value and making sure the validation detects that and throws the expected exception. This ones easy because I won’t have to worry about that query. But what about the test to ensure that a good ID passes the validation. I can’t say “okay but after you check that just STOP and don’t call the query. The query will have to run. But why do I want to hit the database when all I’m trying to do is test that the ID validation works. This is where the challenge comes in.

In the save method, my focus will be on the validations. I want to be sure that they do the right thing with bad data and do the right thing with good data. When testing this, I don’t really need to send the data to the database.

The interface is in it’s own project and has a reference to the classes project as well as System.Data.Entity. The only reason it needs System.Data.Entity is to get its hands on IObjectSet.

The Query repository project needs access to the classes, the repository interface and the model repository (with the model & ObjectContext).

image

In the next blog post we’ll start looking at the testing side of things for this solution and in doing so, some of the puzzle pieces that I put in place here will make more sense.

  Sign up for my newsletter so you don't miss my conference & Pluralsight course announcements!  

16 thoughts on “Agile Entity Framework 4 Repository: Part 2- The Repository

  1. Hi Julie,

    Good series so far.

    I would like to point out that your repository interface/class is allowing some of the persistence concerns to leak out which might result in a direct dependency being created to EF. IObjectSet<T> is a interface defined by the ORM, any way to return IEnumeriable<T> or IQueryable<T> instead?

    Also your repository class is going to grow as your domain grows, it’s a better practice to create a repository per aggregate root of your domain. Which is a fancy way of saying one repo per entry point in to your domains object hierarchy.

    For instance in your model it appears that all other objects depend on some Customer context. You would prolly never search for a Reservation directly with out giving it a Customer to filter by. So in this case Customer is an aggregate root and it makes sense to have a ICustomerRepository interface.

    Here is a great blog post that talks about the Repository pattern much more clearly than I have… 8)

    blogs.hibernatingrhinos.com/…/the-repository-

  2. Julie,

    Great Post.

    I too noticed that you’re allowing some of your persistence tier/EF details to ‘leak’ into your interface just as NotMySelf did. I’d just make those List<T> instead of IObjectSet<T> and that way your repo will help support persistence ignorance.

  3. IObjectSet and ObjectSet provide me with some methods critical to EF that I won’t get with an IQueryable or IList or other relevant interfaces.

    If I created my own interface that had those same methods (so that my IReposityr doesn’t have to rely on EF API) does that still break PI?

    I should also do a check here – are you guys striving for *perfection* or is this just basic goodness?

  4. Hi!

    A repository for me is limited to one aggregate root. I don’t want it to be for all my entities in my domain. Although I’m ok with a solution that gives you access to "all" your entities, just don’t put the word "Repository" in the name.

    I have written a bit about how you can put together a generic entity store (not entity repository) that can handle all entities that you provide mapping for. The way I like to implement the "named queries" is not to have a custom implementation of this entity store that contains specific functions for getting results like "GetNextOrderToProccess". Instead I would extend IQueryable<Order> with this specific query. This lets my run the query against any store that can give me an IQueryable<Order>.

    My latest post about this can be found here: daniel.wertheim.se/…/extend-iqueryab

    //Daniel

  5. Agreed that your repository shouldn’t return IObjectSet<T>. The issue isn’t about perfection, it’s about your dependency graph; what consumers will have to ‘know’ about and be dependant on.

    Also, and this one’s a minor nit, but why does your GetReservationsForCustomer method take an int? parameter, then immediately throw an exception if it’s null? Wouldn’t that make more sense to just take a regular int32 (int) rather than a nullable?

  6. ahh crap I noticide that int? when I was doing my demo and meant to take it out. Can’t remember where that came from. Maybe leftover from expiermenting.

    I’m working out a new set of repositories based on discussions here and offline with Bobby & Mike. However, notice that the repository in this post ("Query Repostories") does not return an IObjectSet. Thats’ in my context.

  7. Interesting comments so far.

    My understanding of EF is that all of your related objects have to part of the same context to be tracked properly. If you go down the "One repository interface per domain root – ICustomer… IContact…ITrip… – they will all have to inherit IDomainRoot which has the context property on it, won’t they?

    Otherwise, you’ll try to relate a Reservation to a customer and you’ll get a "The reservation isn’t being tracked by the customer context" type error message….because it’s being tracked by the reservation context.

  8. I think that you should use a T4 template to generate your repository. It’s what I did in a previous project and it’s very useful because you can use the same template for all your projects.

    Matthieu

  9. @Dave – I’ve got this worked out and am posting the next post shortly

    @Matthieu – I totally agree T4 is the way to go, but the purpose of these posts is to lay out the concepts. T4 would be part of "next steps".

  10. The repository (in the DDD context) is supposed to be a generic collection for a aggregate root entity. It should only have Add (aka AttachTo), Remove (aka DeleteObject) and various retrieve methods.

    It should _not_ expose collections of every single entity nor the SaveChanges/DetectChanges methods.

    I would make a repository interface that implements IObjectSet<T> + IQueryable<T> and provides the methods described in the first paragraph.

    SaveChanges/DetectChanges should be abstracted into a unit-of-work interface.

    Then you can create a context/session abstraction that is independent from the System.Data.Entities assembly. It would include all the repositories and implement the unit-of-work interface.

  11. I just realized that there is part 3 to the repository series and it looks _much_ more inline with what I described.

  12. If I had the extra methods on my object context interface – CreateQuery and ExecuteFunction to execute ESQL, and stored procedures – I wouldnt be able to create a test double of this context for testing without re-implementing these functions.

    In this case, would it be better to define an interface for the whole repository and create a test double for that instead of test doubles for my object context and IObjectSet?

  13. Hi Julie,

    First of all, I really admire your work and your strong understanding of EF. You are a veteran and please, keep up the great work.

    I am currently implementing a new system using EF 6 and was wondering whether the Repository pattern still holds in EF 6? Do I still need to consider it in terms of test-ability and DDD?

    1. Less of a fan of actual repo pattern with EF, but a big fan of separating and encapsulating the data access code. If it helps, you can see some of that in the DDD Fundamentals course that Steve Smith and I did on Pluralsight. It’s at bit.ly/PS-DDD

      HTH
      julie

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.