NHibernateへのクライアント側Linq

ほとんどすべての.NET開発者は、何らかの形でLinqテクノロジーを実際に使用しています。 Linqを使用すると、美しく簡潔なコードを記述して、データソースからオブジェクトを受け取ることができます。また、要求されたオブジェクトをオンザフライで受信および/または変換する基準を決定できます。 Linqサポートは、NHibernateを含むほとんどすべての一般的なORMフレームワークに存在します。 NHibernateはLinqプロバイダーを提供します。これを使用して、設計段階(設計時)で要求を作成できますが、実行時に要求を作成するには、Reflectionをいじる必要があります。 ただし、サービスのクライアント部分などの外部プロセスでリクエストを形成する必要がある場合、この場合、Reflectionは保存せず、クライアント部分は原則としてサーバーORMを認識しません(そして、何も知らないはずです)。

以下では、クエリが1つのプロセスで書き込まれ、別のプロセスで実行される状況で、LinqクエリをNHibernateに書き込むためのAPIを作成する方法を見ていきます。 また、ソースアプリケーションから実行中のアプリケーションにクエリをブロードキャストする独自のIQueryProviderを実装します。



内容



1. IEnumerableとIQueryable

2. NHibernateのLinq

3. ISessionオブジェクトなしのLinqクエリ

4.外部プロセスからNHibernateを介したデータベースへのLinqクエリ

5.テストアプリケーションを作成します

おわりに

参照資料



1. IEnumerableとIQueryable



まず、IEnumerableインターフェイスとIQueryableインターフェイスを簡単に思い出してください。 彼らはここここについて書かれて ました 。 また、 式ツリーとその仕組みについて読むことも役立ちます。

IEnumerable




IEnumerableリクエストの実行方法:

1.データソースはIEnumerable(列挙型)として表されます。コレクションの場合、これはオプションのアクションです。

2.列挙されたデータソースは、WhereListIteratorイテレーターによってラップ(装飾)されます。

3.最初のWhereListIteratorイテレーターは、次のWhereListIteratorイテレーターで装飾されます

4.リストコンストラクターでは、「トップレベル」のWhereListIteratorが渡されます。 大まかな概算では、WhereListIteratorが受信したforeachループを介したループを介して、内側のListコンテナーがいっぱいになると言えます。 次の要素を要求するとき、「トップレベル」デコレータはデコレータのチェーン全体を呼び出します。デコレータの各要素は、押し上げる要素とスキップする要素を決定します

擬似コード
// List ctor public List<T>(IEnumerable<T> source) { //  IEnumerator  WhereListIterator. var enumerator = source.GetEnumerator(); //        WhereListIterator'        while(enumerator.MoveNext()) { items.Add(enumerator.Current) } }
      
      







クエリ可能




コレクションの例を使用してIQueryableクエリを実行する方法:

1.データソースはEnumerableQueryableでラップされます(「A」と呼びます)。その内部で、ConstantExpression式がソースオブジェクトへのリンクで作成されます(IQueryProviderも作成されます。IEnumerableの場合、元のコレクションを参照します)。

2.オブジェクト「A」は新しいEnumerableQueryableオブジェクトで装飾され(「B」と呼びましょう)、ExpressionプロパティはオブジェクトAから取得されます。オブジェクトAは式MethodCallExpressionで装飾されます。 新しいオブジェクトの要求プロバイダーは、オブジェクトAから要求プロバイダーを設定します。オブジェクトAは不要になりました。

3.前のステップで取得した、式MemberExpressionを持つオブジェクトBは新しいオブジェクトEnumerableQueryableで装飾されます(「C」と呼びましょう)。式ExpressionのプロパティはオブジェクトBから取得され、式MethodCallExpressionで装飾されます。 新しいオブジェクトの要求プロバイダーは、オブジェクトBから要求プロバイダーを設定します。オブジェクトBは不要になりました。

4.クエリ結果にアクセスする段階で、メインアクションが実行されます。

IQueryableインターフェイスはIEnumerableの子孫です。そのため、IQueryable型のオブジェクトをListコンストラクターに渡すこともできます。foreachループを実行する前に(再び大まかな概算)、GetEnumeratorメソッドは転送されたIQueryableオブジェクトで呼び出されます。 GetEnumerator()の呼び出し中に、要求プロバイダーは結果のMethodCallExpressionをコンパイルし、IEnumerableの場合のように、要求時に次の要素を返すデコレーターメソッドのチェーンを返します。

擬似コード
 public List<T>(IQueryable<T> source) { //  IEnumerator  WhereListIterator. var enumerator = source.GetEnumerator(); //        WhereListIterator'        while(enumerator.MoveNext()) { items.Add(enumerator.Current) } } public class EnumerableQueryable<T> : IQueryable<T> { private Expression expression; private IEnumerable enumerableResult; public IEnumerator GetEnumerator() { if (enumerableResult == null) enumerableResult = expression.Compile().Invoke(); return enumerableResult.GetEnumerator(); } }
      
      







ここで、IEnumerableとIQueryableの違いについて説明します



式ツリーの利点は、本質的にILコードを生成するための高レベルAPIであり、実行時にビルド/編集し、デリゲートにコンパイルして呼び出すことができることです。



2. NHibernateのLinq



NHibernateの典型的なLinqスクリプトは次のようになります。

 var activeMasterEntities = session //  NhQueryable<T>,  ConstantExpression ,        . .Query<Entity>() //  IQueryable,  MethodCallExpression ,   ConstantExpression. .Where(e => e.IsMaster == true) //  IQueryable,  MethodCallExpression ,    MethodCallExpression. .Where(e => e.IsActive == true) //    .ToList()
      
      





session.Query()を呼び出すと、NhQueryable型のオブジェクトが返されます。 元のNhQueryableオブジェクトから式を装飾するWhere条件をさらにまとめます。 最初の要求は、収集要求とまったく同じ方法でラップされます。 違いは、ToList()を呼び出した瞬間から始まります。

GetEnumerator()メソッドを呼び出した時点で、構築された式ツリーはコンパイルされず、SQLクエリに変換されます。 Remotion.Linqライブラリは、LinqクエリのSQLへの変換メカニズムを担当し、NHibernateから受け取った式ツリーを解析します。 解析段階では、ツリーのノードで閉包が計算されます。次に例を示します。

 int stateCoefficient = 0.9; int ageLimitInCurrentState = 18 * stateCoefficient; var availableMovies = session .Query<Movie>() .Where(m => m.AgeLimit >= ageLimitInCurrentState) .ToList()
      
      





ラムダ式m => m.AgeLimit> = ageLimitは、ローカル変数ageLimitにクロージャーを作成します。 このラムダ式を解析するとき、m.AgeLimit> = ageLimitInCurrentStateという形式の式のツリーは式m.AgeLimit> = 16に計算され、この形式ではすでに式がLinq2Sqlトランスレーターに与えられます。

LinqをSQLに変換




IQueryableおよびIQueryProvider



IQueryableおよびIQueryProviderインターフェイスを 詳しく見てみましょう。

IQueryableインターフェイスには、現在のデコレーター式(データソースのデコーダーまたは別の式のデコレーター)を提供するExpressionプロパティと、現在のリクエストプロバイダーを取得できるIQueryProvider型のProviderプロパティがあります。

IQueryProviderインターフェイスは、下位レベルの式を修飾するCreateQuery(式)メソッドと、データソースを照会するExecute(式)を提供します。

すべてのLinqメソッドは、2つのグループに分類できます。



session.Query()メソッド呼び出しは、NhQueryable型のオブジェクトを返します。このオブジェクトのProviderプロパティは、INhQueryProvider型のオブジェクトによって示されます。

NhQueryableの非常に単純化された実装は次のとおりです

Nhqueryable
 public class NhQueryable<T> : QueryableBase<T> { public NhQueryable(ISessionImplementor session) { //     INhQueryProvider Provider = QueryProviderFactory.CreateQueryProvider(session); //     . Expression = Expression.Constant(this); } }
      
      







NhQueryableオブジェクトをSystem.Linq.Queryableクラスのメソッド(Where、Select、Skip、Takeなど)でさらにラップすると、NhQueryableオブジェクトで作成された同じデータプロバイダーが使用されます。





3. ISessionオブジェクトなしのLinqクエリ



セッションからクエリをバインド解除するには、それぞれ式ツリーのルートにあるSession.Query()およびNhQueryableの呼び出しを取り除く必要があります。 linqを使用できるようにするには、IQueryableオブジェクトを返すスタブソースが必要です。

定義してください:

RemoteQueryable
 public class RemoteQueryable<T> : IQueryable<T> { public Expression Expression { get; set; } public Type ElementType { get; set; } public IQueryProvider Provider { get; set; } public RemoteQueryable() { Expression = Expression.Constant(this); } }
      
      







次のように書くことができます:

 var query = new RemoteQueryable<Entity>().Where(e => e.IsMaster);
      
      





使いやすくするために、リクエストの作成をリポジトリクラスにまとめます。

RemoteRepository
 public static class RemoteRepository { public static IQueryable<TResult> CreateQuery<TResult>(IChannelProvider provider) { return new RemoteQueryable<TResult>(provider); } }
      
      







今、このような何かを書くことによって

 var query = RemoteRepository.CreateQuery<Entity>() .Where(e => e.IsMaster) .Where(e => e.IsActive);
      
      





クエリオブジェクトのExpressionプロパティにアクセスすると、次の式ツリーが取得されます。



式ツリーはランタイムで編集できるため、結果のツリーをバイパスし、MockDataSourceをNhQueryableに置き換えることができると上記で書きました。 NhQueryableは、session.Query()を呼び出すことで取得できます。

ツリーを横断するために、Visitorパターンを使用します。 式ツリーのビジターを「ゼロから」記述しないために、 この基本的な実装を使用します 。 相続人では、VisitConstantメソッドとVisitMethodCallメソッドを再定義し、式を編集するためのアクセスポイントとなるVisitメソッドも再定義します。

NhibernateExpressionVisitor
 public class NhibernateExpressionVisitor : ExpressionVisitor { protected IQueryable queryableRoot; public new Expression Visit(Expression sourceExpression, IQueryable queryableRoot) { this.queryableRoot= queryableRoot; return Visit(sourceExpression); } protected override Expression VisitMethodCall(MethodCallExpression m) { var query = m; var constantArgument = query.Arguments.FirstOrDefault(e => e is ConstantExpression && e.Type.IsGenericType && e.Type.GetGenericTypeDefinition() == typeof(EnumerableQuery<>)); if (constantArgument != null) { var constantArgumentPosition = query.Arguments.IndexOf(constantArgument); var newArguments = new Expression[query.Arguments.Count]; for (int index = 0; index < newArguments.Length; index++) { if (index != constantArgumentPosition) newArguments[index] = query.Arguments[index]; else newArguments[index] = queryableRoot.Expression; } return Expression.Call(query.Object, query.Method, newArguments); } return base.VisitMethodCall(query); } protected override Expression VisitConstant(ConstantExpression c) { if (c.Type.IsGenericType && typeof(RemoteQueryable<>).IsAssignableFrom(c.Type.GetGenericTypeDefinition())) return queryableRoot.Expression; return c; } }
      
      







使い方:

 var query = RemoteRepository.CreateQuery<Entity>() .Where(e => e.IsMaster) .Where(e => e.IsActive); using (var session = CreateSession()) { var nhQueryable = session.Query<Entity>(); var nhQueryableWithExternalQuery = new NhibernateExpressionVisitor().Visit(query.Expression, nhQueryable); var result = nhQueryable.Provider.Execute(nhQueryableWithExternalQuery); }
      
      





新しい行NhibernateExpressionVisitor()。Visit(query.Expression、nhQueryable)で、RemoteQueryableスタブがNhQueryableに変更されました。



このような式を収集するコードが動的にNHibernateと同じプロセスにある場合、そのようなジェスチャー自体は合理的ではありません。次に、linq要求を生成するコードを偽のソースとともに外部プロセスに転送する方法を検討します。





4.外部プロセスからNHibernateを介したデータベースへのLinqクエリ



リクエストの送信元のプロセスを「クライアント」、リクエストを実行するプロセスを「サーバー」と呼びます



クライアント部



linqを完全に実装するには、偽のスタブを提供するクライアント要求プロバイダーを定義する必要があります。 ただし、最初に、サーバープロセスにアクセスできるインターフェイスを定義します。

 public interface IChannelProvider { T SendRequest<T>(string request); }
      
      





クエリ結果(オブジェクトまたはオブジェクトのコレクション)をシリアル化するプロセスの責任は、トランスポートレベル、より具体的にはIChannelProviderの実装に割り当てられます。 次に、サーバープロセスデータプロバイダー(IChannelProvider)をこれらのクラスに渡すことができるように、RemoteQueryableコンストラクターとRemoteRepositoryクラスを改良する必要があります。

 public RemoteQueryable(IChannelProvider channelProvider) { Expression = Expression.Constant(this); Provider = new RemoteQueryableProvider<T>(channelProvider); } public static IQueryable<TResult> CreateQuery<TResult>(IChannelProvider provider) { return new RemoteQueryable<TResult>(provider); }
      
      





要求の送信プロセスを簡素化するために、文字列としてトランスポート層に渡します。 次に、IQueryProviderインターフェイスを使用してクライアントクエリプロバイダー(RemoteQueryableProvider)と、サーバーに要求を送信するためのDTOクラス(QueryDto)を定義します。

RemoteQueryProvider
 public class RemoteQueryProvider : IQueryProvider { public IQueryable CreateQuery(Expression expression) { var enumerableQuery = new EnumerableQuery<T>(expression); var resultQueryable = ((IQueryProvider)enumerableQuery).CreateQuery(expression); return new RemoteQueryable<T>(this, resultQueryable.Expression); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { var enumerableQuery = new EnumerableQuery<TElement>(expression); var resultQueryable = ((IQueryProvider)enumerableQuery).CreateQuery<TElement>(expression); return new RemoteQueryable<TElement>(this, resultQueryable.Expression); } public object Execute(Expression expression) { var serializedQuery = SerializeQuery(expression); return channelProvider.SendRequest<object>(serializedQuery); } public TResult Execute<TResult>(Expression expression) { var serializedQuery = SerializeQuery(expression); return this.channelProvider.SendRequest<TResult>(serializedQuery); } public RemoteQueryableProvider(IChannelProvider channelProvider) { this.channelProvider = channelProvider; } private static string SerializeQuery(Expression expression) { var newQueryDto = QueryDto.CreateMessage(expression, typeof(T)); var serializedQuery = JsonConvert.SerializeObject(newQueryDto, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, TypeNameHandling = TypeNameHandling.All }); return serializedQuery; } }
      
      







問い合わせ先
 public class QueryDto { public ExpressionNode SerializedExpression { get; set; } public string RequestedTypeName { get; set; } public string RequestedTypeAssemblyName { get; set; } public static QueryDtoCreateMessage(Expression expression, Type type) { var serializedExpression = expression.ToExpressionNode(); return new QueryDto(serializedExpression, type.FullName, type.Assembly.FullName); } private QueryDto(ExpressionNode serializedExpression, string requestedTypeName, string requestedTypeAssemblyName) { this.SerializedExpression = serializedExpression; this.RequestedTypeName = requestedTypeName; this.RequestedTypeAssemblyName = requestedTypeAssemblyName; } protected QueryDto() { } }
      
      







ファクトリメソッドQueryDto.CreateMessage()は、 Serialize.Linqライブラリを使用して、結果の式をシリアル化します。 QueryDtoクラスには、RequestedTypeNameプロパティとRequestedTypeAssemblyNameプロパティも含まれており、要求されたエンティティのタイプを識別し、式をサーバーに正しく復元します。 QueryDtoクラス自体のオブジェクトは、 Json.NETライブラリを使用して文字列にシリアル化されます。 シリアル化された要求は、プロキシオブジェクトIChannelProviderを介してサーバーに送信されます。



悪魔の詳細



1.式でクロージャを処理します。

NhibernateLinqセクションで、式でクロージャーを計算するプロセスについて誤って言及しました。 リクエストの形成は外部プロセスで行われるため、リクエストを送信する前に、構築された式のクロージャーの値を計算する必要があります。 (今のところ)述語のクロージャのみに興味があります。例えば

擬似コード
 RemoteRepository.Query<WorkItem>() .Where(w => w.Priority == EnvironmentSettings.MaxPriority)) //   EnvironmentSettings
      
      







リンクの値を計算し、値をConstantExpressionに変換するために、式を変更するクライアントExpressionVisitorを作成します。

ClientExpressionVisitor
 internal class ClientExpressionVisitor : ExpressionVisitor { public Expression Evaluate(Expression expression) { return base.Visit(expression); } private Expression EvaluateIfNeed(Expression expression) { var memberExpression = expression as MemberExpression; if (memberExpression != null) { if (memberExpression.Expression is ParameterExpression) return expression; var rightValue = GetValue(memberExpression); return Expression.Constant(rightValue); } var methodCallExpression = expression as MethodCallExpression; if (methodCallExpression != null) { var obj = ((ConstantExpression)methodCallExpression.Object).Value; var result = methodCallExpression.Method.Invoke(obj, methodCallExpression.Arguments.Select(ResolveArgument).ToArray()); return Expression.Constant(result); } return expression; } protected override Expression VisitBinary(BinaryExpression b) { Expression left = this.EvaluateIfNeed(this.Visit(b.Left)); Expression right = this.EvaluateIfNeed(this.Visit(b.Right)); Expression conversion = this.Visit(b.Conversion); if (left != b.Left || right != b.Right || conversion != b.Conversion) { if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null) return Expression.Coalesce(left, right, conversion as LambdaExpression); else return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); } return b; } private static object ResolveArgument(Expression exp) { var constantExp = exp as ConstantExpression; if (constantExp != null) return constantExp.Value; var memberExp = exp as MemberExpression; if (memberExp != null) return GetValue(memberExp); return null; } private static object GetValue(MemberExpression exp) { var constantExpression = exp.Expression as ConstantExpression; if (constantExpression != null) { var member = constantExpression.Value .GetType() .GetMember(exp.Member.Name) .First(); var fieldInfo = member as FieldInfo; if (fieldInfo != null) return fieldInfo.GetValue(constantExpression.Value); var propertyInfo = member as PropertyInfo; if (propertyInfo != null) return propertyInfo.GetValue(constantExpression.Value); } var expression = exp.Expression as MemberExpression; if (expression != null) return GetValue(expression); return null; } }
      
      







ClientExpressionVisitorの機能を考慮して、RemoteQueryableProviderクラスのExecuteメソッドを変更します。

RemoteQueryableProviderメソッド
 public object Execute(Expression expression) { var partialEvaluatedExpression = this.expressionEvaluator.Evaluate(expression); var serializedQuery = SerializeQuery(partialEvaluatedExpression); return channelProvider.SendRequest<object>(serializedQuery); } public TResult Execute<TResult>(Expression expression) { var partialEvaluatedExpression = this.expressionEvaluator.Evaluate(expression); var serializedQuery = SerializeQuery(partialEvaluatedExpression); return this.channelProvider.SendRequest<TResult>(serializedQuery); }
      
      









2.マッピングで指定されていないクエリでのエンティティプロパティの使用。

NHibernateでWhere(x => x.UnmappedProperty == 4))の形式の条件を記述した場合、NHibernateリクエストバリデーターはそのような式を見逃しません。 この問題を解決するために、APIリクエストポスト、つまり sql-samplingの結果として既に取得されているデータへのクエリ。

ポストクエリ可能
  internal class PostQueryable<T> : BaseQueryable<T> { public PostQueryable(IChannelProvider channelProvider) : base(channelProvider) { } public PostQueryable(AbstractQueryProvider provider, Expression expression) : base(provider, expression) { } public PostQueryable() { Expression = Expression.Constant(this); } }
      
      







便利なリクエストラッピングのための拡張機能

 public static class Ex { public static IQueryable<T> PostQuery<T>(this IQueryable<T> sourceQuery) { var query = Expression .Call(null, typeof (PostQueryable<T>).GetMethod(nameof(PostQueryable<T>.WrapQuery)), new [] {sourceQuery.Expression}); return sourceQuery.Provider.CreateQuery<T>(query); } }
      
      







これで、次の形式のクエリを作成できます。

擬似コード
 int stateCoefficient = 0.9; int ageLimitInCurrentState = 18 * stateCoefficient; var availableMovies = session .Query<Movie>() .Where(m => m.AgeLimit >= ageLimitInCurrentState) .PostQuery() .Where(m => m.RatingInCurrentState > 8) // unmapped- RatingInCurrentState .ToList()
      
      







サーバーに送信します。



サーバー側



サーバーとクライアントの両方のコードは同じアセンブリにある必要がありますが、コードにはNHibernateアセンブリからの型への参照が含まれています。 クライアントの過剰な依存関係を解く必要があります。 Nhibernateタイプを操作してNhibernate.dllアセンブリへのハードリンクを削除するためのヘルパーを作成します。 サーバー側のアセンブリNHibernate.dllは、Reflectionを介してロードされます。

NHibernateTypesHelper
 internal static class NHibernateTypesHelper { private static readonly Assembly nhibernateAssembly; public static Type SessionType { get; private set; } public static Type LinqExtensionType { get; private set; } public static bool IsSessionObject(object inspectedObject) { return SessionType.IsInstanceOfType(inspectedObject); } static NHibernateTypesHelper() { nhibernateAssembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(asm => asm.FullName.Contains("NHibernate")) ?? Assembly.Load("NHibernate"); if (nhibernateAssembly == null) throw new InvalidOperationException("Caller invoking server-side types, but the NHibernate.dll not found in current application domain"); SessionType = nhibernateAssembly.GetTypes() .Single(p => p.FullName.Equals("NHibernate.ISession", StringComparison.OrdinalIgnoreCase)); LinqExtensionType = nhibernateAssembly.GetTypes() Single(p => p.FullName.Equals("NHibernate.Linq.LinqExtensionMethods", StringComparison.OrdinalIgnoreCase)); } }
      
      







サーバー側でRemoteQueryExecutorクラスを定義します。このクラスは、シリアル化されたQueryDtoを受け入れ、復元して実行します。

RemoteQueryExecutor
 public static class RemoteQueryExecutor { public static object Do(string serializedQueryDto, object sessionObject) { var internalRemoteQuery = DeserializeQueryDto(serializedQueryDto); var deserializedQuery = DeserializedQueryExpressionAndValidate(internalRemoteQuery); var targetType = ResolveType(internalRemoteQuery); return Execute(deserializedQuery, targetType, sessionObject); } private static TypeInfo ResolveType(QueryDto internalRemoteQuery) { var targetAssemblyName = internalRemoteQuery.RequestedTypeAssemblyName; var targetAssembly = GetAssemblyOrThrownEx(internalRemoteQuery, targetAssemblyName); var targetType = GetTypeFromAssemblyOrThrownEx(targetAssembly, internalRemoteQuery.RequestedTypeName, targetAssemblyName); return targetType; } private static Expression DeserializedQueryExpression(QueryDto internalRemoteQuery) { var deserializedQuery = internalRemoteQuery.SerializedExpression.ToExpression(); return deserializedQuery; } private static TypeInfo GetTypeFromAssemblyOrThrownEx(Assembly targetAssembly, string requestedTypeName, string targetAssemblyName) { var targetType = targetAssembly.DefinedTypes .FirstOrDefault(type => type.FullName.Equals(requestedTypeName, StringComparison.OrdinalIgnoreCase)); if (targetType == null) throw new InvalidOperationException(string.Format("Type with name '{0}' not found in assembly '{1}'", requestedTypeName, targetAssemblyName)); return targetType; } private static Assembly GetAssemblyOrThrownEx(QueryDto internalRemoteQuery, string targetAssemblyName) { var targetAssembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(asm => asm.FullName.Equals(internalRemoteQuery.RequestedTypeAssemblyName, StringComparison.OrdinalIgnoreCase)); if (targetAssembly == null) throw new InvalidOperationException(string.Format("Assembly with name '{0}' not found in server app domain", targetAssemblyName)); return targetAssembly; } private static QueryDto DeserializeQueryDto(string serializedQueryDto) { var internalRemoteQuery = JsonConvert .DeserializeObject<QueryDto>(serializedQueryDto, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, TypeNameHandling = TypeNameHandling.All }); return internalRemoteQuery; } private static object Execute(Expression expression, Type targetType, object sessionObject) { var queryable = GetNhQueryableFromSession(targetType, sessionObject); var nhibernatePartialExpression = ExpressionModifier.GetNhibernatePartialExpression(expression, queryable); var resultFromStorage = queryable.Provider.Execute(nhibernatePartialExpression); var requestedCollection = resultFromStorage as IEnumerable<object>; if (requestedCollection == null) return resultFromStorage; var resultCollectionType = requestedCollection.GetType(); if (resultCollectionType.IsGenericType) targetType = resultCollectionType.GetGenericArguments().Single(); var enumerableQueryable = (IQueryable)Activator .CreateInstance(typeof(EnumerableQuery<>).MakeGenericType(targetType), new[] { requestedCollection }); var postQueryPartialExpression = ExpressionModifier .GetPostQueryPartialExpression(expression, enumerableQueryable); if (postQueryPartialExpression == null) return resultFromStorage; return enumerableQueryable.Provider.Execute(postQueryPartialExpression); } private static IQueryable GetNhQueryableFromSession(Type targetType, object sessionObject) { var finalQueryMethod = ResolveQueryMethod(targetType); var queryable = (IQueryable) finalQueryMethod.Invoke(null, new object[] {sessionObject}); return queryable; } private static MethodInfo ResolveQueryMethod(Type targetType) { var queryMethod = typeof(LinqExtensionMethods).GetMethods(BindingFlags.Public | BindingFlags.Static) .Where(m => m.IsGenericMethod) .Where(m => m.Name.Equals("Query")) .Single(m => m.GetParameters().Length == 1 && NHibernateTypesHelper.SessionType.IsAssignableFrom(m.GetParameters().First().ParameterType)); var finalQueryMethod = queryMethod.MakeGenericMethod(targetType); return finalQueryMethod; } }
      
      







リクエストを受信すると、Json.NETデシリアライザーを使用してサーバーにQueryDtoを復元します。 DTOオブジェクト内のシリアル化された式は、 Serialize.Linqライブラリを使用して復元されます。 次に、NhibernateExpressionVisitorビジターを使用して式を変更します。上記で説明したように、偽のルートをNhQueryableに置き換えます。

結果の式は2つのクエリに分割されます。



データベースへのクエリは、既知のスキームに従ってコンパイルせずに機能します。選択結果へのクエリがコンパイルされ、式で指定されたメソッドを使用してオブジェクトが処理されます。前のクエリの結果に対するクエリは、通常のEnumerableQueryとしてコンパイルおよび実行されます。

図面のクエリ






5.テストアプリケーションを作成します



テストクライアントサーバーアプリケーションを実装します。クライアント部分では、WPFテクノロジを使用します。サーバー側では、データベースとしてSQLiteを使用し、プロセス間の通信にHTTPバインディングを使用したWCFを使用します。オブジェクトモデルとして、WorkItemクラスを使用します

 [DataContract] public class WorkItem : BaseEntity { [DataMember] public virtual string Text { get; set; } [DataMember] public virtual int Priority { get; set; } }
      
      





NHibernateマッピング設定とnhibernate.cfg.xml構成をデータ転送のWCF設定と同様に残し、ListViewに200個のWorkItemオブジェクトを表示する目標を設定し、必要に応じてデータベースからデータをロードします。

リストのWPFは、UIレイヤーでのデータ仮想化のメカニズムを提供します。これは、ViewModelデータコレクションのレベルでの仮想化についても改善できます。この記事の例を基にして、データベースからクライアントへのページングデータの読み込みの例を変更します。

IItemsProviderを実装し、例の実装をクラスDemoWorkItemProviderに置き換えます。FetchCount()およびFetchRange()メソッドでは、RemoteQueryable APIを使用してLinqクエリを使用します。FetchRangeメソッドでは、表示に必要なデータ範囲に対してのみリクエストを指定します。

DemoWorkItemProvider
 public class DemoWorkItemProvider : IItemsProvider<WorkItem> { public int FetchCount() { return RemoteRepository.CreateQuery<WorkItem>(new DemoChannelProvider()) .Count(); } public IList<WorkItem> FetchRange(int startIndex, int count) { return RemoteRepository.CreateQuery<WorkItem>(new DemoChannelProvider()) .Skip(startIndex) .Take(count) .ToList(); } }
      
      







WorkItemを表示するためのUIおよびListViewスタイルをわずかに修正します。サーバーとクライアントを起動し、[更新]ボタンをクリックすると、クライアントはサーバーに要素の数を求めるLinq要求と、リストの最初と2番目のページを受信する2つの後続の要求を送信します。リストを下にスクロールすると、次のページが

RemoteQueryablyProvider-> WCF-> HTTP-> WCF-> RemoteQueryExecutor-> NHibernate-> SQLite チェーンを介してデータベースからロードされます。

作業実演




RemoteQueryable APIの長所:





おわりに



これはおそらく停止する可能性があります。次のステップは、サーバー側でリクエストを変更すること、ユーザー認証を考慮に入れること、Reflectionで動作するコードを最適化すること、またはDynamic.Linqを接続することかもしれませんが、これは別の記事のトピックです。



参照資料



1. GitHubの記事のコードとサンプルを含むリポジトリ

2. IEnumerableとIQueryable、違いは何ですか?

3.
IQueryableおよびLINQデータプロバイダーの動作原理

4. C#プログラミング言語でのクロージャ

5. Serialize.Linq ソース

6. NHibernateソース

7. codeplexのRemotion.Linq

8. MSDNのIQueryProvider

9. 方法:式ツリービジターを実装する



All Articles