Reputation:
I have this extension method:
public static IQueryable<T> FilterByEmployee<T>(this IQueryable<T> source, EmployeeFilter filter)
where T : class, IFilterableByEmployee
{
if (!string.IsNullOrEmpty(filter.Gender))
source = source.Where(e => e.Employee.Gender == filter.Gender);
if (!string.IsNullOrEmpty(filter.NationalityID))
source = source.Where(e => e.Employee.NationalityID == filter.NationalityID);
// filter the group
if (filter.IncludeChildGroups)
{
var groups = Security.GetAllChildGroups(filter.GroupID);
source = source.Where(e => e.Employee.EmployeeGroupID.HasValue
&& groups.Contains(e.Employee.EmployeeGroupID.Value));
}
else
{
source = source.Where(e => e.Employee.EmployeeGroupID == filter.GroupID);
}
// filter status
if (filter.OnlyActiveEmployees)
source = source.Where(e => e.Employee.Status == "Active");
return source;
}
and another one, which is exactly the same, but it filters the Employees
context directly:
public static IQueryable<T> Filter<T>(this IQueryable<T> source, EmployeeFilter filter)
where T : Employee
{
if (!string.IsNullOrEmpty(filter.Gender))
source = source.Where(e => e.Gender == filter.Gender);
if (!string.IsNullOrEmpty(filter.NationalityID))
source = source.Where(e => e.NationalityID == filter.NationalityID);
// filter the group
if (filter.IncludeChildGroups)
{
var groups = Security.GetAllChildGroups(filter.GroupID);
source = source.Where(e => e.EmployeeGroupID.HasValue
&& groups.Contains(e.EmployeeGroupID.Value));
}
else
{
source = source.Where(e => e.EmployeeGroupID == filter.GroupID);
}
// filter status
if (filter.OnlyActiveEmployees)
source = source.Where(e => e.Status == "Active");
return source;
}
I hate the idea of having almost the same code twice, how can I combine these two methods into one? (if possible) or at least make it two methods but the filtering in one of them? the original code is much longer that's also one of the reasons.
Upvotes: 6
Views: 666
Reputation:
This should be possible with LINQKit:
public static IQueryable<T> Filter<T>(this IQueryable<T> source, Expression<Func<T, Employee>> employeeSelector, EmployeeFilter filter)
{
source = source.AsExpandable();
if (!string.IsNullOrEmpty(filter.Gender))
source = source.Where(e => employeeSelector.Compile().Invoke(e).Gender == filter.Gender);
if (!string.IsNullOrEmpty(filter.NationalityID))
source = source.Where(e => employeeSelector.Compile().Invoke(e).NationalityID == filter.NationalityID);
// filter the group
if (filter.IncludeChildGroups)
{
var groups = Security.GetAllChildGroups(filter.GroupID);
source = source.Where(e => employeeSelector.Compile().Invoke(e).EmployeeGroupID.HasValue
&& groups.Contains(employeeSelector.Compile().Invoke(e).EmployeeGroupID.Value));
}
else
{
source = source.Where(e => employeeSelector.Compile().Invoke(e).EmployeeGroupID == filter.GroupID);
}
// filter status
if (filter.OnlyActiveEmployees)
source = source.Where(e => employeeSelector.Compile().Invoke(e).Status == "Active");
return source;
}
source = source.AsExpandable();
creates a wrapper around the EF query that ensures that employeeSelector.Compile().Invoke(e)
gets translated appropriately and does not, despite how it looks, actually compile any expression tree, and EF should only see expressions that it actually supports.
You can use e => e
as the employee selector if you filter directly on the employees, or e => e.Employee
if you don't.
Upvotes: 1
Reputation: 6932
You could implement IFilterByEmployee
on Employee
directly and explicitly:
public class Employee : IFilterByEmployee
{
Employee IFilterByEmployee.Employee
{
get { return this; }
}
}
By implementing the interface explicitly, it essentially makes this a 'means to an end' kind of solution.
EDIT: This would likely not work with LinqToEf. You have the same problem with writing the SQL directly. The context of the query is critical here thus making it very difficult to abstract it in a way that LinqToEf could intelligently (or magically) interpret it correctly.
Upvotes: 0