(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
- Agile Entity Framework 4 Repository: Part 1- Model and POCO Classes
- Agile Entity Framework 4 Repository: Part 2- The Repository
- 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.
- Agile EF 4 Repositories Part 4: Compiled LINQ Queries
- Agile Entity Framework 4 Repository Part 5: IObjectSet and Include
- 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).
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.