常にリポジトリパターンを使用していましたが、最新のプロジェクトでは、その使用と「作業単位」の実装を完璧に行えるかどうかを確認したかったのです。掘り始めると、次の質問を自問し始めました。「本当に必要ですか?」
これはすべて、Stackoverflowに関するコメントから始まり、Ayende Rahienの彼のブログへの投稿へのトレースで、具体的には2つ、
これはおそらく永遠に語られる可能性があり、さまざまなアプリケーションに依存します。知りたいこと、
これは、拡張メソッドを使用して簡単に実行できます。すっきりとシンプルで再利用可能。
public static IEnumerable GetAll(
this ISession instance, Expression<Func<T, bool>> where) where T : class
{
return instance.QueryOver().Where(where).List();
}
このアプローチとNinject
をDIとして使用すると、Context
をインターフェイスにして、コントローラーに挿入する必要がありますか?
私は多くの道をたどり、さまざまなプロジェクトでリポジトリの多くの実装を作成しました...タオルを投げ捨てて、あきらめました。これが理由です。
例外のコーディング
データベースが1つのテクノロジーから別のテクノロジーに変更される可能性が1%になるようにコーディングしていますか?あなたのビジネスの将来の状態について考えており、それが可能性があると言っている場合、a)彼らは別のDBテクノロジーへの移行を行う余裕があるか、b)楽しみのためにDBテクノロジーを選択するかc )使用することに決めた最初のテクノロジーで何かがひどく間違っていました。
なぜリッチLINQ構文を捨てるのですか?
LINQとEFは、オブジェクトグラフを読み取ってトラバースするためにきちんと処理できるように開発されました。同じ柔軟性を提供できるリポジトリを作成して維持するのは、大変な作業です。私の経験では、リポジトリを作成したときはいつでも[〜#〜] always [〜#〜]クエリをよりパフォーマンスの高いものにしたり、削減したりするために、リポジトリレイヤーにビジネスロジックがリークしていましたデータベースへのヒット数。
私が書かなければならないクエリのすべての順列に対してメソッドを作成したくありません。ストアドプロシージャを作成することもできます。 GetOrder
、GetOrderWithOrderItem
、GetOrderWithOrderItemWithOrderActivity
、GetOrderByUserId
などは必要ありません...メインエンティティを取得してトラバースし、オブジェクトグラフを含めてください。
リポジトリのほとんどの例はでたらめです
ブログのような本当に骨の折れるものや、リポジトリパターンを取り巻くインターネットで見つけた例の90%ほど簡単なクエリを作成する場合を除きます。私はこれを十分に強調することはできません!これは、泥の中をクロールして把握する必要があるものです。作成した完全に考え抜かれたリポジトリ/ソリューションを破壊するクエリが常に1つあります。そして、自分自身を推測し、技術的な負債/侵食が始まるその時点までではありません。
仲間をユニットテストしないでください
しかし、リポジトリがない場合の単体テストについてはどうでしょうか?どうすればいいですか?単純なことではありません。両方の角度から見てみましょう:
リポジトリなし-DbContext
またはその他のトリックを使用してIDbContext
をモックできますが、実際には単体テストLINQ to ObjectsではなくLINQ to Entitiesクエリは実行時に決定されるため... OKしたがって、これをカバーするのは統合テスト次第です。
リポジトリを使用する-リポジトリをモックし、その間のレイヤーを単体テストできるようになりました。いいね?そうではありません...上記の場合、クエリのパフォーマンスを向上させたり、データベースへのヒットを少なくするためにロジックをリポジトリレイヤーにリークする必要がある場合、単体テストではどのように対応できますか?これはレポジトリレイヤーにあり、_IQueryable<T>
_をテストしたくないのですか?また、正直に言って、ユニットテストは20行の.Where()
句を持つクエリをカバーしません。また、.Include()
は一連のリレーションシップであり、これをすべて行うためにデータベースに再度ヒットします。クエリは実行時に生成されるため、とにかく何とか、何とか、何とか。また、上位層の永続性を無視するリポジトリを作成したため、データベーステクノロジーを変更する場合、統合テストに戻って、実行時に同じ結果が得られることをユニットテストで保証できないことは間違いありません。そのため、リポジトリの全体のポイントは奇妙に思えます。
2セント
プレーンストアドプロシージャ(一括挿入、一括削除、CTEなど)でEFを使用すると、既に多くの機能と構文が失われますが、C#でコーディングするため、バイナリを入力する必要はありません。 EFを使用することで、さまざまなプロバイダーを使用し、オブジェクトグラフをさまざまなものの中で関連性の高い方法で操作できるようになります。特定の抽象化は有用であり、いくつかはそうではありません。
リポジトリパターンは、abstractionです。その目的は、複雑さを軽減し、残りのコードを無知にすることです。ボーナスとして、integrationテストの代わりにunitテストを記述できます。
問題は、多くの開発者がパターンの目的を理解できず、永続性固有の情報を呼び出し側に漏らすリポジトリを作成できないことです(通常、IQueryable<T>
)。そうすることで、OR/Mを直接使用するよりも利点が得られません。
例外のコーディング
リポジトリを使用することは、永続化テクノロジーを切り替えることができることではありません(つまり、データベースを変更するか、代わりにWebサービスを使用するなど)。複雑さと結合を減らすために、ビジネスロジックを永続性から分離することです。
単体テストと統合テスト
リポジトリの単体テストは作成しません。期間。
ただし、リポジトリー(または永続性とビジネス間のその他の抽象化レイヤー)を導入することにより、ビジネスロジックの単体テストを作成できます。つまり、データベースの構成が正しくないためにテストが失敗することを心配する必要はありません。
クエリに関しては。 LINQを使用する場合、リポジトリで行うのと同じように、クエリが機能することも確認する必要があります。そしてそれは統合テストを使用して行われます。
違いは、ビジネスとLINQステートメントを混在させていない場合、永続コードが失敗し、他の何かではないことを100%確信できることです。
テストを分析すると、懸念事項が混在していない場合(つまり、LINQ +ビジネスロジック)
リポジトリの例
ほとんどの例はでたらめです。それは非常に真実です。ただし、任意のデザインパターンをグーグルで検索すると、安っぽい例がたくさん見つかります。これは、パターンの使用を避ける理由にはなりません。
正しいリポジトリ実装の構築は非常に簡単です。実際、次の1つのルールに従うだけで済みます。
必要な瞬間までリポジトリクラスに何も追加しないでください
多くのコーダーは怠け者であり、汎用リポジトリを作成し、多くのメソッドを持つ基本クラスを使用しようとしますmight必要。ヤグニ。リポジトリクラスを1回作成し、アプリケーションが存続する限り保持します(何年もかかる場合があります)。怠け者でそれを性交する理由。基本クラスを継承せずにクリーンに保ちます。これにより、読みやすく保守しやすくなります。
(上記の説明はガイドラインであり、法律ではありません。基本クラスは非常によく動機づけられます。追加する前に考えて、正しい理由で追加してください)
結論:
ビジネスコードにLINQステートメントを含めたり、単体テストを気にしたりしない場合は、Entity Frameworkを直接使用しない理由はありません。
更新
リポジトリパターンと「抽象化」が実際に意味するものの両方についてブログに投稿しました。 http://blog.gauffin.org/2013/01/repository-pattern-done-right/
更新2
20以上のフィールドを持つ単一エンティティタイプの場合、並べ替えの組み合わせをサポートするためにクエリメソッドをどのように設計しますか名前だけで検索を制限したり、ナビゲーションプロパティを使用して検索したり、特定の価格コードを持つアイテムを含むすべての注文を一覧表示したり、ナビゲーションプロパティを3レベル検索したりする必要はありません。
IQueryable
が発明された全体の理由は、データベースに対する検索の任意の組み合わせを作成できるようにするためでした。理論的にはすべてが素晴らしく見えますが、ユーザーのニーズは理論を上回ります。
繰り返しますが、20個以上のフィールドを持つエンティティが誤ってモデル化されています。 GODエンティティです。それを分解します。
私はIQueryable
が研磨のために作られたのではないと主張しているのではありません。 Repository patternのような抽象化レイヤーには適さないと言っています。 100%完全なLINQ To Sqlプロバイダー(EFなど)はありません。
これらはすべて、熱心な/遅延読み込みの使用方法やSQL "IN"ステートメントの実行方法など、実装固有のものがあります。リポジトリでIQueryable
を公開すると、ユーザーはそれらすべてを知る必要があります。したがって、データソースを抽象化しようとする試み全体が完全な失敗です。 OR/Mを直接使用するよりも、利益を得ることなく複雑さを追加するだけです。
Repositoryパターンを正しく実装するか、まったく使用しないでください。
(本当に大きなエンティティを処理したい場合は、リポジトリパターンと 仕様パターン を組み合わせることができます。これにより、テスト可能な完全な抽象化が得られます。)
IMOは、Repository
抽象化とUnitOfWork
抽象化の両方が、意味のある開発において非常に貴重な場所を持っています。人々は実装の詳細について議論しますが、猫の皮を剥ぐ方法がたくさんあるように、抽象化を実装する方法もたくさんあります。
あなたの質問は、具体的に使用するかどうか、そしてその理由です。
Entity Frameworkにこれらのパターンの両方が既に組み込まれていることは間違いありませんが、DbContext
はUnitOfWork
であり、DbSet
はRepository
です。 UnitOfWork
またはRepository
自体を単体テストする必要はありません。クラスと基になるデータアクセスの実装との間を単純に単純化するためです。サービスのロジックを単体テストするときに、これらの2つの抽象化をあざけることを何度も繰り返す必要があります。
テストを実行するロジックとロジックの間に、コード依存関係のレイヤー(制御しない)を追加する外部ライブラリを使用して、モック、偽造などを行うことができます。テスト中。
マイナーポイントは、UnitOfWork
およびRepository
を独自に抽象化することで、ユニットテストをモックするときに最大限の制御と柔軟性が得られることです。
すべて非常にうまくいきますが、私にとって、これらの抽象化の本当の力は、アスペクト指向プログラミング技術を適用し、SOLID原則を遵守する簡単な方法を提供することです。
IRepository
があります:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
そしてその実装:
public class Repository<T> : IRepository<T>
where T : class
{
private readonly IDbSet<T> _dbSet;
public Repository(PPContext context)
{
_dbSet = context.Set<T>();
}
public T Add(T entity)
{
return _dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
}
これまでのところ普通のことは何もありませんが、今度はロギングを追加したいと思います-ロギングDecoratorで簡単に。
public class RepositoryLoggerDecorator<T> : IRepository<T>
where T : class
{
Logger logger = LogManager.GetCurrentClassLogger();
private readonly IRepository<T> _decorated;
public RepositoryLoggerDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
T added = _decorated.Add(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
return added;
}
public void Delete(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
_decorated.Delete(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable();
}
}
すべて完了し、既存のコードに変更はありません。例外処理、データキャッシュ、データ検証など、設計およびビルドプロセス全体など、追加できる横断的な懸念事項は他にも数多くあります。既存のコードを一切変更しないシンプルな機能がIRepository
abstractionです。
今、StackOverflowでこの質問を見てきました-「Entity Frameworkをマルチテナント環境でどのように動作させるのですか?」。
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Repository
抽象化がある場合、答えは「簡単にデコレータを追加する」です
public class RepositoryTennantFilterDecorator<T> : IRepository<T>
where T : class
{
//public for Unit Test example
public readonly IRepository<T> _decorated;
public RepositoryTennantFilterDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
return _decorated.Add(entity);
}
public void Delete(T entity)
{
_decorated.Delete(entity);
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable().Where(o => true);
}
}
IMOでは、いくつかの場所以外で参照されるサードパーティのコンポーネント上に常に単純な抽象化を配置する必要があります。この観点から、ORMは多くのコードで参照されているため、完璧な候補です。
通常、誰かが「なぜこれまたはそのサードパーティのライブラリを抽象化する必要があるのか(たとえば、Repository
)」と言うときに思い浮かぶ答えは、「なぜですか?」
追伸 SimpleInjector などのデコレータは、IoCコンテナを使用して簡単に適用できます。
[TestFixture]
public class IRepositoryTesting
{
[Test]
public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
{
Container container = new Container();
container.RegisterLifetimeScope<PPContext>();
container.RegisterOpenGeneric(
typeof(IRepository<>),
typeof(Repository<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryLoggerDecorator<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryTennantFilterDecorator<>));
container.Verify();
using (container.BeginLifetimeScope())
{
var result = container.GetInstance<IRepository<Image>>();
Assert.That(
result,
Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
Assert.That(
(result as RepositoryTennantFilterDecorator<Image>)._decorated,
Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
}
}
}
まず第一に、いくつかの答えで示唆されているように、EF自体はリポジトリパターンであり、リポジトリとして名前を付けるためだけにさらに抽象化を作成する必要はありません。
単体テスト用のモック可能なリポジトリ、本当に必要ですか?
EFは単体テストでテストDBと通信し、SQLテストDBに対してビジネスロジックを直接テストします。リポジトリパターンのモックを持つメリットはまったくありません。テストデータベースに対して単体テストを行うのは本当に間違っていますか?一括操作ができないため、生のSQLを作成することになります。メモリ内のSQLiteは、実際のデータベースに対して単体テストを行うのに最適な候補です。
不必要な抽象化
将来、EFをNHbibernateなどまたは他のものに簡単に置き換えることができるように、リポジトリを作成しますか?素晴らしい計画に聞こえますが、本当に費用対効果は高いですか?
Linqは単体テストを殺しますか?
私はそれがどのように殺すことができるかについての例を見てみたい。
依存性注入、IoC
うわー、これらは素晴らしい言葉です。確かに理論的には見栄えが良いのですが、時には優れたデザインと優れたソリューションの間でトレードオフを選択する必要があります。私たちはそのすべてを使用しましたが、結局、すべてをゴミ箱に捨てて、異なるアプローチを選択しました。サイズと速度(コードのサイズと開発の速度)は、実際の生活において非常に重要です。ユーザーは柔軟性を必要としますが、DIやIoCの点でコードのデザインが優れていてもかまいません。
Visual Studioを構築している場合を除き
多くの人が開発するVisual StudioやEclipseなどの複雑なプログラムを構築する場合、これらの優れた設計はすべて必要であり、高度にカスタマイズできる必要があります。すべての優れた開発パターンは、これらのIDEが長年の開発を経て明らかになり、これらの優れたデザインパターンがすべて重要な場所で進化しました。ただし、単純なWebベースの給与計算、または単純なビジネスアプリを実行している場合は、数百人のユーザーにのみ展開される百万人のユーザーに時間をかけて構築するのではなく、時間の経過とともに開発を進化させることをお勧めします。
フィルターされたビューとしてのリポジトリ-ISecureRepository
一方、リポジトリは、現在のユーザー/ロールに基づいて必要なフィラーを適用することにより、データへのアクセスを保護するEFのフィルタービューである必要があります。
しかし、そうするとリポジトリがさらに複雑になり、維持するための巨大なコードベースになってしまいます。ユーザーは、異なるユーザータイプまたはエンティティタイプの組み合わせに対して異なるリポジトリを作成することになります。これだけでなく、多くのDTOもあります。
次の答えは、クラスとメソッドのセット全体を作成せずにフィルターされたリポジトリの実装例です。質問に直接答えることはできませんが、質問を導き出すのに役立ちます。
免責事項:私はEntity REST SDK。
http://entityrestsdk.codeplex.com
上記を念頭に置いて、CRUD操作のフィルターを保持するSecurityContextに基づいてフィルタービューのリポジトリを作成するSDKを開発しました。また、複雑な操作を簡素化するルールは2種類のみです。 1つはエンティティへのアクセス、もう1つはプロパティの読み取り/書き込みルールです。
利点は、さまざまなユーザータイプのビジネスロジックやリポジトリを書き換えずに、単にアクセスをブロックまたは許可するだけです。
public class DefaultSecurityContext : BaseSecurityContext {
public static DefaultSecurityContext Instance = new DefaultSecurityContext();
// UserID for currently logged in User
public static long UserID{
get{
return long.Parse( HttpContext.Current.User.Identity.Name );
}
}
public DefaultSecurityContext(){
}
protected override void OnCreate(){
// User can access his own Account only
var acc = CreateRules<Account>();
acc.SetRead( y => x=> x.AccountID == UserID ) ;
acc.SetWrite( y => x=> x.AccountID == UserID );
// User can only modify AccountName and EmailAddress fields
acc.SetProperties( SecurityRules.ReadWrite,
x => x.AccountName,
x => x.EmailAddress);
// User can read AccountType field
acc.SetProperties<Account>( SecurityRules.Read,
x => x.AccountType);
// User can access his own Orders only
var order = CreateRules<Order>();
order.SetRead( y => x => x.CustomerID == UserID );
// User can modify Order only if OrderStatus is not complete
order.SetWrite( y => x => x.CustomerID == UserID
&& x.OrderStatus != "Complete" );
// User can only modify OrderNotes and OrderStatus
order.SetProperties( SecurityRules.ReadWrite,
x => x.OrderNotes,
x => x.OrderStatus );
// User can not delete orders
order.SetDelete(order.NotSupportedRule);
}
}
これらのLINQルールは、すべての操作についてSaveChangesメソッドでデータベースに対して評価され、これらのルールはデータベースの前のファイアウォールとして機能します。
どちらの方法が正しいかについては多くの議論がありますので、両方とも受け入れられるので、私は一番好きなものを使用します(リポジトリはありません、UoW)。
EFでは、UoWはDbContextを介して実装され、DbSetはリポジトリです。
データレイヤーの操作方法については、DbContextオブジェクトを直接操作するだけで、複雑なクエリの場合は、再利用可能なクエリの拡張メソッドを作成します。
Ayendeには、CUD操作の抽象化がいかに悪いかについての投稿もいくつかあると思います。
私は常にインターフェイスを作成し、コンテキストを継承させて、DIにIoCコンテナーを使用できるようにします。
私にとって、それは比較的少数の要因で、単純な決定です。要因は次のとおりです。
したがって、私のアプリが#2を正当化できない場合、ドメインモデルとデータモデルを分離すれば、通常は#5に悩むことはありません。
Linqは最近の「リポジトリ」です。
ISession + Linqはすでにリポジトリであり、GetXByY
メソッドもQueryData(Query q)
汎化も必要ありません。 DALの使用には少し偏執的ですが、私はまだリポジトリインターフェイスを好みます。 (保守性の観点からは、特定のデータアクセスインターフェイス上にファサードが必要です)。
ここに使用するリポジトリがあります-nhibernateの直接使用から切り離されますが、linqインターフェイスを提供します(例外的な場合のISessionアクセスとして、最終的にリファクタリングの対象となります)。
class Repo
{
ISession _session; //via ioc
IQueryable<T> Query()
{
return _session.Query<T>();
}
}
EFに最も当てはまるのは、リポジトリパターンではありません。これはFacadeパターンです(EFメソッドの呼び出しをよりシンプルで使いやすいバージョンに要約します)。
EFは、リポジトリパターン(および作業ユニットパターンも)を適用するものです。つまり、EFはデータアクセス層を抽象化するものであり、ユーザーがSQLServerを扱っていることに気付かないようにします。
また、EFのほとんどの「リポジトリ」は、同じシグネチャを持つという点まで、EFの単一のメソッドに非常に簡単にマッピングするだけなので、優れたファサードでもありません。
したがって、このいわゆる「リポジトリ」パターンをEFに適用する2つの理由は、テストを容易にし、それに対する「既定の」呼び出しのサブセットを確立することです。それ自体は悪くありませんが、明らかにリポジトリではありません。
この時点でのRepository(または呼び出すことを選択した場合)は、主に永続層を抽象化することです。
クエリオブジェクトと組み合わせて使用するので、アプリケーションの特定の技術との結合はありません。また、テストが非常に簡単になります。
だから、私は持っている傾向があります
public interface IRepository : IDisposable
{
void Save<TEntity>(TEntity entity);
void SaveList<TEntity>(IEnumerable<TEntity> entities);
void Delete<TEntity>(TEntity entity);
void DeleteList<TEntity>(IEnumerable<TEntity> entities);
IList<TEntity> GetAll<TEntity>() where TEntity : class;
int GetCount<TEntity>() where TEntity : class;
void StartConversation();
void EndConversation();
//if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}
コールバックをデリゲートとして非同期メソッドを追加する可能性があります。レポは簡単に実装できます一般的にですので、アプリからアプリへの実装の行に触れることはできません。まあ、これは少なくともNHを使用している場合に当てはまります。EFでも同じことをしましたが、EFを嫌いにしました。 4.会話はトランザクションの開始です。いくつかのクラスがリポジトリインスタンスを共有する場合、非常に便利です。また、NHの場合、私の実装の1つのレポは、最初の要求で開かれる1つのセッションに相当します。
次に、クエリオブジェクト
public interface IQueryObject<TResult>
{
/// <summary>Provides configuration options.</summary>
/// <remarks>
/// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
/// If not used through a repository, it can be useful as a configuration option.
/// </remarks>
void Configure(object parameter);
/// <summary>Implementation of the query.</summary>
TResult GetResult();
}
NHでISessionを渡すためだけに使用する構成の場合。 EFでは多かれ少なかれ意味がありません。
クエリの例は..(NH)
public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
where TEntity : class
{
public override IList<TEntity> GetResult()
{
return this.Session.CreateCriteria<TEntity>().List<TEntity>();
}
}
EFクエリを実行するには、セッションではなく、Abstractベースにコンテキストが必要です。しかし、もちろんifcも同じです。
このようにして、クエリ自体がカプセル化され、簡単にテストできます。とりわけ、私のコードはインターフェイスのみに依存しています。すべてがとてもきれいです。ドメイン(ビジネス)オブジェクトはそれだけです。テストが困難で、データアクセス(クエリ)コードをドメインオブジェクトに混在させるアクティブレコードパターンを使用するときのような責任の混在はありません。誰でもデータ転送用のPOCOを自由に作成できます。
全体として、このアプローチでは多くのコードの再利用とシンプルさが提供されますが、私が想像できるものは何も失われません。何か案は?
そして、Ayendeの素晴らしい投稿と継続的な献身に感謝します。ここでの彼のアイデア(クエリオブジェクト)は、私のものではありません。