I\'ve got a simple WebApi method like this decorated with the OData queryable attribute.
[Queryable]
public virtual IQueryable Get()
IMHO the accepted solution is not correct. Generally speaking, if your service is using DTOs, you don't want to expose the underlying Entities (Person) to the service. Why would you query against the Person model and return PersonDTO objects?
Since you're already using it, Automapper has Queryable Extensions which allows you to expose only your DTOs and have the filtering applied to the underlying type at the data source. For example:
public IQueryable Get(ODataQueryOptions options) {
Mapper.CreateMap();
var persons = _personRepository.GetPersonsAsQueryable();
var personsDTOs = persons.Project().To(); // magic happens here...
return options.ApplyTo(personsDTOs);
}
Regarding eagerly loading navigation properties...
@philreed: I couldn't put a decent response in the comment so I added it here. There was a post on how to do this here but I'm getting 403s today. Hopefully that's temporary.
Basically, you examine the Select and Expand clauses for your navigation property. If it is present, then you tell EF to eagerly load via IQueryable extension method.
Controller
public IQueryable GetMyDtos(ODataQueryOptions options)
{
var eagerlyLoad = options.IsNavigationPropertyExpected(t => t.MyNavProperty);
var queryable = _myDtoService.GetMyDtos(eagerlyLoad);
// _myDtoService will eagerly load to prevent select N+1 problems
// return (eagerlyLoad) ? efResults.Include(t => t.MyNavProperty) : efResults;
return queryable;
}
Extension method
public static class ODataQueryOptionsExtensions
{
public static bool IsNavigationPropertyExpected(this ODataQueryOptions source, Expression> keySelector)
{
if (source == null) { throw new ArgumentNullException("source"); }
if (keySelector == null) { throw new ArgumentNullException("keySelector"); }
var returnValue = false;
var propertyName = (keySelector.Body as MemberExpression ?? ((UnaryExpression)keySelector.Body).Operand as MemberExpression).Member.Name;
var expandProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawExpand) ? new List().ToArray() : source.SelectExpand.RawExpand.Split(',');
var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List().ToArray() : source.SelectExpand.RawSelect.Split(',');
returnValue = returnValue ^ expandProperties.Contains(propertyName);
returnValue = returnValue ^ selectProperties.Contains(propertyName);
return returnValue;
}
}