5

オブジェクト マッピングをプロジェクト データ リポジトリにカプセル化しようとしています。おそらく、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 つの式ツリーに結合できると考えられます。これが上記のコードの動作です。

だから私は式を高度に構成可能にする方法を見つけました...それはただきれいに感じられません.

4

3 に答える 3

1

わかりました、OPの質問を読み終えていないことを認めなければなりませんが(にやにや笑う)、Linq-to-SQL属性を使用してPOCOオブジェクトを装飾できることをご存知ですか?デザイナーを使用する必要はありません。

これは、現在目の前で開いているコードからのランダムな例です。これは「Product」と呼ばれる POCO で、Linq-to-SQL DataContext とやり取りできるようにするいくつかの属性が適用されています。

HTH

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Web;

namespace Redacted.Site.Models.Store
{
    /// <summary>
    /// A "Product" is a good for purchase at the store.
    /// </summary>
    [Table(Name = "s.products")]
    public partial class Product
    {
        /// <summary>Gets or sets the PK of the object/row.</summary>
        [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL")]
        public Int32 ID { get; set; }

        /// <summary>Gets or sets the Title.</summary>
        [Column(Name = "title", DbType = "NVARCHAR(500) NOT NULL")]
        public String Title { get; set; }

        /// <summary>Gets or sets the Lede.</summary>
        [Column(Name = "lede", DbType = "NVARCHAR(MAX) NOT NULL")]
        public String Lede { get; set; }

        /// <summary>Gets or sets the Description.</summary>
        [Column(Name = "description", DbType = "NTEXT NOT NULL")]
        public String Description { get; set; }

        /// <summary>Gets or sets the Price.</summary>
        [Column(Name = "price", DbType = "FLOAT NOT NULL")]
        public Double Price { get; set; }

        /// <summary>Gets or sets the FK to the <see cref="Department"/>.</summary>
        [Column(Name = "department_id", DbType = "TINYINT NOT NULL")]
        public Byte DepartmentID { get; set; }

        /// <summary>Gets or sets the date/time the product was released to the store.</summary>
        [Column(Name = "released_on_utc", DbType = "DATETIME NOT NULL")]
        public Int32 ReleasedOnUtc { get; set; }

    }
}
于 2009-03-19T21:51:00.663 に答える
1

Linq To SQL が POCO をサポートする方法は少し異なります。

永続性の無視を実現するには、LTS デザイナーではなく、modUser のマップ方法 (列、関連付けなど) を記述したマッピング ファイルを使用します。新しいコンテキストを作成するときは、XML マッピング ファイルを XMLMappingSource として渡します。

このようにして、LTS はデータベースからオブジェクトを返します。

コレクションの関連付けプロパティをタイプ IList(of T) の読み取り/書き込みプロパティとして定義することで、LinqToSQL がこれらのコレクションの遅延読み込みを提供するのに十分であることをあちこちで読みましたが、私はそれを試していないため、保証できません。 .

Entity Framework は、現在のバージョンで POCO をサポートするとさらに悪化します (ほとんどの人が POCO という用語を理解している限り、基本的にはありません)。

通常の LTS 制限がすべてこれに適用されるため、「値オブジェクト」マッピングはありません。データベースと POCO サポートから少し離れたものが必要な場合は、NHibernate を検討する必要があります。

于 2009-03-16T17:49:03.060 に答える
0

POCOを使用する場合、LinqtoSQLは最良の選択ではないと思います。NHibernateのようなものを使用したほうがはるかに良いと思います。POCOでLinqtoSQLを使用するということは、データベースの上にあるデータレイヤー(Linq to SQL)の上にレイヤーを構築していることを意味します。NHibernateを使用すると、すべてのコードをビルドしてデータベースに直接マッピングすることになります。レイヤーが少ない==コードが少ない==作業が少ない。

于 2009-03-14T01:44:34.040 に答える