35

リポジトリパターンとはまだ混乱しています。このパターンを使用する主な理由は、ドメインからEF4.1固有のデータアクセス操作を呼び出さないようにするためです。IRepositoryインターフェースからジェネリックCRUD操作を呼び出したいと思います。これにより、テストが容易になり、将来データアクセスフレームワークを変更する必要が生じた場合でも、多くのコードをリファクタリングすることなく変更できるようになります。

これが私の状況の例です:

Groupデータベースに、、、Personおよびの3つのテーブルがありますGroupPersonMapGroupPersonMapはリンクテーブルであり、主キーGroupPerson主キーのみで構成されます。VS2010デザイナーで3つのテーブルのEFモデルを作成しました。EFは、リンクテーブルであると想定するのに十分賢いGroupPersonMapので、デザイナーには表示されません。EFで生成されたクラスの代わりに既存のドメインオブジェクトを使用したいので、モデルのコード生成をオフにします。

EFモデルに一致する私の既存のクラスは次のとおりです。

public class Group
{
   public int GroupId { get; set; }
   public string Name { get; set; }

   public virtual ICollection<Person> People { get; set; }
}

public class Person
{
   public int PersonId {get; set; }
   public string FirstName { get; set; }

   public virtual ICollection<Group> Groups { get; set; }
}

私は次のような一般的なリポジトリインターフェイスを持っています:

public interface IRepository<T> where T: class
{
    IQueryable<T> GetAll();
    T Add(T entity);
    T Update(T entity);
    void Delete(T entity);
    void Save()
}

および一般的なEFリポジトリ:

public class EF4Repository<T> : IRepository<T> where T: class
{
    public DbContext Context { get; private set; }
    private DbSet<T> _dbSet;

    public EF4Repository(string connectionString)
    {
        Context = new DbContext(connectionString);
        _dbSet = Context.Set<T>();
    }

    public EF4Repository(DbContext context)
    {
        Context = context;
        _dbSet = Context.Set<T>();
    }

    public IQueryable<T> GetAll()
    {
        // code
    }

    public T Insert(T entity)
    {
        // code
    }

    public T Update(T entity)
    {
        Context.Entry(entity).State = System.Data.EntityState.Modified;
        Context.SaveChanges();
    }

    public void Delete(T entity)
    {
        // code
    }

    public void Save()
    {
        // code
    }
}

Groupここで、既存のものを既存のものにマップしたいとしますPerson。私は次のようなことをしなければならないでしょう:

        EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
        EFRepository<Person> personRepository = new EFRepository<Person>("name=connString");

        var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
        var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();

        group.People.Add(person);
        groupRepository.Update(group);

ただし、EFは新しいと見なし、データベースに再入力しようとするため、これは機能しません。Personこれにより、主キー制約エラーが発生します。のメソッドを使用して、データベースにすでに存在することをEFに通知する必要があるため、テーブルとテーブルの間にマップを作成するだけです。INSERTPersonDbSetAttachPersonGroupPersonGroupPersonMap

したがってPerson、コンテキストにアタッチするにはAttach、IRepositoryにメソッドを追加する必要があります。

public interface IRepository<T> where T: class
{
    // existing methods
    T Attach(T entity);
}

主キー制約エラーを修正するには:

EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>(groupRepository.Context);

var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();

personRepository.Attach(person);
group.People.Add(person);
groupRepository.Update(group);

修理済み。Groupここで、グループ/個人マップを作成するたびにデータベースで更新される別の問題に対処する必要があります。これは、私のEFRepository.Update()メソッドでは、エンティティの状態が明示的にUnchanged Group`に設定されてModified'. I must set the Group's state toいるため、so theテーブルが変更されないためです。

これを修正するにはUpdate、ルートエンティティを更新しない何らかのオーバーロードをIRepositoryに追加する必要がありますGroup。この場合、次のようになります。

public interface IRepository<T> where T: class
{
    // existing methods
    T Update(T entity, bool updateRootEntity);
}

UpdateメソッドのEF4の意味は、次のようになります。

T Update(T entity, bool updateRootEntity)
{
   if (updateRootEntity)
      Context.Entry(entity).State = System.Data.EntityState.Modified;
   else
      Context.Entry(entity).State = System.Data.EntityState.Unchanged;

    Context.SaveChanges();
}

私の質問は:私はこれに正しい方法でアプローチしていますか?EFとリポジトリパターンを使い始めると、リポジトリはEF中心に見え始めています。この長い投稿を読んでくれてありがとう

4

1 に答える 1

66

このパターンを使用する主な理由は、ドメインからEF4.1固有のデータアクセス操作を呼び出さないようにするためです。IRepositoryインターフェースからジェネリックCRUD操作を呼び出したいと思います。これにより、テストが容易になります

いいえ、テストが簡単になるわけではありません。公開しIQueryableたため、リポジトリはユニットテスト可能ではありません

将来、データアクセスフレームワークを変更する必要がある場合は、多くのコードをリファクタリングすることなく変更できます。

いいえ、公開したため、またEF / ORMはリークのある抽象化であるため、とにかく多くのコードを変更する必要はありませんIQueryable。上位層は、ORM内で何らかの動作(遅延読み込みなど)が魔法のように発生することを期待しています。また、これはリポジトリに行く最も奇妙な理由の1つです。今すぐ適切なテクノロジーを選択し、それを使用して賭けをするだけです。後で変更する必要がある場合は、間違いをして間違ったものを選択したか、要件が変更されたことを意味します。どちらの場合も、多くの作業が必要になります。

ただし、EFはPersonが新しいと見なし、Personをデータベースに再挿入しようとするため、これは機能しません。これにより、主キー制約エラーが発生します。

はい。リポジトリごとに新しいコンテキストを使用しているためです=これは間違ったアプローチです。リポジトリはコンテキストを共有する必要があります。EF依存関係をアプリケーションに戻すため、2番目のソリューションも正しくありません。リポジトリがコンテキストを公開しています。これは通常、2番目のパターン(作業単位)によって解決されます。作業単位はコンテキストをラップし、作業単位はアトミック変更セットを形成しSaveChangesます。関連するすべてのリポジトリーによって行われた変更をコミットするには、作業単位に公開する必要があります。

グループ/個人マップを作成するたびに、データベースでグループが更新されるという問題が発生しました。

なぜ状態を変えるのですか?Attachリポジトリからエンティティを受け取ったので、それをデタッチするまで、手動で呼び出して状態を変更する理由はありません。これはすべて、接続されたエンティティで自動的に発生するはずです。単に電話してSaveChangesください。デタッチされたエンティティを使用している場合は、すべてのエンティティとリレーションの状態を正しく設定する必要があるため、そのような場合は、すべてのシナリオを処理するためにロジックまたは更新のオーバーロードが実際に必要になります。

私はこれに正しい方法でアプローチしていますか?EFとリポジトリパターンを使い始めると、リポジトリはEF中心に見え始めています。

私はそうは思わない。まず第一に、あなたは集約ルートを使用していません。そうすると、汎用リポジトリがそれに適していないことがすぐにわかります。集約ルートのリポジトリには、ルートによって集約されたリレーションの操作を処理するために、集約ルートごとに特定のメソッドがあります。は集約Groupの一部ではありませんが、Personリポジトリには、Personからのグループの追加と削除を処理するための特定のメソッドが必要です(ただし、グループ自体を作成または削除することはできません)。Imo汎用リポジトリは冗長レイヤーです。PersonGroupPersonMap

于 2011-08-24T08:10:25.743 に答える