web-dev-qa-db-ja.com

Entity Framework 4.1のリポジトリパターンと親子関係

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

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

データベースには、GroupPersonGroupPersonMapの3つのテーブルがあります。 GroupPersonMapはリンクテーブルであり、GroupPersonの主キーのみで構成されています。 VS 2010デザイナーで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が新しいと見なし、データベースにINSERTを再_Personしようとするため、これは機能しません。これにより、主キー制約エラーが発生します。 DbSetAttachメソッドを使用してEFにPersonがデータベースにすでに存在することを通知する必要があるので、GroupPersonの間のマップをGroupPersonMapテーブルに作成します。

したがって、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がUPDATEされる別の問題に対処する必要があります。これは、私のEFRepository.Update()メソッドで、エンティティの状態が明示的に_Modified'. I must set the Group's state to_ Unchanged _so the_ Group`テーブルに設定されていないためです。

これを修正するには、ルートエンティティを更新しない何らかの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中心に見え始めます。この長い投稿を読んでくれてありがとう

35
jodev

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

いいえ テストが簡単になるわけではありませんあなたはIQueryable を公開したので、リポジトリ 単体テストはできません です。

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

とにかくIQueryableを公開したため、およびEF/ORMがリークのある抽象化であるため、多くのコードを変更する必要はありません。また、これはリポジトリに移動する最も奇妙な理由の1つです。今すぐ適切なテクノロジーを選択し、それを使用して賭けをしてください。後で変更する必要がある場合は、次のいずれかを意味します ミスをして間違ったものを選択したか、要件が変更されました -どちらの場合も多くの作業になります。

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

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

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

なぜ状態を変更するのですか?リポジトリからエンティティを受け取ったので、デタッチするまでAttachを呼び出して手動で状態を変更する必要はありません。これはすべて、アタッチされたエンティティで自動的に行われます。単にSaveChangesを呼び出します。デタッチされたエンティティを使用している場合、 すべてのエンティティの状態を正しく設定する必要があります および関係なので、このような場合、実際にすべてのシナリオを処理するには、いくつかのロジックまたは更新オーバーロードが必要になります。

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

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

67
Ladislav Mrnka