Search code examples
c#linqentity-frameworkodata

Freeze a linq IQueryable (as a ToList().AsQueryable() would do)


Is there a way to freeze an IQueryable so that no additional joins will be added to the query when hitting the database? For example, I could do a .ToList() to freeze the query, but that has performance impacts because any filtering I do is on the middle layer and I don't have any performance gains from pre-filtering on the db server?


Edit for clarity:

I have an OData service which returns an IQueryable that the client can filter/sort/project as needed. I just want to prevent them from pulling more data out. I could do that by doing ToList().AsQueryable(), but that loses the advantage of lazyLoading, and with it, the whole purpose of allowing the client to filter the request.

One option that I looked at was to set: EnableQueryAttribute.AllowedQueryOptions to exclude Expand, however even if my initial Query had been expanded, the client is still prevented from selecting those parts.


Solution

  • Im assuming that you actually have an IQueryable<T> instead of a IQueryable.

    If you don't want your client having access to all IQueryable<T> methods, don't return an IQueryable<T>. Since you want them to be able to only filter/sort/project, create a object witch contains an IQueryable<T> but only expose the desired methods, and use that:

    public interface IDataResult<T>
    {
        IDataResult<T> FilterBy(Expression<Func<T, bool>> predicate);
    
        IDataResult<TResult> ProjectTo<TResult>(Expression<Func<T, TResult>> predicate);
    
        IDataResult<T> SortBy<TKey>(Expression<Func<T, TKey>> keySelector);
    
        IDataResult<T> SortByDescending<TKey>(Expression<Func<T, TKey>> keySelector);
    
        List<T> ToList();
    
        IEnumerable<T> AsEnumerable();
    }
    
    public class DataResult<T> : IDataResult<T>
    {
        private IQueryable<T> Query { get; set; }
    
        public DataResult(IQueryable<T> originalQuery)
        {
            this.Query = originalQuery;
        }
    
        public IDataResult<T> FilterBy(Expression<Func<T, bool>> predicate)
        {
            return new DataResult<T>(this.Query.Where(predicate));
        }
    
        public IDataResult<T> SortBy<TKey>(Expression<Func<T, TKey>> keySelector)
        {
            return new DataResult<T>(this.Query.OrderBy(keySelector));
        }
    
        public IDataResult<T> SortByDescending<TKey>(Expression<Func<T, TKey>> keySelector)
        {
            return new DataResult<T>(this.Query.OrderByDescending(keySelector));
        }
    
        public IDataResult<TResult> ProjectTo<TResult>(Expression<Func<T, TResult>> predicate)
        {
            return new DataResult<TResult>(this.Query.Select(predicate));
        }
    
        public List<T> ToList()
        {
            return this.Query.ToList();
        }
    
        public IEnumerable<T> AsEnumerable()
        {
            return this.Query.AsEnumerable();
        }
    } 
    

    That way you can also prevent EF and DB related dependencies creeping up on your application. Any change on IQueryable<T> methods will be contained within this class, instead of spread all over the place.