I have a number of complex queries in a repository accesing a DB via Entity Framework / Linq-to-Entities. These queries, generally, are built up out of a number of non-trivial sub-queries. Generally speaking, the sub-queries are used across different repository methods and also in other domain logic. It makes sense for them to be external to, but accessible to, the repository layer.
As such I want to use the specification pattern to encapsulate some of these sub-queries.
I am using a base class for my specification classes:
public abstract class Specification<T> : ISpecification<T> where T : class
{
public abstract Expression<Func<T, bool>> ToExpression();
public virtual bool IsSatisfiedBy(T candidate)
{
var predicate = ToExpression().Compile();
return predicate(candidate);
}
public Specification<T> And(Specification<T> specification)
{
return new AndSpecification<T>(this, specification);
}
public Specification<T> Or(Specification<T> specification)
{
return new OrSpecification<T>(this, specification);
}
}
A sample specification might look something like this:
public class IsAssignmentSetForStudentSpecification : Specification<Assignment>
{
private readonly Student _student;
public IsAssignmentSetForStudentSpecification(Student student)
{
_student = student;
}
public override Expression<Func<Assignment, bool>> ToExpression()
{
return x => !x.Exclusions.Contains(_student) &&
(
_student.Classes.Select(c => c.Subject).Intersect(x.Subjects).Any() ||
x.TutorGroups.Contains(_student.TutorGroup) ||
x.Houses.Contains(_student.House) ||
x.YearGroups.Contains(_student.YearGroup) ||
x.Students.Contains(_student)
);
}
}
As you can see I would prefer not to be writing such code in each repository query.
As repository query method (using various specifications) might look like this:
public ICollection<Assignment> GetAssignmentsDueInForStudent(Student student, DateRange dateRange)
{
var isAssignmentAssignedToStudent = new IsAssignmentSetForStudentSpecification(student);
var isAssignmentDueInDateRange = new IsAssignmentDueInDateRangeSpecification(dateRange);
var hasStudentCompletedAssignment = new HasStudentCompletedAssignmentSpecification(student);
return (from a in Set
.Where(x => isAssignmentAssignedToStudent
.And(isAssignmentDueInDateRange).IsSatisfiedBy(x))
.Where(x => !hasStudentCompletedAssignment.IsSatisfiedBy(x))
select a)
.ToList(queryOptions);
}
In the above method, Set is an IDbSet<>
Unfortunately, when I run the query, I get the following error:
LINQ to Entities does not recognize the method 'Boolean IsSatisfiedBy(Beehive.Domain.Planner.Assignments.Assignment)' method, and this method cannot be translated into a store expression.
How can I get around this?