オブジェクト マッピングをプロジェクト データ リポジトリにカプセル化しようとしています。おそらく、EF は必要なレベルの抽象化を提供しますが、さまざまな理由から、現時点では Linq to SQL を使用しています。次のコードは、データベース内のユーザーを ModUser オブジェクトのリストとして返すことを目的としています。ここで、ModUser は、リポジトリが公開する POCO です。
public List<ModUser> GetUsers() {
Users.Select(MapUser).ToList();
}
public Expression<Func<User, ModUser>> MapUser {
get {
return u => new ModUser() {
UserId = u.User_Id,
UserResources = u.Resources(MapResource)
}
}
}
public Expression<Func<Resource, ModResource>> MapResource { ...
別の式の中から MapResource 式を呼び出そうとしているため、MapResource 式を呼び出すことができないため、コードは失敗します。「MapResource」を u => new ModResource() に置き換え、ExpressionVisitor を使用してこのプレースホルダー ノードを見つけ、MapResource 式に置き換えることで、これを回避することができました。
ModUser のプロパティを単一のプロパティを含む式、つまり UserResource = MapResource に割り当てようとすると、同様の問題が発生します。Expression クラスのメソッドを使用して必要な式を手動で組み合わせることで、この 2 番目の問題を回避することができました。
上記のコードを次のように変更できることを認識しています
UserResources = u.Resources(r => MapResource.Compile().Invoke(r));
しかし、生成された最終的な SQL クエリは、関数を扱っているため、MapResouce が必要とするものだけでなく、r のすべての属性を取得する必要があります。また、MapResouce が r 上のさらなるテーブルへのアクセスを必要とする場合、それは式ではなく関数として使用されているため不可能です。DeferredLoadingEnabled を true に設定することもできますが、それでは、必要なテーブルと結合するようにメイン クエリを変更するのではなく、多数の個別のクエリが生成されます。
これらの操作が .NET の将来のバージョンでより簡単になるかどうか、または間違った方法で行っているかどうかは誰にもわかりませんか? 私は Linq と Expression の機能がとても気に入っています。より読みやすいコードを使用してそれらを使用できればと思っています。
更新しました
式をより構成可能にした方法の例をいくつか追加するかもしれないと思いました. それらは簡潔ではありませんが、仕事を成し遂げます。
public Expression<Func<User, ModUser>> MapUser {
get {
Expression<Func<User, ModUser>> mapUser = u => new ModUser() {
UserId = u.User_Id,
UserResources = u.Resources(r => new ModResource())
};
return mapUser.MapResources(this);
}
}
public Expression<Func<Resource, ModResource>> MapResource { ... }
public static Expression<Func<T0, T1>> MapResources<T0, T1>(this Expression<Func<T0, T1>> exp, DataContext dc) {
return exp.Visit<MethodCallExpression, Expression<Func<T0, T1>>>(m => {
if(m.Arguments.Count > 1 && m.Arguments[1].Type == typeof(Func<DataContext.Resource, ModResource>)) { //Find a select statement that has the sub expression as an argument
//The resource mapping expression will require the Resource object, which is obtained here
ParameterExpression resourceParam = ((LambdaExpression)m.Arguments[1]).Parameters[0];
return Expression.Call(m.Method, m.Arguments[0], //The first argument is the record selection for the 'select' method
Expression.Lambda<Func<DataContext.Resource, ModResource>>(//Provide the proper mapping expression as the projection for the 'select' method
Expression.Invoke(dc.MapResource, resourceParam),
resourceParam)
);
}
return m;
});
}
私はここで何をしているのですか?このバージョンの MapUser では、ModResource オブジェクトを正しく作成していないことに注意してください。ダミー バージョンを作成するだけです。次に、ダミー呼び出しを検索する式ビジター メソッドを呼び出して、最初に必要だったものに置き換えます。もともと欲しかった式ツリーを本質的に構築できるため、式の構文が不足しているように思えますが、実際にツリーを表示してそれを行う必要があります。以下は、特異なケースを扱う、私が見つけた別の回避策です。
public Expression<Func<User, ModUser>> MapUser {
get {
Expression<Func<User, ModResource, ModUser>> mapUser = (u, resource) => new ModUser() {
UserId = u.User_Id,
UserResource = resource;
}
return mapUser.CollapseArgument(MapResource, user => user.MainResource);
}
}
public Expression<Func<Resource, ModResource>> MapResource { ... }
public static Expression<Func<T0, T3>> CollapseArgument<T0, T1, T2, T3>(this Expression<Func<T0, T1, T3>> exp, Expression<Func<T2, T1>> exp0, Expression<Func<T0, T2>> exp1) {
var param0 = Expression.Parameter(typeof(T0), "p0");
var argExp = Expression.Invoke(exp0, Expression.Invoke(exp1, param0));
return Expression.Lambda<Func<T0, T3>>(
Expression.Invoke(exp, param0, argExp),
param0);
}
この 2 番目の例では、ユーザー データからリソース データを取得できることはわかっていますが、これを行う方法を示してリソース データをリソース POCO にマップする式を「インライン化」することはできません。しかし、既にマップされたリソース POCO を指定して式ツリーを手動で作成し、それを使用することはできます。次に、ユーザーからリソースの生データを取得する方法を示す別の式と、生のリソース データをリソース POCO にマップする方法を示す最後の式を作成します。プライマリ ユーザー パラメータからリソース固有のパラメータを取得できるため、リソース固有のパラメータを「折りたたむ」方法で、このすべての情報を 1 つの式ツリーに結合できると考えられます。これが上記のコードの動作です。
だから私は式を高度に構成可能にする方法を見つけました...それはただきれいに感じられません.