Aspen – A sample app using Silverlight 4 and .Net 4.0 – part 3 of X – Repository implementation

Note: The example code in this blog post is based on the VS2010 Beta 2, changes can be made until VS2010 hits RTM.

In my previous post I wrote about how I use MEF and Entity Framework 4.0 Code-only feature. I have done a lot of refactoring and added the first Repository to the “Aspen” application since then. In this post I’m going to show you the refactoring I have made and how I have implemented the Repository for the Member Entity, MemberRepository.

Changes made since the previous post


I made some changes to the AspenObjectContextFactory. I made it more generic and renamed the class to ObjectContextFactory, the class now implements an interface, IObjectContextFactory:


public
interface IObjectContextFactory { ObjectContext Create(DbConnection con); ObjectContext Create(string connectionString); }


The following code is the ObjectContextFactory:


public
class ObjectContextFactory : IObjectContextFactory { [ImportMany(typeof(StructuralTypeConfiguration), AllowRecomposition = true)] private List<StructuralTypeConfiguration> _configurations = null; public ObjectContext Create(string connectionString) { if (string.IsNullOrEmpty(connectionString)) throw new ArgumentNullException("connectionString"); return this.Create(new SqlConnection(connectionString)); } public ObjectContext Create(DbConnection con) { if (con == null) throw new ArgumentNullException("con"); if (_configurations == null) this.Compose(); var builder = new ContextBuilder<ObjectContext>(); if (_configurations != null) _configurations.ForEach(c => builder.Configurations.Add(c)); return builder.Create(con); } private void Compose() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var catalogParts = new AggregateCatalog(); foreach (var assembly in assemblies) { if (assembly.FullName.Contains("EntityMapping")) catalogParts.Catalogs.Add( new AssemblyCatalog(assembly)); } var container = new CompositionContainer(catalogParts); container.ComposeParts(this); } }


The changes made to the “Aspen”ObjectContextFactory are a new constructor, taking a connection string as an argument and will create a SqlConnection by default. The other changes made to the class is to make sure it will only return an ObjectContext, not the AspenObjectContext as before (AspenObjectContext is now removed from the project).


Repository


When the ObjectContextFactory was in place I wanted to use it, so I created the first Repository, MemberRepository. The MemberRepository will take an IObjectContext as an argument. The reason why I want the Repository to take an IObjectContext is to make sure several repositories in the future can share the same ObjectContext and also remove some dependencies etc (Using the Dependency Inversion Principle). Entity Framework doesn’t have an IObjectContext interface and I will not use the ObjectContext because it will only make my Repository to be dependent on a detail instead of an abstraction (Will make it harder for me to write unit test). So I had to create my own IObjectContext interface:


public interface IObjectContext : IDisposable
{
   IObjectSet<T> CreateObjectSet<T>() where T : class;
        
   void SaveChanges();

}


The IObjectContext interface has two methods, the CreateObjectSet<T> and the SaveChanges. At the moment I don’t need more methods. I created an ObjectContextAdapter which inherits the IObjectContext interface, the ObjectContextAdapter will use the ObjectContextFactory to create an instance of an ObjectContext. Here is the ObjectContextAdapter:

public class ObjectContextAdapter : IObjectContext
{
    private ObjectContext _context = null;
    private IObjectContextFactory _objectContextFactory = new ObjectContextFactory();

    public ObjectContextAdapter(string connectionString)
    {
        _context = _objectContextFactory.Create(connectionString);
    }

    public ObjectContextAdapter(DbConnection con)
    {
       _context = _objectContextFactory.Create(con);
    }

    public ObjectContextAdapter(ObjectContext context)
    {
        _context = context;
    }

    public IObjectSet<T> CreateObjectSet<T>() where T : class
    {
        return _context.CreateObjectSet<T>();
    }

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

    public void Dispose()
    {
        _context.Dispose();
    }
}


First I was thinking about making it possible to replace the IObjectContextFactory, but didn’t really find any reason to do so. I will see if I will made it possible in the future. The following is an example how the ObjectContextAdapter will be used in the production code:


var objectContext = new ObjectContextAdapter(
      ConfigurationManager.ConnectionStrings["AspenConnectionString"].ConnectionString);

var memberRepository = new MemberRepository(objectContext);



Because the ObjectContextAdapter will create the instance of an ObjectContext, I can now for example use Unity or StructureMap etc to create an instance of an Repository and use dependency injection. Here is the how I have implemented the MemberRepository:



public class MemberRepository : IMemberRepository
{
    readonly IObjectContext _objectContext = null;
    readonly IObjectSet<Member> _objectSet = null;

    public MemberRepository(IObjectContext objectContext)
    {
        if (objectContext == null)
           throw new ArgumentNullException("objectContext");

        _objectContext = objectContext;
        _objectSet = _objectContext.CreateObjectSet<Member>();
    }

    public IQueryable<Member> GetQuery()
    {
        return _objectSet;
    }

    public IEnumerable<Member> GetAll()
    {
        return _objectSet.ToList();
    }

    public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> where)
    {
       return _objectSet.Where(where);
    }


    public TEntity Single(Expression<Func<TEntity, bool>> where)
    {
       return _objectSet.SingleOrDefault(where);
    }

public void Delete(Member entity) { _objectSet.DeleteObject(entity); this.SaveChanges(); } public void Add(Member entity) { _objectSet.AddObject(entity); this.SaveChanges(); } private void SaveChanges() { _objectContext.SaveChanges(); } }


Note: I could have done a more generic Repository without creating one for each Entity, but I decided to not do that. I prefer to have a Repository for each Entity instead of an generic one.


The IMemberRepository interface implemented by the MemberRepository is kind of empty right now, it implements an generic IRepository<T> interface:

public interface IMemberRepository : IRepository<Member>
{
}


The following is the IRepository<T> code:


public interface IRepository<T> where T : class
{
   IQueryable<T> GetQuery();
       
   IEnumerable<T> GetAll();
        
   IEnumerable<T> Find(Expression<Func<TEntity, bool>> where);
        
   T Single(Expression<Func<TEntity, bool>> where);
        
   void Delete(T entity);
        
   void Add(T entity);
}


If you have any comments about anything, please let me know.

Testing

I will end this blog post about mentioning something about testing. Because I have created the IObjectContext interface, I don’t need to create a Stub for my MemberRepository in my unit tests, instead I can mock the IObjectContext. Here are the classes I use in my unit tests for the moment:

The ObjectContext which will be passed to the MemberRepository:

class MockObjectContext : IObjectContext
{
   public IObjectSet<T> CreateObjectSet<T>() where T : class
   {
        return new MockObjectSet<T>();
   }

   public void SaveChanges()
   {
   }

   public void Dispose()
   {
   }
}


The ObjectSet for testing, MockObjectSet<T>:

class MockObjectSet<T> : IObjectSet<T> where T : class
{
   private List<T> _items = new List<T>();

    public void AddObject(T entity)
    {
        _items.Add(entity);
    }

    public void Attach(T entity)
    {
    }

    public void DeleteObject(T entity)
    {
      _items.Remove(entity);
    }

    public void Detach(T entity)
    {
    }
public IEnumerator<T> GetEnumerator() { return _items.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _items.GetEnumerator(); } public Type ElementType { get { return typeof(T); } } public System.Linq.Expressions.Expression Expression { get { return null; } } public IQueryProvider Provider { get { return null; } } }


Here is how I can test my MemberRepository:

var mockObjectContext = new MockObjectContext();
            
var memberRepository = new MemberRepository(mockObjectContext);

memberRepository.Add(new Member() { FirstName = "John", LastName = "Doe" });

Assert.AreEqual<int>(1, memberRepository.GetAll().Count());

This test is kind of dumb, but I decided to use some sort of TDD (Test Driven Design) first to make sure I find a good design for the Repository etc.

If you want to know when I publish a new blog post to my blog, you can follow me on twitter: http://www.twitter.com/fredrikn

7 Comments

  • Hello Fredrik

    Just wanted to know whether this application published into codeplex as you had already mentioned earlier.

    regards
    Dhinesh Kumar


  • @Dhinesh Kumar:
    It's on codeplex, but the project is not published yet. We will have a meeting today and I will see when we will start publishing the code.

  • Hello Frederick

    Can these repositories be exposed as WCF Services?

    regards
    Dhinesh Kumar


  • @Dhinesh Kumar:
    Well, you don't directly expose them.. Your WCF Service can use the Repositories, but you need to create a DataContract for your WCF Services.. you can then map the Domain Entity to the contract..

  • Hi, do you think it is a good idea to persist changes when you add an item to repository?
    And what if you modify an object, how will you persist?

    Good work


  • @André Werland:
    I have changed that in the latest code, several changes, the ObjectContext will now be created when the DomainService is requested and live and persist changes at the end of the request.. So the Repository will not make a call to the SubmitChanges.. Will blog about it later.

  • Hi, what's your plan for creating an Edit method in the Repository?

Comments have been disabled for this content.