例:
ブックテーブルへのアクセスを提供するDbContextがあるとします。 同時に、各本は著者の表に示されている著者を示している場合があります。
したがって、コード内の著者の表現は次のようになります。
public class Book { public string Title{get;set;} public string ISBN{get;set;} public Author Author{get;set;} ... } public class Author { public string Name {get;set;} ... }
また、ローカルデータベースに加えて、Webサービスをデータソースとして使用し、外部からの情報を提供するとします。 アダプタでラップしたため、著者とまったく同じ本のコレクションが提供されますが、内部にはリストがあります。
統一のために、両方のデータソースをIBookSourceインターフェイスでラップします。
public interface IBookSource { T GetBookData<T>(string isbn, Expression<Func<Book, T>> selector) where T:class; }
このような一般的な実装により、常にデータベースからナビゲーションプロパティを持つBookエンティティ全体を取得するのではなく、要求によって必要な情報のみを受信できます。 DbContextに対するこのメソッドの実装は簡単です。
public T GetBookData<T>(string isbn, Expression<Func<Book, T>> selector) where T:class { return Set<Book>().Where(b=>b.ISBN == isbn).Select(selector).SingleOrDefault(); }
著者が次のように指定されていない本に対してこのメソッドを呼び出すとどうなりますか:
var authorName = GetBookData(isbn, b=>b.Author.Name);
authorName変数はnullになります。
次に、データソースが実際にメモリ内のコレクションである場合(データプロバイダーにサービスを要求することで受け取ったブックリストを一覧表示する)に、同じメソッドを実装してみましょう。
public T GetBookData<T>(string isbn, Expression<Func<Book, T>> selector) where T:class { List<Book> books = GetDataFromService(); return books.Where(b=>b.ISBN == isbn).Select(selector.Compile()).SingleOrDefault(); }
ブックはIEnumerableであり、IQueriableではないため、Selectメソッドに渡す前に式をコンパイルする必要があることに注意してください。
著者が次のように指定されていない本のメソッドを再度呼び出すとどうなりますか?
var authorName = GetBookData(isbn, b=>b.Author.Name);
NullReferenceExceptionが発生します。 疑問が生じます。何をすべきか、IBookSourceインターフェイスの背後にあるものに関係なく、データを統一して統一したいのです。 したがって、同じ動作へのクラスプロパティを使用したアクセスの問題では、IQueriableとIEnumerableの動作を減らす必要があります。 この場合、IQueriableの動作がより好ましいため、IEnumerableで実行されるExpressionを同じ動作に変換します。
これを行うには、式の各PropertyGetterが、PropertyGetterがnullと呼ばれる値が等しいかどうかを確認する必要があります。等しい場合、PropertyGetterを呼び出す代わりにnullが返されます。 この動作を実装するメソッドは次のとおりです。
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); }
ただし、この動作を既に準備されている式(式)に追加する方法は?
ExpressionVisitorクラスが助けになります。 これは、式ツリー(ExpressionTree)のすべてのノードを通過し、必要に応じて変更できる特別なクラスです。
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) { Visit(node.Expression); var expressionType = node.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, node.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); } }
このExpressionVisitorの使用方法は? 非常にシンプル:
public T GetBookData<T>(string isbn, Expression<Func<Book, T>> selector) where T:class { List<Book> books = GetDataFromService(); var modifiedSelector = new AddMaybeVisitor().Modify(selector); return books.Where(b=>b.ISBN == isbn).Select(modifiedSelector.Compile()).SingleOrDefault(); }
ここで、著者が指定されていない本から著者の名前を要求すると、DbContextを介してデータベースを照会する場合とまったく同じように、NullReferenceExceptionではなくnullを取得します。