Third post in my Agile EF4 Repository blog series:
- 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
- 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
In the previous post I created an uber repository (someone called it a ‘god class’ :)) that was responsible for all of my object generation. You’ll see some great discussion in the comments with Bobby Johnson (who I met when speaking at the South Sound .NET group in Olympia), Mike Campbell (a friend and unbeknownst to him, sometimes mentor) and myself about the structure of this repository. They encouraged me to look deeper and create repositories that had singular responsibility for classes.
So with a lot of cajoling and many emails back and forth where I tried to explain EF to them and they helped me with a better understanding of the repository pattern and of persistence ignorance, I have taken the repository to the next step and refactored my solution. Faisal Mohamood’s (Entity Framework team) post on EF, Repository and Unit of Work helped as well. This is important because I use the ObjectContext as a unit of work. Therefore, I don’t want Customer to be responsible for saving itself. I might want to save a Customer with Orders and line items. Or perhaps I want to save a Customer and Addresses. Technically speaking, I can still accomplish that by saving a customer graph without having a unit of work. What about querying. If I want to query a customer and it’s orders separately yet still save them together, now I’ll want to have a shared context to perform the save. EF4’s Foreign Keys simplifies things a bit, but as Faisal points out, the unit of work pattern also works with EFv1 where the relationships are maintained by the context.
Okay back to code.
Repository Interface for Entities
I’ve seen plenty of repositories that define an explicit interface for each component, such as ICustomerRepository and IOrderRepository. But I’m not doing that here. Rather, I have a single interface called IEntityRepository that defined a contract for any entity for which I am defining a repository. My instinct has driven me to do this and if it’s really wrong, I’ll find out quickly enough through the comments of this blog post. 🙂
I’m using generics here so that I don’t have to worry about the type. Thanks to a quick generics refresher that I received from Kathleen Dollard last week when we were hold up in her hotel room at DevConnections one evening, this particular usage came easily to me.
using System.Collections.Generic; using POCOBAGA.Classes; namespace POCOBAGA.Repository.Interface { public interface IEntityRepository<TEntity> { TEntity GetById(int id); void Add(TEntity entity); void Delete(TEntity entity); List<TEntity> All(); IContext context { get; } } }
Implementing IEntityRepository
Next I created some repositories that implement this interface to serve as entry points to get at some of my classes. The classes define additional methods as well. The constructor allows the repository to spin up a new context if necessary or use a pre-existing one that can be passed in. This is the same pattern I used in the previous repository.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using POCOBAGA.Repository.Interface; using POCOBAGA.Repository.Model; using POCOBAGA.Classes; using ExtensionMethods; namespace POCOBAGA.Repository.Queries { public class ReservationRepository : IEntityRepository<Reservation> { IContext _context; #region Constructors public ReservationRepository() { _context = new BAGAContext(); } public ReservationRepository(IContext context) { _context = context; } #endregion #region Interface public Reservation GetById(int id) { return _context.Reservations.Include("Customer.Contact") .Where(r => r.ReservationID == id) .FirstOrDefault(); } public void Add(Reservation entity) { context.Reservations.AddObject(entity); } public void Delete(Reservation entity) { _context.Reservations.DeleteObject(entity); } public List<Reservation> All() { return _context.Reservations.ToList(); } public IContext context { get { return _context; } } #endregion #region Reservation Methods 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 } }
Here’s another IEntityRepository implementation.
using System.Collections.Generic; using System.Linq; using POCOBAGA.Repository.Interface; using POCOBAGA.Repository.Model; using POCOBAGA.Classes; using ExtensionMethods; namespace POCOBAGA.Repository.Queries { public class CustomerRepository : IEntityRepository<Customer> { IContext _context; #region Constructors public CustomerRepository() { _context = new BAGAContext(); } public CustomerRepository(IContext context) { _context = context; } #endregion #region Interface public Customer GetById(int id) { return _context.Customers.Include("Contacts") .Where(c => c.ContactID == id).FirstOrDefault(); } public void Add(Customer entity) { context.Customers.AddObject(entity); } public void Delete(Customer customer) { _context.Customers.DeleteObject(customer); } public List<Customer> All() { return _context.Customers.ToList(); } public IContext context { get { return _context; } } #endregion #region CustomerSpecific public IList<Contact> CustomersWhoHaveReservations() { return _context.Contacts .Where(c => c.Customer.Reservations.Any()) .ToList(); } #endregion } }
Saving
Now what about saving? I don’t simply want to call SaveChanges on the context. If you saw the previous post, you may recall that I had a Save method with all types of validation. I want to use that again.
This will mean some mods to the BAGAContext from the first post. Currently, IContext currently exposes SaveChanges. That means that I would be able to call SaveChanges directly from my various IEntityRepository classes. How? ReservationRepository.Context.SaveChanges. That’s not good. This is where I diverge from Faisal’s demo on EF & Unit of Work. His is a simple demo and I am not suggesting that it represents his idea of a fully fleshed out app.
To fix this, change the SaveChanges method in IContext to Save.
string Save();
Next, remove the SaveChanges from BAGAContext and in its place, implement the Save method. Actually, we’ve already created all of the save logic in the original repository from the previous post. I’ll just move that into the BAGAContext class.
Because the BAGAContext class inherits ObjectContext, it does have the ability to call SaveChanges. The switch here is that it’s private to the BAGAContext class, so we can call it from within our Save method.
public string Save()
{
string validationErrors = "";
if (PreSavingValidations(out validationErrors) == true)
{
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 ManagedReservations)
{
try
{
bool isResValid;
string validationError;
isResValid = res.ValidateBeforeSave(out validationError);
if (!isResValid)
{
isvalid = false;
validationErrors += validationError;
}
}
catch (Exception ex)
{
throw ex;
}
}
return isvalid;
}
Now, because I have access to the context from my repositories (e.g. ReservationRepository.Context) I could call this.Context.Save method from any of the repositories.
Here is an important concept to understand if you are new to Entity Framework. ObjectContext.SaveChanges will save EVERY object that it is currently managing. Therefore, if you are sharing a context between the CustomerRepostiroy and the ReservationRepository, and you have created objects in both repositories, then when you call Save, regardless of which repository you access the context through, e.g. custRepository.Context.Save, the context will save all of the customers and all of the reservations that it is managing. And any other types involved as well, such as the Contact class that was eager loaded with customers.
In a class (or UI) that consumes ReservationRepository, e.g. using an instance named resRepository, I can call context.Save like this:
string result = resRepository.context.Save();
And here is how it gets back to the database:
But do I want really want those repositories to give me access to saving? Not really, but I”m going to leave it this way for now. What I will eventually do is create another class to represent the context. The user will have to instantiate that class and pass it into the repositories. Then I will call mycontext.Save when the time comes. IT also means *not* exposing the context, or at least context.Save through the repositories.
So that’s it for now. Thanks again to Bobby and Mike for their encouragement, persistence and patience.
Next up will be TESTING!