I have a generic Repo
class that exposes a database LINQ provider:
class Repo<T> where T : IPersisted
{
IQueryable<T> Get()
{
return _queryable;
}
}
(IPersisted
is a simple marker interface for persisted objects).
Now... I would like to find an elegant way to inject a default LINQ expression for certain derived types of IPersisted
. For example, for the following IPersisted implementation:
class Deletable : IPersisted
{
bool IsDeleted { get; set; }
}
I want the IQueryable<T> Get()
method to return _queryable.Where(q => !q.IsDeleted)
, only when T
is of type Deletable
.
I thought about creating some type of dictionary map IDictionary<Type, Expression>
, and doing a lookup on typeof(T)
inside the Get
method but I'm not sure that I'll be able to strongly type the expressions in this case.
How can I inject a "default" LINQ expression into the Get
method, based on the type of T
? I'd like to have an extensible, object oriented way of mapping types to default expressions.
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.
Not sure if we're 100% on the same page but this get method example will take a linq Func which you can use as a where clause to filter your query.
class Repo<T> where T : IPersisted
{
IQueryable<T> Get(Func<T, bool> filter)
{
return _queryable.Where(filter);
}
}
Edit: Not sure if this will translate well to sql but to make sure i have the right idea of what you want (and maybe spark a light) : Have you considered something like this?
class Repo<T> where T : IPersisted
{
IQueryable<T> Get()
{
if (typeof (T).IsAssignableFrom(typeof (IDeletable)))
{
return _queryable.Where(o => ((IDeletable) o).Deleted = false).AsQueryable();
}
return _queryable;
}
}