4

UnitOfWork と Repository のどちらを巡るかについて、多くの投稿 (および議論!) を見てきました。私が好むレポジトリ パターンの 1 つは、型付きの汎用レポジトリ パターンですが、これにより、クリーンなコードとテスト容易性に関するいくつかの問題が発生したのではないかと心配しています。次のリポジトリ インターフェイスとジェネリック クラスを使用します。

public interface IDataEntityRepository<T> : IDisposable where T : IDataEntity
{
   // CRUD
   int Create(T createObject);
   // etc.
}


public class DataEntityRepository<T> : IDataEntityRepository<T> where T : class, IDataEntity
{
   private IDbContext Context { get; set; }

   public DataEntityRepository (IDbContext context)
   {
     Context = context;
   }

   private IDbSet<T> DbSet { get { return Context.Set<T>(); } }   

   public int Create(T CreateObject)
   {
      DbSet.Add(createObject);
   }

   // etc.

}

// where

public interface IDbContext
{
   IDbSet<T> Set<T>() where T : class;
   DbEntityEntry<T> Entry<T>(T readObject) where T : class;

   int SaveChanges();
   void Dispose();
}

したがって、基本的には各パターンで Context プロパティを使用して、基になるコンテキストにアクセスしています。私の問題は次のとおりです。作業単位を作成すると、それは事実上、リポジトリに知ってもらいたいコンテキストのラッパーになります。したがって、次のように宣言する Unit Of Work があるとします。

public UserUnitOfWork(
    IDataEntityRepository<User> userRepository,
    IDataEntityRepository<Role> roleRepository)
{
    _userRepository = userRepository;
    _roleRepository = roleRepository;
}

private readonly IDataEntityRepository<User> _userRepository;

public IDataEntityRepository<User> UserRepository
{
    get { return _userRepository; }
}

private readonly IDataEntityRepository<Role> _roleRepository;

public IDataEntityRepository<Role> RoleRepository
{
    get { return _roleRepository; }
}

私が渡している2つのリポジトリは、それらが渡されている作業単位でインスタンス化する必要があるという事実に問題があります。明らかに、コンストラクター内でリポジトリをインスタンス化して「this」を渡すこともできますが、それでは私の作業単位がリポジトリの特定の具体的なインスタンスに緊密に結合され、単体テストがはるかに難しくなります。他の誰かがこの道を進んで同じ壁にぶつかったかどうか知りたいです。これらのパターンはどちらも私にとって初めてのことなので、根本的に間違ったことをしている可能性があります。どんなアイデアでも大歓迎です!

更新 (@MikeSW への応答)

こんにちはマイク、ご意見をお寄せいただきありがとうございます。私は EF Code First を使用していますが、特定の要素を抽象化して、必要に応じて別のデータ ソースまたは ORM に切り替えることができるようにしたいと考えていました。特定の要素は純粋な意味で単体テストできないが、統合テストはできるという難しい方法に気付いたと思います! ビジネス オブジェクトやビューモデルなどを操作するリポジトリについてのポイントを上げたいと思います。おそらく誤解しているかもしれませんが、私のコア ビジネス オブジェクト (POCO) と見なされるものがあり、EF コードなどの ORM を使用したい場合最初にこれらのエンティティをラップして、データベースを作成してから操作します (そして、ViewModel 内でこれらのエンティティを再利用する可能性があります)。リポジトリは、一連の CRUD 操作のコンテキストでこれらのエンティティを直接処理することを期待しています。エンティティは、永続化レイヤーについて何も認識していません。ViewModel も認識していません。私の作業単位は、必要なリポジトリをインスタンス化して保持するだけで、トランザクションのコミットを実行できます (複数のリポジトリにまたがるが、同じコンテキスト/セッション)。私のソリューションで行ったことは、UnitOfWork コンストラクターから IDataEntityRepository の注入を削除することです。 、実際には EFDataEntityRepository のような名前にする必要があります)。ユニットロジック全体が、データベースへのコンテキスト(自体)を使用してリポジトリを確立することになるため、これ自体をユニットテストすることはできません。単に統合テストが必要です。それが理にかなっていることを願っていますか?!

4

3 に答える 3

3

応答が遅れたことをお詫びします-その間、私はこれに対するさまざまなアプローチを試してきました。コメントに同意するので、上記の回答をマークアップしました。

これは、複数の回答があり、全体的なアプローチに大きく依存している質問の1つです。EFが既製の作業単位パターンを効果的に提供することに同意しますが、独自の作業単位とリポジトリレイヤーを作成するという私の決定は、データベースエンティティへのアクセスを制御できるようにすることでした。

私が苦労したのは、作業単位にリポジトリを挿入できるようにする必要があることでした。しかし、私が気付いたのは、EFの場合、私の作業単位は、事実上、Commit(SaveChanges)メソッドを使用して複数のリポジトリーを囲む薄いラッパーであったということです。FindCustomerなどの特定のアクションを実行する責任はありませんでした。

そこで、作業単位をその特定のタイプのDataRepositoryパターンに緊密に結合できると判断しました。テスト可能なパターンを確保するために、CreateCustomer、FindCustomersなどの特定のアクションを実行するためのファサードを提供するサービスレイヤーを導入しました。これらのサービスは、リポジトリ(インターフェイスとして)およびリポジトリへのアクセスを提供するIUnitOfWorkコンストラクターパラメーターを受け入れました。コミットメソッド。

その後、テスト目的で、作業単位および/またはリポジトリの両方の偽物を作成することができました。これにより、偽物で単体テストできるものと、具体的なインスタンスで統合テストする必要があるものを決定することができました。

また、これにより、データベースで実行されるアクションとその実行方法を制御する機会も得られます。

この特定の猫の皮を剥ぐ方法はたくさんあると思いますが、テスト可能なクリーンなインターフェースを提供するという目標は、このアプローチでほぼ達成されました。

g1gaとMikeの入力に感謝します。

于 2013-03-01T11:03:16.197 に答える
3

Unit of Work 内の各リポジトリへの依存を避けるために、次のコントラクトに基づくプロバイダーを使用できます。

public interface IRepositoryProvider
{
    DbContext DbContext { get; set; }
    IRepository<T> GetRepositoryForEntityType<T>() where T : class;
    T GetRepository<T>(Func<DbContext, object> factory = null) where T : class;
    void SetRepository<T>(T repository);
}

次に、次のように UoW に注入できます。

public class UserUnitOfWork: IUserUnitOfWork
{
    public UserUnitOfWork(IRepositoryProvider repositoryProvider)
    {
        RepositoryProvider = repositoryProvider;
    }

    protected IDataEntityRepository<T> GetRepo<T>() where T : class
    {
        return RepositoryProvider.GetRepositoryForEntityType<T>();
    }

    public IDataEntityRepository<User> Users { get { return GetRepo<User>(); } }        
    public IDataEntityRepository<Role> Roles { get { return GetRepo<Role>(); } }        
...
于 2013-01-20T13:01:27.017 に答える
2

Entity Framework (EF) (使用していると思います) を使用している場合、既に汎用リポジトリ IDbSet があります。EF メソッドを呼び出すためだけに別のレイヤーを上に追加しても意味がありません。

また、リポジトリはアプリケーション オブジェクト (通常はビジネス オブジェクトですが、ビュー モデルまたはオブジェクト状態の場合もあります) と連動します。db エンティティのみを使用している場合は、Repository パターンの目的 (ビジネス オブジェクトをデータベースから分離するため) を無効にします。元のパターンはビジネス オブジェクトのみを扱いますが、ビジネス レイヤー外でも役立つパターンです。

ポイントは、EF エンティティは Persistence オブジェクトであり、ビジネス オブジェクトとは関係がない (または関係があるべきではない) ということです。リポジトリ パターンを使用して、ビジネス オブジェクトを永続オブジェクトに、またはその逆に「変換」します。

場合によっては、アプリケーション オブジェクト (ビューモデルなど) が永続エンティティと同じになることがあります (その場合、EF オブジェクトを直接使用できます) が、これは偶然です。

Unit of Work (UoW) については、ややこしいとしましょう。個人的には、DDD (ドメイン駆動設計) アプローチを使用することを好み、リポジトリに送信されるビジネス オブジェクト (BO) はすべて UoW であるため、トランザクションにラップされると考えています。

複数の BO を更新する必要がある場合は、メッセージ駆動型アーキテクチャを使用して、関連する BO にコマンドを送信します。もちろん、これはより複雑であり、結果整合性の概念に慣れる必要がありますが、特定の RDBMS に依存しているわけではありません。

特定の RDBMS を使用し、それが決して変更されないことがわかっている場合は、トランザクションを開始し、関連する接続を各リポジトリに渡し、最後にコミット (UoW) することができます。Web 設定の場合は、さらに簡単です。リクエストの開始時にトランザクションを開始し、リクエストの終了時にコミットします (ASp.Net Mvc の ActionFilter を使用できます)。

ただし、このソリューションは 1 つの RDBMS に結び付けられているため、NoSql やトランザクションをサポートしないストレージには適用されません。そのような場合には、メッセージ駆動型の方法が最適です。

于 2013-01-08T14:23:36.183 に答える