NULL値の操作に関して、LINQ to IEnumerableとLINQ to IQueriableの動作を統一しましょう。 パート2 IQueryProviderの独自の実装

最初の部分へのコメントで私はIEnumerableとIQueryableの統一を約束したことを正しく発言し、私はそれらをリポジトリのような自己記述インターフェースの後ろに隠しました。 この記事では、LINQを直接操作したい場合の回復方法と例を示します。 このために、IQueryProviderインターフェイスの独自の実装を提供します。



Github

ヌジェ





したがって、最初の部分は、式ツリーの各ノードに追加されたExpressionVisitorを作成したという事実で終わりました。これは、nullをチェックするラッパープロパティの呼び出しです。

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); } }
      
      







再び、書籍のコレクションへのアクセスを提供する2つのデータソースがあるとします。 同時に、各本著者の表に示されている著者を示している場合があります。 データソースの1つはデータベースであり、それに応じてIQueryableさえもデータベースから返されます。2つ目はリストを返すインメモリキャッシュです。 初めて、このインターフェイスを非表示にしますが、今度はIQueryableを返し、標準のLINQメソッドを使用して他のすべての変換を実行します。

 public interface IBookSource { IQueryable<Book> GetBooks(); }
      
      







IEnumerable(特にList)からIQueryableを取得する標準的なアプローチは、AsQueryable()拡張メソッドを呼び出すことです。 ただし、このオプションは、使用される各式で独自の操作を行う必要があるため、つまり、プロパティゲッターをnullチェックでラップする必要があるため、私たちには適していません。

したがって、独自の拡張メソッドを作成します。

  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; } }
      
      







すべての違いは、標準のEnumerableQueryクラスの代わりに、EnumerableQueryのラッパーであるMaybeEnumerableQuery実装を返すことです。

  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 }
      
      





同時に、ExpressionVisitorを使用して、受け取った各式を処理します。



使用例:

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







重要な注意:式ツリーを書き換える操作は無料ではありません。 データベースからデータを収集するのに比べて高速ですが、このソリューションを使用して純粋にIEnumerableで作業することはお勧めしません。



All Articles