First of all, sorry for the question's title, it was hard to come up with a way to phrase it, let me explain the situation.
I am using the Specification pattern to perform db filtering with Entity Framework, and avoid doing it on memory (I roughly followed this article). My base specification class is something like this:
public abstract class Specification<T> : ISpecification<T>{
public abstract Expression<Func<T, bool>> FilterExpr();
public bool IsSatisfied(T entity)
{
Func<T, bool> func = this.FilterExpr().Compile();
return func(entity);
}
public Specification<T> And(Specification<T> otherSpec)
{
return new CombinedSpecification<T>(this, otherSpec);
}
}
From this base Specification class, multiple Strongly-typed specifications are derived, which work well on their own. However, the problem arises when trying to combine specifications of inherited types. For example, let's say I have the following models:
public abstract class Person
{
public int Age {get; set;}
public string Name {get; set;}
}
public class Baby:Person
{
public bool CanTalk {get; set;}
}
Now, I create the respective specifications to be able to filter the persons on the database:
public class NameSpec : Specification<Person>
{
private string name;
public Namespec(string aName)
{
this.name = aName;
}
public override Expression<Func<Person, bool>> FilterExpr()
{
return p => p.Name == this.name;
}
}
public class IsAbleToTalkSpec : Specification<Baby>
{
public override Expression<Func<Baby, bool>> FilterExpr()
{
return p => p.CanTalk == true;
}
}
So finally, let's say I want to filter for every baby named John who can talk:
var johnSpec = new NameSpec("John");
var combinedSpecification = johnSpec.And(new IsAbleToTalkSpec());
List<Baby> result = myRepository.Find(combinedSpecification);
Despite my models being properly binded to the DB via the EF configuration, doing this results in a compilation error, because there's no way a Specification<Baby>
can be converted to a Specification<Person>
when combining them, despite the mentioned inheritance. I understand why this happens, but I have no idea how to solve this without creating a NameSpec<Baby>
instead of reusing the NameSpec<Person>
, which scales horribly as my models grow. Additionaly, here is my CombinedSpecification<T>
class for reference:
internal class CombinedSpecification<T> : Specification<T>
{
private Specification<T> leftSpec;
private Specification<T> rightSpec;
public CombinedSpecification(Specification<T> aSpec, Specification<T> otherSpec)
{
this.leftSpec = aSpec;
this.rightSpec = otherSpec;
}
public override Expression<Func<T, bool>> FilterExpr()
{
var parameter = this.leftSpec.Parameters[0];
var combined = Expression.AndAlso(
leftSpec.Body,
rightSpec.Body.ReplaceParam(rightSpec.Parameters[0], parameter)
);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}
}
Thanks in advance for taking the time to read this lengthy rambling, I hope I was clear enough at describing my problem.