10

私のプロジェクトのほとんどでは、nHibernate + Fluentマッピングを使用していますが、最近、読み取り操作をDapperに移動できるかどうかを確認するためにDapperをいじり始めました。

私はDDDアプローチに従うので、ドメインエンティティにはパブリックセッターがありません。例えば:

public class User
{
    private int _id;
    private string _name;
    private IList<Car> _carList;

    protected User(){} // Fluent Mapping

    public User(string id, string name)
    {
        // validation 
        // ...
        _id = id;
        _name = name;
    }

    public int Id{ get {return _id;} }
    public string Name { get {return _name;} }
    public IList<Car> CarList { get {return _carList;}}         
}

public class Car
{
    private int _id;
    private string _brand;

    protected Car(){} // Fluent Mapping

    public Car(string id, string brand)
    {
        // validation 
        // ...
        _id = id;
        _brand= brand;
    }

    public int Id{ get {return _id;} }
    public string Brand { get {return _brand;} }
}

Fluent nHibernateを使用すると、マッピング用のメンバーを表示できます。

Id(Reveal.Member<User>("_id")).Column("id");
Map(Reveal.Member<User>("_name")).Column("name");

ドメインエンティティをDapperでマッピングする方法はありますか?もしそうなら、どのように?

4

2 に答える 2

17

1つのオプションは、Dapperと連携するための永続化クラスの個別のセットを作成することです。例:UserRecordおよびCarRecord。レコードクラスはdbテーブルと一致し、永続モジュール内にカプセル化されます。Dapperクエリはこのクラスに対して実行され、ドメインエンティティをアセンブルしてクライアントに返す別の永続化ファクトリを作成できます。

小さな例:

        var carRecord = DbConnection.Query<CarRecord>("select * from cars where id = @id", new {id = 1});
        var carEntity = CarFactory.Build(carRecord);

これにより、優れた分離が作成され、柔軟性が提供されます。

于 2013-03-08T22:08:42.140 に答える
10

プライベートセッターを使用するプリミティブ型のプロパティの場合Dapper、名前がデータベース/クエリから取得したものと一致する限り、それらを自動的にマップするのに十分賢いです。

あなたのUserクラスが集約ルートであるとしましょう

public class User : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    ...
}

GetByIdリポジトリからユーザーを再構築する方法があります

public class UserRepository : Repository<User>, IUserRepository
{
    private UserRepository(IDbConnection dbConnection) : base(dbConnection) {}

    ...

    public User GetById(int id)
    {
        const string sql = @"
            SELECT *
            FROM [User]
            WHERE ID = @userId;
        ";

        return base.DbConnection.QuerySingleOrDefault<User>(
            sql: sql,
            param: new { userId = id }
        );
    }
}

SQLがIdNameの列を返す限り、それらがプライベートセッターを持っている場合でも、それらは自動的にUserクラスの一致するプロパティにマップされます。素敵できれい!

人間関係の問題

ロードする必要のある1対多のオブジェクトがある場合、すべてがトリッキーになります。

ここで、Userクラスに、 Userに属する読み取り専用の車のリストと、車の追加/削除に使用できるいくつかのメソッドがあるとします。

public class User : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    private readonly IList<Car> _cars = new List<Car>();
    public IEnumerable<Car> Cars => _cars;

    public void PurchaseCar(Car car)
    {
        _cars.Add(car);
 
        AddEvent(new CarPurchasedByUser { ... });
    }

    public void SellCar(Car car)
    {
        _cars.Remove(car);

        AddEvent(new CarSoldByUser { ... });
    }
}

public class Car : Entity
{
    public int Id { get; private set; }
    public string Brand { get; private set; }
}

では、 Userクラスが作成されたときに、どのように車のリストをロードしますか?

複数のクエリを実行することを提案する人もいます。ユーザーを作成した後、PurchaseCarandSellCarメソッド(またはクラスで使用可能なメソッド)を呼び出して車を追加/削除して、車のリストを作成します。

public User GetById(int id)
{
    const string sql = @"
        SELECT *
        FROM [User]
        WHERE ID = @userId;

        SELECT *
        FROM [Car]
        WHERE UserID = @userId;
    ";

    using (var multi = base.DbConnection.QueryMultiple(sql, new { userId = id })
    {
        var user = multi.Read<User>()
            .FirstOrDefault();

        if (user != null)
        {
            var cars = multi.Read<Car>();

            foreach (var car in cars)
            {
                user.PurchaseCar(car);
            }
        }

        return user;
    }
}

ただし、これらのメソッドには通常、他のコマンドを起動するために他のユーザーがサブスクライブする可能性のある追加のイベントが発生するため、練習している場合は実際にそうすることはできません。UserオブジェクトDomain-Driven Designを初期化しようとしていました。

反射でそれを解決する

私のために働いた唯一のことは使用することSystem.Reflectionです!

public User GetById(int id)
{
    const string sql = @"
        SELECT *
        FROM [User]
        WHERE ID = @userId;

        SELECT *
        FROM [Car]
        WHERE UserID = @userId;
    ";

    using (var multi = base.DbConnection.QueryMultiple(sql, new { userId = id })
    {
        var user = multi.Read<User>()
            .FirstOrDefault();

        if (user != null)
        {
            var cars = multi.Read<Car>();

            // Load user car list using Reflection
            var privateCarListField = user.GetType()
                .GetField("_cars", BindingFlags.NonPublic | BindingFlags.Instance);

            privateCarListField.SetValue(car, cars);
        }

        return user;
    }
}
于 2019-04-11T21:13:05.163 に答える