📜 ⬆️ ⬇️

We unify the behavior of LINQ to IEnumerable and LINQ to IQueriable in terms of working with null values. Part two. Its implementation of IQueryProvider

In the comments to the first part, I was rightly made a remark that I promised to unify IEnumerable and IQueryable, and I hid them behind a samopisny repository-like interface. In this article I will try to fix it and give an example of what to do if we want to work with LINQ directly. For this, I will offer my own implementation of the IQueryProvider interface.

Github
Nuget


So, the first part ended with the fact that we wrote ExpressionVisitor, which added a wrapper checking for null to each node of the expression tree, which is an appeal to the property:
public class AddMaybeVisitor : ExpressionVisitor { //      ,   . public Expression<Func<T1, T2>> Modify<T1, T2>(Expression<Func<T1, T2>> expression) { return (Expression<Func<T1, T2>>)Visit(expression); } //      ,          ,   ,    . protected override Expression VisitMember(MemberExpression node) { var expression = Visit(node.Expression); var expressionType = expression.Type; var memberType = node.Type; var withMethodinfo = typeof(AddMaybeVisitor) .GetMethod("With") .MakeGenericMethod(expressionType, memberType); var p = Expression.Parameter(expressionType); var l = Expression.Lambda(Expression.MakeMemberAccess(p, node.Member), p); return Expression.Call(withMethodinfo, expression, Expression.Constant(l.Compile(), typeof(Func<,>).MakeGenericType(expressionType, memberType)) ); } public static TResult With<TSource, TResult>(TSource source, Func<TSource, TResult> action) where TSource : class { if (source != default(TSource)) return action(source); return default(TResult); } } 

')
Let us again have a pair of data sources providing access to the book collection. In this case, each book may be listed by the author, presented in the table of authors. One of the data sources is a database, and accordingly an even IQueryable is returned from it, the second is an in-memory cache that returns a List. Like the first time, we hide this interface, but now it will return IQueryable, and we will do all other transformations using standard LINQ methods.
 public interface IBookSource { IQueryable<Book> GetBooks(); } 


The standard approach to getting from IEnumerable (and, in particular, from List) IQueryable is to call the AsQueryable () extension method. However, this option does not suit us, because we need to perform our own manipulations with each used expression, namely, to wrap the property getter into a null test.
Therefore, we will write our own extension method:
  public static class QueryableExtensions { public static IQueryable<TElement> AsMaybeQueryable<TElement>(this IEnumerable<TElement> source) { if (source == null) throw new ArgumentNullException("source"); var elements = source as IQueryable<TElement>; //      AsQueryable() return elements ?? new MaybeEnumerableQuery<TElement>(source); } public static IQueryable AsMaybeQueryable(this IEnumerable source) { if (source == null) throw new ArgumentNullException("source"); var queryable = source as IQueryable; if (queryable != null) return queryable; var enumType = FindGenericType(typeof(IEnumerable<>), source.GetType()); if (enumType == null) throw new ArgumentException("Source is not generic","source"); //      AsQueryable() return MaybeEnumerableQuery.Create(enumType.GetGenericArguments()[0], source); } private static Type FindGenericType(Type definition, Type type) { while (type != null && type != typeof(object)) { if (type.IsGenericType && type.GetGenericTypeDefinition() == definition) return type; if (definition.IsInterface) { foreach (Type itype in type.GetInterfaces()) { Type found = FindGenericType(definition, itype); if (found != null) return found; } } type = type.BaseType; } return null; } } 


The difference is that instead of the standard EnumerableQuery class, we return our implementation of MaybeEnumerableQuery, which is a wrapper around EnumerableQuery:
  public class MaybeEnumerableQuery<T>: MaybeEnumerableQuery, IQueryProvider, IOrderedQueryable<T>, IQueryable<T>, IOrderedQueryable, IQueryable, IEnumerable<T>, IEnumerable { private EnumerableQuery<T> _innerQuery; public MaybeEnumerableQuery(IEnumerable<T> enumerable) { _innerQuery = new EnumerableQuery<T>(enumerable); } public MaybeEnumerableQuery(Expression expression) { _innerQuery = new EnumerableQuery<T>(RewriteExpression(expression)); } private Expression RewriteExpression(Expression expression) { var rewriter = new AddMaybeVisitor(); return rewriter.Visit(expression); } public IQueryable CreateQuery(Expression expression) { return ((IQueryProvider)_innerQuery).CreateQuery(RewriteExpression(expression)); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return ((IQueryProvider)_innerQuery).CreateQuery<TElement>(RewriteExpression(expression)); } public object Execute(Expression expression) { return ((IQueryProvider)_innerQuery).Execute(RewriteExpression(expression)); } public TResult Execute<TResult>(Expression expression) { return ((IQueryProvider)_innerQuery).Execute<TResult>(RewriteExpression(expression)); } ... //    ,         _innerQuery } 

In this case, each received expression we process using our ExpressionVisitor.

Usage example:
 IQueryable<Book> GetBooks() { List<Book> books = GetDataFromCache(); return books.AsMaybeQueryable(); } ... var names = GetBooks.Select(c=>c.Author.Name).ToArray(); 


Important note: The operation of rewriting the expression tree is not free. It is fast compared to the data collection from the database, but I would not recommend using this solution to work purely with IEnumerable.

Source: https://habr.com/ru/post/256403/


All Articles