web-dev-qa-db-ja.com

データベースからレイジーオブジェクトを生成する方法

私はこれらのクラスを持っています:

public class Order
{
    private Lazy<IEnumerable<Volume>> _volumes;
    long ID { get; private set; }
    string Description { get; private set; }
    IEnumerable<Volume> Volumes { get { return _volumes.Value; } }
    //etc..
}
public class Volume
{
    long ID { get; private set; }
    string Description { get; private set; }
    //etc..
} 

複数の注文をインスタンス化して入力する必要があります。これらのクラスのオブジェクトを提供するために、次のリポジトリがあります。

public interface IOrderRepository
{
    Order GetByID(long ID);
    IEnumerable<Order> GetAll();
    Order Create(long ID, string Description);
    void Update(Order O);
    void Delete(Order O);
}
public interface IVolumeRepository
{
    Volume GetByID(long ID);
    IEnumerable<Volume> GetAll(Order O);
    Volume Create(long ID, string Description, Order O);
    void Update(Volume V);
    void Delete(Volume V);
}

インスタンス化の際に各注文のボリュームを入力する必要はありません。多くのボリュームが存在する可能性があるので、私の意見では遅延ロードを実装するのが最善でしょう。
では、ボリュームをフェッチする場所を注文にどのように通知すればよいですか?インスタンス化されたOrderを生成する次のコードがあります。

//Interface
public interface IVolumeFetcher { IEnumerable<Volume> GetAll(Order O); }
//Order Constructor
public Order(long ID, string Description, IVolumeFetcher VF)
{
    _volumes = new Lazy<IEnumerable<Volume>>(() => VF.GetAll(this));
}

これはOrderにリポジトリへの参照を提供します。私には良い考えではないようです。
注文のボリュームをシンプルなリストにして、必要に応じて記入することができます。

public List<Volume> Volumes { get; private set; }

しかし、このアプローチは簡単に破損し、呼び出し元はリストにボリュームを追加でき、DBに永続化されません。ところで、これは3層のアプリケーションで、各層のOrderクラスとVolumeクラス、およびリポジトリがあります。

6

トランザクションスクリプト がオブジェクトに対してあり、ORMがないと仮定します。

オブジェクトにリポジトリコードを含めたくありません。 _Lazy<T>_は、コンストラクターのパラメーターと同じように扱います。そのため、IOrderRepositoryは、構築されたオブジェクトとしてOrderコンストラクターに提供します。

_public Order(long id, string description, Lazy<IEnumerable<Volume>> volumes)
{
    ...
    _volumes = volumes;
}
_

_Lazy<T>_のクロージャ内にあるリポジトリリファレンスまたは破棄する必要がある場合、この使用法は読み取り専用であるため、Lazyメソッド内でIVolumeRepositoryを作成して破棄できます。

_// assuming IVolumeRepository is IDisposable or UOW
var volumes = new Lazy<IEnumerable<Volume>>(() =>
{
    //using (var repo = new ConcreteVolumeRepository())
    // or repo factory
    //using (var repo = volumeRepoFactoryFn())
    // or DI container
    using (var repo = DIContainer.Get<IVolumeRepository>())
    {
        return VF.GetAll(orderId))
    }
};

var order = new Order(orderId, description, volumes)
_

リポジトリがシングルトンの場合(そうである必要はありません)、次のようにします。

_//IVolumeRepository volumeRepo ...
var orderId = ...
var description = ...
var volumes = new Lazy<IEnumerable<Volume>>(() => volumeRepo.GetAll(orderId));

var order = new Order(orderId, description, volumes);
_

補足として、ボリュームリポジトリは、それ自体を注文するのではなく、注文IDでロードする必要があります。 IVolumeRepository.GetAll(Order O)メソッドは、注文からIDプロパティを取得するだけであると想定しています。これは、抽象化するのに役立ちません。それが(他の種類のlongに対して)OrderからのIDであることを確認したい場合は、OrderIdをlongをラップする別個のクラスまたは構造にすることができます。これは、ボリュームのロードで鶏卵の問題を引き起こさずに同じ目的を達成します。ただし、個別のOrderId構造体を作成しなくても、単体テストで十分な検証が提供されると思います。 (単体テストを行わないのに、なぜリポジトリインターフェイスを使用するのですか?)

1
Kasey Speakman

OrderIVolumeRepositoryを参照しているのは問題ありませんが、2つの条件でのみです。

1つ目は、関連するすべてのエンティティが単一のContextの下にあることです。例えば。すべてのエンティティのライフタイムを処理する現在のトランザクションを表すクラスが存在します。したがって、これらのエンティティを操作し、新しくフェッチおよび作成されたエンティティがこのコンテキストに該当する限り、このコンテキストが存在する必要があります。これはORMが行う方法です。

2つ目は、IVolumeRepositoryの実装がデータベースから作成またはマテリアライズされたときに、Orderの実装が透過的に注入されることです。これにより、ドメインがリポジトリの実際の実装に関する知識を持たなくなります。繰り返しますが、これはORMが行う方法です。エンティティのすべての具体化された具象インスタンス(ORMは、ほとんどの場合、ランタイムコード生成を通じて各エンティティに対して独自の具象クラスを作成します)がContextを参照するためです。

0
Euphoric