web-dev-qa-db-ja.com

C#エンティティフレームワーク:リポジトリクラス内でのDBContextクラスの正しい使用

以下に示すように、以前はリポジトリクラスを実装していました。

public Class MyRepository
{
      private MyDbContext _context; 

      public MyRepository(MyDbContext context)
      {
          _context = context;
      }

      public Entity GetEntity(Guid id)
      {
          return _context.Entities.Find(id);
      }
}

ただし、最近、リポジトリ内のプライベートメンバーとしてデータコンテキストを使用するのは悪い習慣であると言うこの記事を読みました。 http://devproconnections.com/development/solving-net-scalability-problem

現在、理論的には正しい記事です。DbContextはIDisposableを実装しているため、最も正しい実装は次のようになります。

public Class MyRepository
{
      public Entity  GetEntity(Guid id)
      {
          using (MyDbContext context = new MyDBContext())
          {
              return context.Entities.Find(id);
          }
      }
}

ただし、この他の記事によると、DbContextの破棄は必須ではありません。 http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html

2つの記事のどちらが正しいですか?私はかなり混乱しています。リポジトリクラスでプライベートメンバとしてDbContextを使用すると、最初の記事が示唆するように、実際に「スケーラビリティの問題」を引き起こす可能性がありますか?

68
Errore Fatale

私はあなたが最初の記事に従うべきではないと思う、そしてその理由を説明する。

最初のアプローチに従って、1次キャッシュ、IDマップ、作業単位、変更追跡、遅延読み込みなど、Entity FrameworkがDbContextを介して提供するほとんどすべての機能を失います。能力。これは、上記のシナリオでは、すべてのデータベースクエリに対して新しいDbContextインスタンスが作成され、すぐに破棄されるため、DbContextインスタンスがビジネス全体でデータオブジェクトの状態を追跡できなくなるためです。トランザクション。

リポジトリクラスにDbContextをプライベートプロパティとして含めることにも問題があります。より良いアプローチはCustomDbContextScopeを持つことだと思います。このアプローチは、この男:Mehdi El Gueddariによって非常によく説明されています

この記事 http://mehdi.me/ambient-dbcontext-in-ef6/ 私が見たEntityFrameworkに関する最高の記事の1つです。あなたは完全にそれを読むべきであり、私はそれがあなたのすべての質問に答えると信じています。

38
Fabio Luz

複数のリポジトリがあり、異なるリポジトリから2つのレコードを更新する必要があると仮定しましょう。そして、あなたはそれをトランザクションで行う必要があります(1つが失敗した場合-両方の更新がロールバックします):

var repositoryA = GetRepository<ClassA>();
var repositoryB = GetRepository<ClassB>();

repository.Update(entityA);
repository.Update(entityB);

そのため、各リポジトリに独自のDbContextがある場合(ケース2)、TransactionScopeを使用してこれを実現する必要があります。

より良い方法-1つの操作(1つの呼び出し、1つの 作業単位 )に対して1つの共有DbContextを使用します。そのため、DbContextはトランザクションを管理できます。 EFはそのために最適です。 1つのDbContextのみを作成し、多くのリポジトリですべての変更を行い、SaveChangesを1回呼び出し、すべての操作と作業が完了した後に破棄できます。

ここ はUnitOfWorkパターン実装の例です。

2番目の方法は、読み取り専用操作に適しています。

18
Backs

ルートルールは次のとおりです。DbContextの有効期間は、実行中のトランザクションに制限する必要があります

ここで、「トランザクション」とは、読み取り専用クエリまたは書き込みクエリを指します。そして、ご存知かもしれませんが、トランザクションはできるだけ短くする必要があります。

そうは言っても、私はあなたがほとんどの場合「使用する」方法を好むにし、プライベートメンバーを使用しないでください。

私がプライベートメンバーを使用するのを見ることができる唯一のケースは、 CQRSパターン(CQRS:仕組みの相互検証) の場合です。

ちなみに、 Jon Gallantの投稿 のDiego Vegaの応答も、賢明なアドバイスを提供します。

サンプルコードが常に「使用」を使用したり、他の方法でコンテキストを破棄したりする主な理由は2つあります。

  1. デフォルトの自動オープン/クローズ動作は、比較的簡単にオーバーライドできます。接続を手動で開くことで、接続をいつ開いたり閉じたりするかを制御できます。コードの一部でこれを開始すると、開いている接続がリークしている可能性があるため、コンテキストを破棄するのを忘れると有害になります。

  2. DbContextは、推奨パターンに従ってIDiposableを実装します。これには、たとえば他のアンマネージリソースをコンテキストのライフタイムに集約する必要がある場合に、派生型がオーバーライドできる仮想保護Disposeメソッドの公開が含まれます。

HTH

12
Dude Pascalou

どのアプローチを使用するかは、リポジトリの責任に依存します。

完全なトランザクションを実行するのはリポジトリの責任ですか?つまり、変更を行ってから、 `SaveChanges?を呼び出してデータベースに変更を保存します。または、それはより大きなトランザクションの一部にすぎないため、変更を保存せずに変更するだけですか?

ケース#1)リポジトリは完全なトランザクションを実行します(変更を加えて保存します):

この場合、2番目のアプローチの方が優れています(2番目のコードサンプルのアプローチ)。

このようなファクトリーを導入することで、このアプローチをわずかに変更します。

public interface IFactory<T>
{
    T Create();
}

public class Repository : IRepository
{
    private IFactory<MyContext> m_Factory;

    public Repository(IFactory<MyContext> factory)
    {
        m_Factory = factory;
    }

    public void AddCustomer(Customer customer)
    {
        using (var context = m_Factory.Create())
        {
            context.Customers.Add(customer);
            context.SaveChanges();
        }            
    }
}

Dependency Injection を有効にするために、このわずかな変更を行っています。これにより、後でコンテキストの作成方法を変更できます。

リポジトリがそれ自体でコンテキストを作成する責任を負わせたくありません。 IFactory<MyContext>を実装するファクトリーは、コンテキストを作成する責任があります。

リポジトリがコンテキストのライフタイムをどのように管理しているか、コンテキストを作成し、いくつかの変更を行い、変更を保存してからコンテキストを破棄することに注意してください。この場合、リポジトリの有効期間はコンテキストより長くなります。

ケース#2)リポジトリは、より大きなトランザクションの一部です(いくつかの変更を行い、他のリポジトリが他の変更を行い、その後、誰かがSaveChangesを呼び出してトランザクションをコミットします):

この場合、最初のアプローチ(質問で最初に説明する)の方が優れています。

これがリポジトリがより大きなトランザクションの一部になる方法を理解することを想像してください:

using(MyContext context = new MyContext ())
{
    repository1 = new Repository1(context);
    repository1.DoSomething(); //Modify something without saving changes
    repository2 = new Repository2(context);
    repository2.DoSomething(); //Modify something without saving changes

    context.SaveChanges();
}

リポジトリごとに新しいインスタンスが使用されることに注意してください。これは、リポジトリの寿命が非常に短いことを意味します。

私はコード内のリポジトリを更新していることに注意してください(これは依存性注入の違反です)。これを例として示しています。実際のコードでは、ファクトリを使用してこれを解決できます。

さて、このアプローチにできる拡張機能の1つは、リポジトリーがSaveChangesInterface Segregation Principle にアクセスできないようにするために、インターフェースの背後にコンテキストを隠すことです。 。

次のようなものがあります。

public interface IDatabaseContext
{
    IDbSet<Customer> Customers { get; }
}

public class MyContext : DbContext, IDatabaseContext
{
    public IDbSet<Customer> Customers { get; set; }
}


public class Repository : IRepository
{
    private IDatabaseContext m_Context;

    public Repository(IDatabaseContext context)
    {
        m_Context = context;
    }

    public void AddCustomer(Customer customer)
    {
        m_Context.Customers.Add(customer);      
    }
}

必要に応じて、インターフェイスに必要な他のメソッドを追加できます。

また、このインターフェースはIDisposableから継承しないことに注意してください。つまり、Repositoryクラスは、コンテキストのライフタイムを管理する責任を負いません。この場合のコンテキストの有効期間は、リポジトリよりも長くなります。他の誰かがコンテキストのライフタイムを管理します。

最初の記事に関する注意:

最初の記事では、質問に記述した最初のアプローチを使用しないことを提案しています(リポジトリにコンテキストを挿入します)。

この記事では、リポジトリの使用方法については明確ではありません。単一のトランザクションの一部として使用されていますか?または、複数のトランザクションにまたがっていますか?

記事が(否定的に)説明しているアプローチでは、リポジトリは多くのトランザクションにまたがる長期実行サービスとして使用されていると推測しています(確信はありません)。この場合、私は記事に同意します。

しかし、ここで提案していることは異なります。このアプローチは、トランザクションの必要があるたびにリポジトリの新しいインスタンスが作成される場合にのみ使用されることを提案しています。

2番目の記事に関する注意:

2番目の記事で話していることは、どのアプローチを使用すべきかとは無関係だと思います。

2番目の記事では、どのような場合でも(リポジトリの設計とは関係なく)コンテキストを破棄する必要があるかどうかについて議論しています。

2つの設計アプローチでは、コンテキストを破棄していることに注意してください。唯一の違いは、そのような処分の責任者です。

この記事では、DbContextはコンテキストを明示的に破棄することなくリソースをクリーンアップするように見えると述べています。

5
Yacoub Massad

リンクした最初の記事は、1つの重要なことを忘れていました。いわゆるNonScalableUserRepostoryインスタンスの存続期間(NonScalableUserRepostoryIDisposableを適切に実装することも忘れていました。 DbContextインスタンスを破棄します)。

次の場合を想像してください:

public string SomeMethod()
{
    using (var myRepository = new NonScalableUserRepostory(someConfigInstance))
    {
        return myRepository.GetMyString();
    }
}

まあ... DbContextクラス内にはまだいくつかのプライベートNonScalableUserRepostoryフィールドがありますが、コンテキストはonce。したがって、この記事でベストプラクティスとして説明しているものとまったく同じです。

したがって、質問は「プライベートメンバーとusingステートメントを使用する必要がありますか?」ではなく、「what」私のコンテキストの寿命ですか?」。

その場合の答えは、できるだけ短くすることです。 nit Of Work という概念があり、これはビジネスオペレーションを表します。基本的に、作業単位ごとに新しいDbContextが必要です。

作業単位がどのように定義され、どのように実装されるかは、アプリケーションの性質に依存します。たとえば、ASP.Net MVCアプリケーションの場合、DbContextのライフタイムは一般にHttpRequestのライフタイムです。つまり、ユーザーが新しいWeb要求を生成するたびに1つの新しいコンテキストが作成されます。


編集:

コメントに答えるには:

1つの解決策は、コンストラクタを介してファクトリメソッドを注入することです。基本的な例を次に示します。

public class MyService : IService
{
    private readonly IRepositoryFactory repositoryFactory;

    public MyService(IRepositoryFactory repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory;
    }

    public void BusinessMethod()
    {
        using (var repo = this.repositoryFactory.Create())
        {
            // ...
        }
    }
}

public interface IRepositoryFactory
{
    IRepository Create();
}

public interface IRepository : IDisposable
{
    //methods
}
4
ken2k

最初のコードは安定性の問題とは関係がありません。悪い理由は、彼が悪いリポジトリーごとに新しいコンテキストを作成するためです。 Webでは、1リクエスト1 dbContextでした。リポジトリパターンを使用する場合は、1リクエスト>多くのリポジトリ> 1 dbContextに変換されます。これはIoCで簡単に実現できますが、必須ではありません。これは、IoCなしで行う方法です。

var dbContext = new DBContext(); 
var repository = new UserRepository(dbContext); 
var repository2 = new ProductRepository(dbContext);
// do something with repo

処分するかどうかについては、私は通常それを処分しますが、リード自体がこれを言った場合、おそらくそれを行う理由はありません。 IDisposableがある場合は破棄するだけです。

2
kirie

基本的に、DbContextクラスは、次のようなデータベース関連のすべてを処理するラッパーにすぎません。1.接続の作成2.クエリの実行。通常のado.netを使用して上記のことを行う場合、usingステートメントでコードを記述するか、接続クラスオブジェクトでclose()メソッドを呼び出すことにより、明示的に接続を適切に閉じる必要があります。

現在、コンテキストクラスは内部的にIDisposableインターフェースを実装しているため、接続を閉じることを気にする必要がないように、usingステートメントでdbcontextを記述することをお勧めします。

ありがとう。

1
Sachin Gaikwad

2番目のアプローチ(使用)は、必要最小限の時間だけ確実に接続を保持し、スレッドセーフにするのが簡単なので、より優れています。

0
Dexion

最初のアプローチの方が優れていると思います。廃棄しても、各リポジトリのdbcontextを作成する必要はありません。ただし、最初のケースでは、databaseFactoryを使用して1つのdbcontextのみをインスタンス化できます。

 public class DatabaseFactory : Disposable, IDatabaseFactory {
    private XXDbContext dataContext;

    public ISefeViewerDbContext Get() {
        return dataContext ?? (dataContext = new XXDbContext());
    }

    protected override void DisposeCore() {
        if (dataContext != null) {
            dataContext.Dispose();
        }
    }
}

そして、リポジトリでこのインスタンスを使用します。

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    private IXXDbContext dataContext;

    private readonly DbSet<TEntity> dbset;

    public Repository(IDatabaseFactory databaseFactory) {
        if (databaseFactory == null) {
            throw new ArgumentNullException("databaseFactory", "argument is null");
        }
        DatabaseFactory = databaseFactory;
        dbset = DataContext.Set<TEntity>();
    }

    public ISefeViewerDbContext DataContext {
        get { return (dataContext ?? (dataContext = DatabaseFactory.Get()));
    }

    public virtual TEntity GetById(Guid id){
        return dbset.Find(id);
    }
....
0
Donald

最初の方法(dbContextの挿入)を使用します。もちろん、それはIMyDbContextである必要があり、依存関係注入エンジンはコンテキストのライフサイクルを管理しているため、必要な間のみ有効です。

これにより、テストのためにコンテキストをモックアウトできます。2番目の方法では、使用するコンテキストのデータベースなしでエンティティを調べることができません。

0
Loofer