Reputation: 1542
[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
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
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
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
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