Agile EF4 Repository: Part 3 -Fine Tuning the Repository

Third post in my Agile EF4 Repository blog series:

  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
  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

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:

repsave

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!

20 thoughts on “Agile EF4 Repository: Part 3 -Fine Tuning the Repository

  1. Nice. As an nHibernate fan / EF cynic it is encouraging to see the direction you are heading in and the understandings gained moving through this process.

    Your approach for managing UoW sounds promising as this is essentially what is done with nHibernate ( the ISession is equivalent to the UoW / ObjectContext and is usually injected into the repository ). Does EF not integrate with transactions and flush ( Save? ) when a transaction is commited?

    A concern I do have is with the save methods returning strings as sucess depends on a IsNullOrEmpty check rather than a simple true / false. Have you considered using a result object that contains a collection of any errors along with a simple boolean IsSucessful?

  2. Hi Neal. I’m glad I’m helping EF cynics get a new perspective on EF.

    I do have an overload in my repositories to inject the context into them and that’s the path I talked about at the very end of the post.

    EF uses dbtransactions by default and rolls back if there’s a problem during saves. You can also override with a system.transaction.transactionscope but you just have to explicitly accept the changes to your objects.

    Result object – nice idea. What I’m hoping is to get the basic concepts laid out and then fine tune from there but also to give guys like you enough to go on so you can implement something awesome with all of the great ideas and knowledge you have to bring to the table.

    julie

  3. Hi Julie. Nice posts.

    I’m, like Neal, a NHibernate user, but I’m changing to EF because I see a lot o effort and goodnesses on it.

    I take ideas from the world of NHIbernate and I implement artifacts similar to NHibernate.Burrow and its conversation utilities, but I’m a bit worried about transaction management.

    Can you explain or suggest something about than in future posts?

    Thank you very much for your explanations and your time for the comunity.

  4. Hi Kash,

    Transactions are only available with database interaction. There is still no out of the box mechanism for managing transactions in the context.

    Are you asking about transactions with savechanges (which as I said above can be controlled by using TransactionScope) or about managing things within the context e.g. "oh wait, I didn’t want to change that last name after all" ?

  5. The first one.

    In my application there are a lot of legacy code (COM+ with old ADO (not ADO.Net)) and not enough time to change this now, then we need to share transactions between different contexts… Not an easy task.

    Did you do something similar?

  6. TransactionScope wrapping contextA.SaveChanges and contextB.SaveChanges should work just fine. If you don’t call ts.complete, everything will get rolled back (as far as I can recall). Does that cover your needs?

  7. Julie,

    I’m new to the EF. I understand that compiled queries give you a performance boost for often called queries. Do compiled queries fit into your implementation? How would that work when doing unit testing using a mock context?

    I have found your articles very informative. Thank you!

  8. Hi Anders. I’ve actually written a few tweets (julielermanvt) this morning about this. I spent the morning working out how to incorporate precompiled queries into my repository. I might do that post next and push back the testing post. I spent a lot of time today on it though and have to do some other work so probably won’t be posting this for a few more days. But I was pleased with the results after some head scratching. :)

  9. Wondering if you could grab the current transaction in Add / Delete and attach an event handler to the TransactionCompleted event. Then you could use a transaction to manage the save changes / rollback calls rather than having to explicitly call Context.Save.

    Would promote good practice as it could be designed to force use of transactions meaning developers would get used to declaring explicit boundaries for the unit of work.

  10. With context I’m not refer to EF contexts, but different programming contexts (COM+, application domain, web, etc…)

    We are near of the solution I think…

    Thanks a lot for your information and your effort.

  11. I would expose BeginTransaction(IsolationLevel), CommitTransaction and RollbackTransaction in the IContext. Thse methods would call the corresponding methods of the objectContextInstance.Connection object.

    This would allow users to have explicit read/write transactions throughout the context and not just on save.

  12. Hi Dmitry

    The problem is that since ObjectContext stil does not support rollbacks I would have to write an insane amount of code to support them. It wouldn’t be as bad with FKs but if the model doesn’t use FKs you have to be able to roll back relatoinship changes as well.

    The only transax support is with db access not the context.

    Does that make sense or am I missing something in your suggestion?

  13. Julie,

    If you use composition instead of inheritance for your context object such as

    public class BAGAContext : IBAGARepository

    {

    // I wish there was an IObjectContext interface

    private ObjectContext _context;

    private DbTransaction _trans;

    }

    you can implement and Rollback method as follows

    public void BeginTransaction(Isolation level)

    {

    _trans = _context.Connection.BeginTransaction(level);

    }

    public void Rollback()

    {

    if (_trans == null) return;

    _trans.Rollback();

    _context.Dispose();

    _context = new ObjectContext();

    }

    This BAGAContext is an abstraction/facade and should not use inheritance in my opinion.

  14. I also want to add that it makes sense to dispose and recreate the context during a rollback because the data cached by the identity map is technically invalid.

  15. Hi Julie!

    I am a user of EF1 and first I want to thank you very much for writing your book. ;)

    The way you are going in this post looks very good to me, there is just one thing I would change:

    You create the repositories by directly referencing the generic interface like "CustomerRepository : IEntityRepository<Customer>"

    This is acceptable as long as you do not implement any additional public methods in the repository, but you do ;-).

    This would cause a problem when you’d like to write a mock-up of the CustomerRepository as you could only implement the generic interface and therefore would not have access to the additional method.

    Therefore I suggest to add another interface, as soon as additional methods are needed. E.g.:

    public interface IEntityRepository<TEntity>

    {

    //…

    }

    public interface ICustomerRepository : IEntityRepository<Customer>

    {

    IList<Contact> CustomersWhoHaveReservations();

    }

    and then of course

    public class CustomerRepository : ICustomerRepository

    {

    ….

    }

    Are you with me?

    LG

    Alex

  16. Nice series!

    Btw, where are the implementation of the IContext? Have any place where I can download all your solution for this series?

    Thanks!

  17. A question about DeleteObject(s)

    EF uses the construct of marking objects for deletion and then on Save() they get removed.

    I’ve tried to implement this by having:

    // The underlying data of this data set.

    HashSet<TEntity> data;

    // The real data of this data set

    HashSet<TEntity> realdata;

    // The IQueryable version of underlying data.

    IQueryable query;

    and then:

    public void DeleteObject(TEntity item)

    {

    if (item == null)

    throw new ArgumentNullException("entity");

    // need save changes to finalize this remove in data

    realdata.Remove(item);

    }

    public void Save()

    {

    data = realdata;

    query = data.AsQueryable();

    }

    How are folks solving this in their unit tests so that the collection isn’t being altered during enumeration which is sure to throw exceptions

  18. I would like to add an Expression predicate to the IEntityRepository method "List<TEntity> All();" So that it reads something like "List<TEntity> All(Expression someExpressionTree);" or perhaps "List<TEntity> All(LambdaExpression someLambdaExpression);".

    I could type the parameter as "Expression<TDelegate>" but wouldn’t that be evaluated only once the data is returned from the DB?

    It seems like typing it as "Expression" I don’t need to add extra methods to the repository for simple filter statements but still take advantage of EF’s SQL generation capabilities.

Leave a Reply

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


8 × seven =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>