A Generic CRUD Repository for Entity Framework

Update: After a bit more work and discussions with other SSW guys, we've put together an updated version of this implementation. Have a read here.
I wrote a post a while ago about using Generics for lookup tables in Entity Framework and I suggested that the idea could be extended to include all objects and CRUD operations.

Well, I've put together a generic Repository that can be used in your project to give you a consistent base class for all your repositories. It's quite long, so sorry about that.

This implementation follows our SSW Rule, Do you use the repository pattern for data access.

[csharp]
public class DataRepository<TContext> : IDataRepository<TContext> where TContext : ObjectContext
{
// Cached ObjectSets so changes persist
protected Dictionary<string, object> CachedObjects = new Dictionary<string, object>();
protected ObjectSet<TEntity> GetObjectSet<TEntity>() where TEntity : EntityObject
{
var fulltypename = typeof (TEntity).AssemblyQualifiedName;
if (fulltypename == null)
throw new ArgumentException("Invalid Type passed to GetObjectSet!");
if (!CachedObjects.ContainsKey(fulltypename))
{
var objectset = _context.CreateObjectSet<TEntity>();
CachedObjects.Add(fulltypename, objectset);
}
return CachedObjects[fulltypename] as ObjectSet<TEntity>;
}

protected TContext _context;
/// <summary>
/// Constructor that takes a context
/// </summary>
/// <param name="context">An established data context</param>
public DataRepository(TContext context)
{
    _context = context;
}

/// <summary>
/// Constructor that takes a connection string and an EDMX name
/// </summary>
/// <param name="connectionString">The connection string</param>
/// <param name="edmxName">The name of the EDMX so we can build an Entity Connection string</param>
public DataRepository(string connectionString, string edmxName)
{
    var entityConnection =
        String.Format(
            "metadata=res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl;provider=System.Data.SqlClient;provider connection string=",
            edmxName);

    // append the database connection string and save
    entityConnection = entityConnection + "\"" + connectionString + "\"";
    var targetType = typeof (TContext);
    var ctx = Activator.CreateInstance(targetType, entityConnection);
    _context = (TContext) ctx;
}

public IQueryable<TEntity> Fetch<TEntity>() where TEntity : EntityObject
{
    return GetObjectSet<TEntity>();
}

public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : EntityObject
{

    return GetObjectSet<TEntity>().AsEnumerable();
}

public IEnumerable<TEntity> Find<TEntity>(Func<TEntity, bool> predicate) where TEntity : EntityObject
{
    return GetObjectSet<TEntity>().Where(predicate);
}

public TEntity GetSingle<TEntity>(Func<TEntity, bool> predicate) where TEntity : EntityObject
{
    return GetObjectSet<TEntity>().Single(predicate);
}

public TEntity GetFirst<TEntity>(Func<TEntity, bool> predicate) where TEntity : EntityObject
{
    return GetObjectSet<TEntity>().First(predicate);
}

public IEnumerable<TEntity> GetLookup<TEntity>() where TEntity : EntityObject
{
    return GetObjectSet<TEntity>().ToList();
}

public void Add<TEntity>(TEntity entity) where TEntity : EntityObject
{
    if (entity == null)
        throw new ArgumentException("Cannot add a null entity");

    GetObjectSet<TEntity>().AddObject(entity);
}

public void Delete<TEntity>(TEntity entity) where TEntity : EntityObject
{
    if (entity == null)
        throw new ArgumentException("Cannot delete a null entity");

    GetObjectSet<TEntity>().DeleteObject(entity);
}

public void Attach<TEntity>(TEntity entity) where TEntity : EntityObject
{
    if (entity == null)
        throw new ArgumentException("Cannot attach a null entity");

    GetObjectSet<TEntity>().Attach(entity);
}

public void SaveChanges()
{
    SaveChanges(SaveOptions.None);
}

public void SaveChanges(SaveOptions options)
{
    _context.SaveChanges(options);
}

public void Refresh<TEntity>(TEntity entity) where TEntity : EntityObject
{
    _context.Refresh(RefreshMode.StoreWins, entity);
}

public void Refresh<TEntity>(IEnumerable<TEntity> entities) where TEntity : EntityObject
{
    _context.Refresh(RefreshMode.StoreWins, entities);
}

#region IDisposable implementation
private bool disposedValue;
protected void Dispose(bool disposing)
{
    if (!this.disposedValue)
    {
        if (disposing)
        {
            // dispose managed state here if required
        }
        // dispose unmanaged objects and set large fields to null
    }
    this.disposedValue = true;
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
#endregion

}
[/csharp]

A lot of code to be sure, but you only need one of these. After that, whenever you create a new EDMX, you can create a Repository that inherits from this base repository.

Let's look at using it for the same AdventureWorks repository I used last time:

An Adventure Works Entity Model

Here's how you might implement a repository for this data model:

[csharp]
public class AdventureWorksRepository : DataRepository
{
public AdventureWorksRepository(string connectionString) : base(connectionString, "AdventureWorksEntities") { }
}
[/csharp]

Yep, seriously. That's it. Now you have access to all your common CRUD operations (as well as Linq queries) and can do things like this:

[csharp]
var repository = new AdventureWorksRepository(MyConnectionString);

// return all states and provinces
var states = repository.GetAll<StateProvince>();

// get states and provinces with a three letter code
var threeLetterStates = repository.Fetch<StateProvince>()
.Where(s => s.StateProvinceCode.Length == 3)

// get a customer
var cust = repository.GetSingle<Customer>(c => c.CustomerID == 1);

// delete that customer
repository.Delete(cust);

// get a contact
var contact = repository.GetSingle<Contact>(c => c.ContactID == 1);

// edit some properties
contact.Title = "Mr";
contact.Phone = "61 411 111 111";

// save all the changes (this will save the customer deletion and the updated contact)
repository.SaveChanges();
[/csharp]

So just by inheriting a base repository class, you can get some really powerful behaviour which is specific to your data model.

I'd be interested to hear your thoughts on this technique, particularly if you try it in your own project.