开发者

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?


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();
    }
}


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


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜