Ronald
Ronald

Reputation: 1542

C# repository implementation, need advice on generic fix

[Edited: The entities below are generated by Entity-Framework]

I am trying to implement a generic repository. Below are some interfaces that define specialize traits.

namespace AnimeVoter.DataLayer.Repositories
{
    internal interface ICanCreate<TEntity>
    {
        void Create(TEntity entity);
    }
    internal interface ICanUpdate<TEntity>
    {
        bool Update(TEntity entity);
    }
    internal interface ICanDelete<TEntity>
    {
        bool Delete(TEntity entity);
    }
    internal interface ICanGetList<TEntity>
    {
        IEnumerable<TEntity> GetList();
    }
    internal interface ICanGetById<TEntity>
    {
        TEntity GetById(int id);
    }
}

Now I also have an abstract class that combines the traits like below.

namespace AnimeVoter.DataLayer.Repositories
{
    public abstract class CrudRepository<TEntity> : 
        ICanCreate<TEntity>,
        ICanUpdate<TEntity>,
        ICanDelete<TEntity>,
        ICanGetList<TEntity>,
        ICanGetById<TEntity>
    {
        public abstract void Create(TEntity entity);
        public abstract bool Update(TEntity entity);
        public abstract bool Delete(TEntity entity);
        public abstract IEnumerable<TEntity> GetList();
        public abstract TEntity GetById(int id);
    }
}

Then I have somewhere like 10-15 concrete classes that uses the abstraction above. I will show only two. I will also limit the discussion to the common method Create().

Below is for the User table in the database:

namespace AnimeVoter.DataLayer.Repositories.Impl
{
    public class UserRepository : CrudRepository<User>, IDisposable
    {
        DbEntities db = new DbEntities();

        public override void Create(User entity)
        {
            db.Users.AddObject(entity);
        }

        ...

And below is for the Title table in the database:

namespace AnimeVoter.DataLayer.Repositories.Impl
{
    public class TitleRepository : CrudRepository<Title>, IDisposable
    {
        DbEntities db = new DbEntities();

        public override void Create(Title entity)
        {
            db.Titles.AddObject(entity);
        }

        ...

So there's the problem! When I am adding a new record to the User table I do db.Users.AddObject(entity). And when adding to the Title table I do db.Titles.AddObject(entity).

I am now wondering how I can refactor this using generics so I can just do something like db<"TableName">.AddObject(entity) or anything to that effect so I can have just one implementation for all the tables instead of having many implementations for each one of them?

Upvotes: 4

Views: 1330

Answers (4)

John Landheer
John Landheer

Reputation: 4019

Just replace the db.Title with CreateObjectSet I do it something like this:

public class CrudRepository<TEntity> where TEntity : class
{
    DbEntities db = new DbEntities();

    public override void Create(TEntity entity)
    {
        db.CreateObjectSet<TEntity>().AddObject(entity);
    }
}

EDIT: Forgot the where...

Upvotes: 1

Ronald
Ronald

Reputation: 1542

I have found the answer here, http://geekswithblogs.net/seanfao/archive/2009/12/03/136680.aspx. This is very good because it eliminates having multiple repository objects for each table mapped by EF particularly for mundane operations like CRUD, which is exactly what I was looking for.

Upvotes: 0

Tom Brothers
Tom Brothers

Reputation: 6007

The main thing you need to do is create an ObjectSet for the entity and then perform your actions against that ObjectSet instance. Here is a complete example on how to create a generic repository that handles all the CRUD actions.

public class TitleRepository : CrudRepository<Title>
{
    public TitleRepository()
        : base(new DbEntities())
    {
    }
}

public abstract class CrudRepository<TEntity> :
    ICanCreate<TEntity>,
    ICanUpdate<TEntity>,
    ICanDelete<TEntity>,
    ICanGetList<TEntity>,
    ICanGetById<TEntity>
    where TEntity : EntityObject
{
    private readonly ObjectSet<TEntity> _objectSet;
    private readonly string _primaryKey;

    protected CrudRepository(ObjectContext context)
    {
        this._objectSet = context.CreateObjectSet<TEntity>();
        this._primaryKey = this.GetPrimaryKeyPropertyName();
    }

    public void Create(TEntity entity)
    {
        this._objectSet.AddObject(entity);
        this._objectSet.Context.SaveChanges();
    }

    public bool Update(TEntity entity)
    {
        if (entity.EntityState == EntityState.Detached)
        {
            this._objectSet.Attach(entity);
        }

        this._objectSet.Context.SaveChanges();
        return true;
    }

    public bool Delete(TEntity entity)
    {
        this._objectSet.DeleteObject(entity);
        this._objectSet.Context.SaveChanges();
        return true;
    }

    public IEnumerable<TEntity> GetList()
    {
        return this._objectSet.ToList();
    }

    public TEntity GetById(int id)
    {
        return this._objectSet.Where(this.CreateGetByIdExpression(id)).FirstOrDefault();
    }

    // Build an Expression that can be used to query an Entity by Id.
    private Expression<Func<TEntity, bool>> CreateGetByIdExpression(object id)
    {
        ParameterExpression e = Expression.Parameter(typeof(TEntity), "e");
        PropertyInfo pi = typeof(TEntity).GetProperty(this._primaryKey);
        MemberExpression m = Expression.MakeMemberAccess(e, pi);
        ConstantExpression c = Expression.Constant(id, id.GetType());
        BinaryExpression b = Expression.Equal(m, c);
        Expression<Func<TEntity, bool>> lambda = Expression.Lambda<Func<TEntity, bool>>(b, e);

        return lambda;
    }

    // Use the EF metadata to get the primary key property name.
    private string GetPrimaryKeyPropertyName()
    {
        return this._objectSet.Context
                              .MetadataWorkspace
                              .GetEntityContainer(this._objectSet.Context.DefaultContainerName, DataSpace.CSpace)
                              .BaseEntitySets
                              .First(meta => meta.ElementType == this._objectSet.EntitySet.ElementType)
                              .ElementType.KeyMembers
                              .Select(k => k.Name)
                              .FirstOrDefault();
    }
}

Upvotes: 2

sll
sll

Reputation: 62504

To accomplish this you have to define such mappings between entity type and db table. You can consider something like DbContractFactory and inject it in base CrudRepository<TEntity> class, so it will be able to retrieve Table reference/name in runtime based on current entity type TEntity like

dbContractFactory.GetDbContract<TEntity>()

In this way you can separate db-specifics from entities implementation itself by storing all relations in such factory/map.

EDIT: An example

interface IDbContract
{
   string TableName { get; }
}

public sealed class DbContractFactory
{
   private readonly IDictionary<Type, IDbContract> dbContractMap;

   public void RegisterContract<TEntity>(IDbContract)
   {
         // store in dbContractMap
   }

   public IDbContract GetDbContract<TEntity>()
   {  
       if (dbContractMap.Contains(typeof(TEntity))
       {
          // retrieve and return
       }    
   }
}

Upvotes: 1

Related Questions