Since each IPersisted
type is going to have its own expression, I would make that a constraint on the part of IPersisted
. So now you have some kind of polymorphism.
Something like?
interface IPersisted<T> where T: IPersisted<T>
{
Expression<Func<T, bool>> Predicate { get; }
}
class Repo<T> where T : IPersisted<T>, new()
{
public IQueryable<T> Get()
{
var dummy = new T();
return _queryable.Where(dummy.Predicate);
}
}
class Deletable : IPersisted<Deletable>
{
public Deletable()
{
}
public Expression<Func<Deletable, bool>> Predicate
{
get { return x => !x.IsDeleted; }
}
bool IsDeleted { get; set; }
}
I think what you need here is some kind of static polymorphism, but since C# doesnt offer that, you might need create a dummy instance for yourself, just to get the expression.
If you can't have a default constructor, then you can rely on FormatterServices.GetUninitializedObject(t)
. You can adjust your Get
method like this:
public IQueryable<T> Get()
{
var dummy = (T)FormatterServices.GetUninitializedObject(typeof(T));
return _queryable.Where(dummy.Predicate);
}
Two things out of possibly many things to note about FormatterServices.GetUninitializedObject
:
It will not initialize anything or run the constructor, but that shouldn't be a problem for us.
It's relatively slower. Shouldn't be a big deal since you can cache the instances :) Something like:
class Repo<T> where T : IPersisted<T>
{
//caching mechanism: this is run only once per instance; you can make it
//static if this shud be run only once the entire lifetime of application
readonly T dummy = (T)FormatterServices.GetUninitializedObject(typeof(T));
public IQueryable<T> Get()
{
return _queryable.Where(dummy.Predicate);
}
}
If the exact expression is not important, then you can get rid of the object instantiation. Something like:
interface IPersisted<T> where T: IPersisted<T>
{
Func<T, bool> Predicate { get; }
}
class Repo<T> where T : IPersisted<T>
{
public IQueryable<T> Get()
{
return _queryable.Where(x => x.Predicate(x));
}
}
class Deletable : IPersisted<Deletable>
{
public Func<Deletable, bool> Predicate
{
get { return x => !x.IsDeleted; }
}
}
If preserving the original definition of IPersisted
is important, then you can make it non-generic. Not sure if that would make it any less strongly-typed.
interface IPersisted
{
Expression<Func<object, bool>> Predicate { get; }
}
class Repo<T> where T : IPersisted
{
public IQueryable<T> Get()
{
return _queryable.Where(dummy.Predicate);
}
}
class Deletable : IPersisted
{
public Expression<Func<object, bool>> Predicate
{
get { return x => !((Deletable)x).IsDeleted; }
}
}
The above approach can be made more strongly typed by going for a method in IPersisted
but need not be good enough a constraint:
interface IPersisted
{
Expression<Func<T, bool>> GetPredicate<T>() where T : IPersisted;
}
class Repo<T> where T : IPersisted
{
public IQueryable<T> Get()
{
return _queryable.Where(dummy.GetPredicate<T>());
}
}
class Deletable : IPersisted
{
Expression<Func<T, bool>> IPersisted.GetPredicate<T>() //made it explicit
{
return x => ((Deletable)(object)x).IsDeleted;
}
}
Note: Make the Predicate
implementation explicit if it doesn't make sense outside the Repo<T>
class.