新しいアプリのジェネリックリポジトリを作成する利点についていくつかの記事を読んでいました( 例 )。同じリポジトリを使用して、いくつかの異なるエンティティタイプに対して一度にいくつかのことを実行できるので、アイデアはいいようです。
IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();
ただし、リポジトリの残りの実装はLinqに大きく依存しています。
IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on
このレポジトリパターンはEntity Frameworkにとって素晴らしい働きをし、DbContext/DbSetで利用可能なメソッドの1対1のマッピングをほぼ提供しました。しかし、Entity Frameworkの外の他のデータアクセステクノロジーでのLinqの取り込みが遅いことを考えると、DbContextを直接操作する場合と比べて、どのような利点がありますか?
PetaPoco バージョンのリポジトリを作成しようとしましたが、PetaPocoはLinq式をサポートしていないため、基本的なGetAll、GetById、Addにのみ使用しない限り、汎用IRepositoryインターフェイスの作成はほとんど役に立ちません。 、Update、Delete、およびSaveメソッドを使用して、基本クラスとして使用します。次に、特別なメソッドを使用して特定のリポジトリを作成し、以前に述語として渡すことができるすべての「where」句を処理する必要があります。
Generic RepositoryパターンはEntity Framework以外の何かに役立ちますか?そうでない場合、Entity Frameworkを直接操作する代わりに、なぜそれを使用するのでしょうか。
元のリンクは、サンプルコードで使用していたパターンを反映していません。これは( 更新されたリンク )です。
汎用リポジトリは、Entity Frameworkにとって 役に立たない場合でも (そしてIMHOも悪い)です。 IDbSet<T>
によってすでに提供されているもの(これは汎用リポジトリです)に追加の値をもたらすものではありません。
汎用リポジトリを他のデータアクセステクノロジーの実装で置き換えることができるという議論はすでにあるので、独自のLinqプロバイダーの作成を要求できるため、かなり弱いです。
単純化された単体テストに関する2番目の一般的な引数は これも間違っています です。これは、リポジトリをモックする/インメモリデータストレージで設定すると、Linqプロバイダーが別の機能を持つ別のプロバイダーに置き換えられるためです。 Linq-to-entitiesプロバイダーは、Linq機能のサブセットのみをサポートしています。IQueryable<T>
インターフェースで利用可能なすべてのメソッドをサポートしているわけではありません。データアクセスレイヤーとビジネスロジックレイヤーの間で式ツリーを共有すると、データアクセスレイヤーの偽造を防ぐことができます。クエリロジックは分離する必要があります。
強力な「汎用」抽象化が必要な場合は、他のパターンも含める必要があります。この場合、リポジトリによって、使用されるデータアクセスレイヤーでサポートされている特定のクエリ言語に変換できる抽象クエリ言語を使用する必要があります。これは仕様パターンで処理されます。 IQueryable
のLinqは仕様です(ただし、変換にはプロバイダー、または式ツリーをクエリに変換するカスタム訪問者が必要です)独自の簡略化バージョンを定義して使用できます。たとえば、NHibernateはCriteria APIを使用します。それでも最も簡単な方法は、特定の方法で特定のリポジトリを使用することです。この方法は、クエリロジックが完全に非表示にされ、抽象化の背後に分離されているため、実装が最も簡単で、テストが最も簡単で、ユニットテストでの偽造が最も簡単です。
問題はリポジトリのパターンではありません。データの取得と取得方法を抽象化することは良いことです。
ここでの問題は実装です。フィルタリングのために任意の式が機能すると仮定すると、せいぜい危険なだけです。
すべてのオブジェクトのリポジトリを直接動作させることは、要点を逃してしまいます。データオブジェクトがビジネスオブジェクトに直接マッピングされることはほとんどありません。これらの状況では、Tをフィルターに渡すことはあまり意味がありません。そして、その多くの機能を提供することで、別のプロバイダーが登場すると、それらのすべてをサポートできないことがほぼ保証されます。
一般的なデータレイヤー(リポジトリは特定の種類のデータレイヤー)の価値により、コードは、呼び出し元のコードにほとんどまたはまったく影響を与えずに、基になるストレージメカニズムを変更できます。
理論的には、これはうまく機能します。実際には、あなたが観察したように、抽象化はしばしば漏れやすいです。 1つのデータへのアクセスに使用されるメカニズムは、別のメカニズムとは異なります。場合によっては、コードを2回記述することになります。1回はビジネスレイヤーで、もう1回はデータレイヤーで繰り返します。
汎用データレイヤーを作成する最も効果的な方法は、アプリケーションが事前に使用するさまざまな種類のデータソースを知ることです。見てきたように、LINQまたはSQLが普遍的であると仮定することは問題になる可能性があります。新しいデータストアを改造しようとすると、おそらく書き換えが発生します。
[編集:以下を追加。]
また、アプリケーションがデータ層から何を必要としているかにも依存します。アプリケーションがオブジェクトをロードまたは格納するだけの場合、データレイヤーは非常にシンプルになります。検索、並べ替え、およびフィルター処理の必要性が高まると、データレイヤーの複雑さが増し、抽象化が漏洩し始めます(問題のLINQクエリを公開するなど)。ただし、ユーザーが独自のクエリを提供できるようになったら、データレイヤーのコスト/メリットを慎重に検討する必要があります。
データベースの上にコードレイヤーを置くことは、ほとんどすべての場合に価値があります。私は通常、上記のコードで "GetByXXXXX"パターンを使用することをお勧めします。これにより、UIをデータインターフェイスの煩雑さから解放しながら、背後でクエリを最適化できます。
ジェネリックスを利用することは間違いなく公平なゲームです。Load<T>(int id)
メソッドを使用することは非常に理にかなっています。しかし、LINQの周りにリポジトリを構築することは、SQLクエリをすべての場所にドロップすることと同等で、タイプセーフが少し追加された2010年代の同等物です。
まあ、提供されたリンクを使用すると、DataServiceContext
の便利なラッパーであることがわかりますが、コード操作を減らしたり、読みやすさを向上させたりすることはありません。さらに、_DataServiceQuery<T>
_へのアクセスが妨げられ、.Where()
および.Single()
への柔軟性が制限されます。 AddRange()
や代替案も提供されていません。便利なDelete(Predicate)
も提供されていません(Joe-sを削除するにはrepo.Delete<Person>( p => p.Name=="Joe" );
)。等。
結論:そのようなAPIはネイティブAPIを妨害し、いくつかの単純な操作に制限します。