web-dev-qa-db-ja.com

Entity Framework 6を​​リポジトリパターンで使用しないでください。

したがって、私は以下を読んだ後にこれを尋ねています: Entity Frameworkでリポジトリパターンを使用しないのはなぜですか?

イェイと言う人とイェと言う人が大勢分かれているようです。 codeであれ、正当な理由であれ、何であれ、いくつかの回答に欠けているように見えるのは、具体的な例です。

問題は、「まあEFはすでに抽象化されている」と言っている人々を読み続けていることです。まあそれは素晴らしいことであり、それはおそらく本当ですが、リポジトリパターンなしでどのように使用するのでしょうか。

そうでないと言う人のために、なぜそうでないとあなたは言うのでしょうか?

8
MZawg

これを解決するために、私はEntity Frameworkの大きな支持者ですが、注意が必要ないくつかの欠点が伴います。

長い回答もお詫びしますが、これは非常にホットなトピックであり、多くの意見と多くの必要な考慮事項があります。小さなアプリケーションの場合、これらの考慮事項の多くは重要ではありませんが、エンタープライズグレードのアプリケーションの場合、それらは非常に重要です。

EFディスカッションをこれほどホットなトピックにしている理由の1つは、それが一連​​のイベントにつながり、各ソリューションが新しい問題を引き起こすことです(これは、より高度なケースにのみ適用される場合があります)。私があなたに最後の(または現在のことを言えば)答えを与えた場合、私は他のいくつかの解決策を省略していたと思うので、解決策を順を追って説明することと、それらが問題の最終的な解決策ではないことは適切だと思います。


リポジトリ

リポジトリパターンなしでどのように使用しますか?

これに対する簡単な答えは、(単純な)リポジトリはEntity Frameworkに対するアンチパターン*であるということです。

EFは、データベース全体へのアクセスを本質的に提供するコンテキストを提供します。たとえば、 Provinceエンティティがすでに入力されているすべてのCountryエンティティを返すクエリを起動します。各州のCityエンティティはすでに入力されています。つまり、複数のエンティティタイプのクエリを実行できます(これは、私が作成したフレーズです。リポジトリとの違いを説明します)。

リポジトリは、少なくともその基本的な実装では、「リポジトリごとに1つのエンティティタイプ」のアプローチを取る傾向があります。すべての国のすべての州とすべての州の都市のリストを取得する場合は、CountryRepositoryProviceRepositoryCityRepositoryに個別に話す必要があります。 。つまり、リポジトリでは、単一エンティティタイプのクエリしか実行できないように制限されています。同じ例の場合、すべての国とその都道府県と都市を取得するには、3つの個別のデータベースクエリを起動する必要があります。

誤解しないでください。リポジトリが好きです。きちんとした小さなボックスが好きなので、さまざまなドメインオブジェクトのストレージを分離できます。データベースから国を取得できますが、リモートAPIから州を取得し、2番目のリモートAPIの都市を取得します。

しかし、エンティティタイプを独自のプライベートボックスに分割することは、リレーショナルデータベースを使用することの利点とはかなり相反します。その利点の一部は、関連するエンティティを考慮に入れることができる単一のクエリを起動できることです(フィルタリング、並べ替え、または返すため)。 。

"リポジトリはまだ複数のエンティティタイプを返すことができる"と正しく応答するかもしれません。そして、あなたは正しいでしょう。しかし、FooエンティティとBarエンティティの両方を返すクエリがある場合、どこに配置しますか? FooRepositoryBarRepository?選択が簡単な例もあるかもしれませんが、選択が難しい例もいくつかあり、複数の開発者が異なる分類方法を使用している可能性があるため、コードベースに一貫性がなくなり、「リポジトリごとに1つのエンティティタイプ」アプローチの真の目的は窓から投げ出されました。


*リポジトリがアンチパターンであると私が言うとき、それはグローバルなステートメントではなく、EFの目的を明確に打ち消すものではありません。 EFまたは同様のソリューションがなければ、リポジトリはアンチパターンではありません。


クエリオブジェクト

クエリオブジェクトは、「リポジトリごとに1つのエンティティタイプ」アプローチを回避するための唯一の実際の方法です。クエリオブジェクトが何であるかを説明する最も簡単な方法は、「1つのメソッドリポジトリ」と考えることです。

リポジトリーは複数のタイプのエンティティーを処理する必要があるという欠点があり、リポジトリーが持つメソッドが多いほど、処理されるエンティティー・タイプが明確になります。各リポジトリメソッドを独自のクエリオブジェクトに分離することで、「このリポジトリは1つのタイプのみを処理する」という矛盾する提案を削除し、代わりに、「このクエリオブジェクトはどのエンティティタイプに関係なくこの特定のクエリを実行する」という提案をしています。それを使用する必要があります。」.

リポジトリを同時に使用することができ、リポジトリが指定されたエンティティタイプ以上を処理しないようにするenforceことができます。

  • クエリが複数のエンティティタイプ(例:CountryProvince)を使用する場合、それは独自のプライベートクエリオブジェクト(例:CountriesAndTheirProvincesQuery)に属します。
  • クエリが1つのエンティティタイプ(例:Country)のみに焦点を当てている場合、そのエンティティタイプのリポジトリ(例:CountryRepository)に属しています。

技術レベルでは、クエリオブジェクトはリポジトリと同じように機能します。唯一の違いは、複数のエンティティタイプのクエリが単一のエンティティタイプのリポジトリに属しているように見せかけて、ロジックを別々に分離することです。


リポジトリ2

リポジトリに関連する2番目の問題があります。これらは別個のクラスであるため、相互に依存しません。これは通常、各リポジトリが独自のEFコンテキストを使用することも意味します(ここでは、依存関係の注入を省略して、回答の焦点を回避しています)。

データベースに国と都市を追加するインポートを実行するとします。ただし、トランザクションの安全性が必要です。つまり、any障害が発生した場合、nothingをデータベースに保存する必要があります。
しかし、それぞれが独自のコンテキストを持つ2つのリポジトリを処理する必要がある場合、他のコンテキストのSaveChanges()が成功したことを知る前に、1つのコンテキストでSaveChanges()を意図的に呼び出すにはどうすればよいでしょうか。 ?推測する必要があり、2番目のコンテキストのコミットが失敗すると、最初のコンテキストのコミットを手動で元に戻せなくなります。

リポジトリを分離することで、sharedコンテキストを持つ機能を削除しました。これは、複数のエンティティタイプで動作するトランザクションを処理するときに必要になります。同時に。


作業単位

私がリポジトリとEFを使用した十分に大きなコードベースまたはドメインでは、トランザクションの安全性の問題に少なくともいくらか対抗するために、作業ユニットを実装することになりました。

簡単に言えば、作業単位はすべてのリポジトリのコレクションであり、リポジトリが同じコンテキストを共有するように強制し、開発者がすべてのリポジトリのコンテキストを同時に直接コミット/ロールバックできるようにします。簡単な例:

public class UnitOfWork : IDisposable
{
    public readonly FooRepository FooRepository;
    public readonly BarRepository BarRepository;
    public readonly BazRepository BazRepository;

    private readonly MyContext _context;

    public UnitOfWork()
    {
        _context = new MyContext();

        this.FooRepository = new FooRepository(_context);
        this.BarRepository = new BarRepository(_context);
        this.BazRepository = new BazRepository(_context);
    }

    public void Commit()
    {
        _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

そして簡単な使用例:

using (var uow = new UnitOfWork())
{
    uow.FooRepository.Add(myFoo);
    uow.BarRepository.Update(myBar);
    uow.BazRepository.Delete(myBaz);

    uow.Commit();
}

そして今、トランザクションの安全性があります。 3つのオブジェクトすべてがデータベースで処理されるか、いずれも処理されません。


しかし、Entity Frameworkはフレームワークです! (個人メモ)

気づいたかもしれないし、気づかなかったかもしれませんが、EFのDbContextと私が作成したばかりのUnitOfWorkとの類似性が高いはずです。それらは本質的に同じものです。これらはデータベースへの単一のトランザクションを表し、使用可能なすべてのエンティティタイプのコレクションへのアクセスを提供します。

public class UnitOfWork
{
    public readonly FooRepository FooRepository;
    public readonly BarRepository BarRepository;
    public readonly BazRepository BazRepository;

    public void Commit() { }
}

public class MyContext : DbContext
{
    public Set<Foo> Foos { get; private set; }
    public Set<Bar> Bars { get; private set; }
    public Set<Baz> Bazs { get; private set; }

    public int SaveChanges() { }
}

EFのDbContextは、 作業単位の定義 を満足させます。

作業ユニットは、ビジネストランザクション中にデータベースに影響を与える可能性のあるすべてのことを追跡します。完了すると、作業の結果としてデータベースを変更するために実行する必要があるすべてのことがわかります。

では、なぜこれを行うのですか?簡単に言えば、開発者は常に依存関係を抽象化しようとするためです。ビジネスレイヤーがEFに直接依存することは望ましくありません。これは、そもそもリポジトリを作成しているのとまったく同じ理由です。ビジネスロジックがEFを直接使用しないようにするためです。

しかし、それのすべての意味は何ですか? EF、アンチパターン化されたリポジトリ、アンチアンチパターン化された作業単位を使用してすべてを機能させるのはなぜですか?これには非常に多くの労力がかかります。 EFがスローする任意のラムダメソッドを(かなり)解析するEFの機能に本質的に依存する代わりに、手動で検索フィルターを作成する必要があります。 箱から出してすぐに動作するように意図されている方法でEFを使用する代わりに、なぜこのすべての取り組みを行うのですか?

そして、私は長い間この質問をしてきたことを認めざるを得ませんが、私の意見にはほとんど賛成できません。少しの間soapboxを許可してくれたら、問題に関する私の意見は、これがEFがエンティティライブラリではなくエンティティFrameworkと呼ばれる理由であるということです。
フレームワークとライブラリの違いは、多くの場合意味論的であり、議論の余地がありますが、賛成の線を引くことができると思います ここで説明しているように

ライブラリは特定の明確に定義された操作を実行します。

フレームワークは、アプリケーションがスケルトンに記入することによって操作の「肉」を定義するスケルトンです。スケルトンにはパーツをリンクするコードがまだありますが、最も重要な作業はアプリケーションによって行われます。

このフレームワークの説明は、EFに適合します。 DBとのやり取りはほぼ完全に行われますが、EFで使用する予定のエンティティ(およびモデル構成)を使用してDbContextを拡張する必要があります。

依存関係(ライブラリー)を抽象化できるのは、それが可能であり、そうすることの利点(スワップ可能性)が欠点(抽象化の実装に必要な労力)をはるかに上回るためです。しかし、システムの骨格であるフレームワークは、簡単に抽象化できないため、簡単に置き換えることはできません。この作業は、依存関係を置き換える必要がある可能性よりもはるかに大きいため、これを行う価値はありません。

多くのボイレル化するコードをカットするためには、EFを、アプリケーションを構築し、簡単に移動できない(ライブラリの場合と同じように)ことができないフレームワークと見なすことが有益だと思います。これは、リポジトリと作業ユニットを完全に廃止できることを意味します。それらの唯一の目的は、EFがすでに持っている機能へのアクセスを提供することです。代わりにEFを直接使用し、EFからの移行を容易にすることを意図して実装しないアーキテクチャ上の選択であることを受け入れます。

つまり、リポジトリと作業単位を切り捨て、代わりにビジネスロジックでコンテキストを直接処理することができます。ビジネスロジックコードがほとんど変更されていないことに注意してください。

// OLD

using (var uow = new UnitOfWork())
{
    uow.FooRepository.Add(myFoo);
    uow.BarRepository.Update(myBar);
    uow.BazRepository.Delete(myBaz);

    uow.Commit();
}

// NEW

using (var db = new MyContext())
{
    db.Foos.Add(myFoo);
    db.Bars.Update(myBar);
    db.Bazs.Delete(myBaz);

    db.SaveChanges();
}

問題は、「まあEFはすでに抽象化されている」と言っている人々を読み続けていることです。まあそれは素晴らしいことであり、それはおそらく本当ですが、リポジトリパターンなしでどのように使用するのでしょうか。

EFを直接使用し、自己開発したリポジトリー(および場合によっては作業単位)の壁の背後でEFを抽象化しようとしないこと。

そうでないと言う人のために、なぜそうでないとあなたは言うのでしょうか?

答えは、過去6〜7年間のEFでの私の経験の要約です。基本的なリポジトリ自体は、解決するよりも多くの問題を引き起こします。基本的なリポジトリによって引き起こされる問題を解決する高度なソリューションがあります。しかし、最終的には、リポジトリを使用しないことを選択する方が良いのではないかと疑問に思うようになり、EFを適切に使用するために労力を費やす必要がなくなります。

彼らはEFでうまく遊ぶように作ることができますか?確実なこと。そのような抽象化をすべて作成するのは、努力する価値がありますか?それは、EFから離れる(またはEFと互換性のないデータストアを使用する)可能性に大きく依存します。

23
Flater

エンティティフレームワークは完全な抽象化ではなく、部分的な抽象化です。データのクエリを抽象化するだけです。

たとえば、Orderエンティティがあり、アプリケーションが成長するにつれて、それがパフォーマンスのボトルネックになる場合はどうでしょう。次に、DBAはそれを2つのテーブルに分割することを提案します。過去30日以内に行われた注文の場合はcurrent_order、それ以外の場合はolder_orderです。さらに複雑にし、パフォーマンスをさらに向上させるために、DBAはcurrent_order int16およびolder_order int64 PKのPKを作成するため、単一のエンティティを表す2つのテーブルに同じスキーマさえありません。 。

さらに複雑なことに、一部の注文は処理に30日以上かかります。 Entity Frameworkはこの複雑さをどのように抽象化しますか?ヒント:それはしません、そしてリポジトリパターンを使用してデータアクセスを抽象化しない限り、この変更はコードを混乱させようとしています。

PostgresからMongoDbに切り替える必要がある場合はどうなりますか? EFはMongoDBをサポートしていません。その抽象化が進んでいます。

実際、EFはアクティブレコードパターンを使用しているため、データソースに直接リンクされており、データクエリを単純化するためのツールにすぎないため、データ抽象化をまったく提供しないと言っても過言ではありません。

3
TheCatWhisperer