Help me with architectural question
Here is the simple snippet:
public interface IRepository<T>
{
T Get(int id);
}
public class Repository<T> : IRepository<T>开发者_开发问答; where T : class
{
private readonly IRepositoryContext _repositoryContext;
private IObjectSet<T> _objectSet;
public Repository(IRepositoryContext repositoryContext)
{
_repositoryContext = repositoryContext;
_objectSet = repositoryContext.GetObjectSet<T>();
}
public virtual T Get(int id)
{
return ObjectSet.First(x=> x.Id == id)
// that wouldn't work because there is no guarantee that T entity has Id property
}
Now, as you can see I can instantiate Repository for any entity object and use methods defined (although in the example we have only Get(). But I can't use any constrains in expressions, unless I create non-abstract classes for each entity of T
based on IRepository<T>
and then implement methods the way I want.
But what if I need to use a method like Get
which implementation stays the same for probably all entities (every entity has an Id).
How to do that?
I thought, I could create an abstract entity in EF data model designer, mapped to nothing, and mark that as the base type for all other entities, but It's asking for a mapping i.e. table. I tried to go with a complex type instead - that wouldn't let me inherit from it.
So, show me please the most efficient way to achieve that
That's not really the repository's role to know whether there's an ID or not in your object.
Most repositories implement the Single
method like this
public T Single( Expression<Func<T, bool>> predicate ) {
return ObjectSet.Single<T>( predicate );
}
The best way to do what you're asking would probably be to build a custom expression. Something like this:
public Expression<Func<T, bool>> GetIdEqualsExpression(int id)
{
var idProp = typeof(T).GetProperty("ID");
var param = Expression.Parameter(typeof(T), "t");
return Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.PropertyOrField(param, "ID"),
Expression.Constant(id)), param);
}
public virtual T Get(int id)
{
return ObjectSet.AsQueryable().Single(GetIdEqualsExpression(id));
}
That said, Bertrand makes a good point in stating that maybe it should be the responsibility of the calling code to provide the expression, so you don't risk runtime exceptions.
You can use an interface:
public interface IEntity { int Id { get; } }
then instead of "where T : class" you can do "where T : IEntity" and any object will have a retrievable Id.
If every entity has the Id property, you could create an interface which exposes the Id property make each entity implement the interface.
//Let's call the interface IIDInterface
public interface IIDInterface
{
int Id { get; }
}
// Now, suppose one of your entity classes is called: EntityOne
public class EntityOne : IIDInterface
{
// .. class constructors etc.
// the IIDInterface interface method implementation
public int Id
{
get
{
// getter implementation goes here
}
}
// .. other members of the class
}
Then you could cast to the interface in the implementation of Get and if the cast succeeds use the .Id property. The Get function becomes:
public virtual T Get(int id)
{
if(T is IIDInterface)
return ObjectSet.First(x => ((IIDInterface)x).Id == id)
return default(T);
}
EDIT
Another solution could be to use reflection and use the Id property of the T object if it has one. But doing that might be slower and I think the overhead is unnecessary unless you can't modify the entity objects to implement the IIDInterface interface (i.e. if you don't own them)
精彩评论