リポジトリメソッドとIQueryableの拡張
-
06-07-2019 - |
質問
ドメインモデルへのデータアクセスをカプセル化するリポジトリ(ContactRepository、UserRepositoryなど)があります。
データの検索を見ていたとき、たとえば
- 名を持つ連絡先を見つける XYZで始まる
-
誕生日が後の連絡先 1960
(etc)、
基本的に多くの例に従って、 FirstNameStartsWith(string prefix)や YoungerThanBirthYear(int year)などのリポジトリメソッドの実装を開始しました。
その後、問題が発生しました-複数の検索を組み合わせる必要がある場合はどうなりますか?上記のような各リポジトリ検索メソッドは、実際のドメインオブジェクトの有限セットのみを返します。より良い方法を探して、IQueryable <!> lt; T <!> gt;で拡張メソッドを書き始めました。これ:
public static IQueryable<Contact> FirstNameStartsWith(
this IQueryable<Contact> contacts, String prefix)
{
return contacts.Where(
contact => contact.FirstName.StartsWith(prefix));
}
次のようなことができるようになりました
ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);
ただし、拡張メソッドを作成している(そして ContactsQueryableExtensions などのクレイジークラスを作成しているのに気付いたので、<!> quot; nice grouping <!> quot;をすべて失うことで)適切なリポジトリ。
これは本当にそれを行う方法ですか、それとも同じ目標を達成するためのより良い方法がありますか?
解決
@Alex-これは古い質問であることは知っていますが、私がやろうとしているのは、リポジトリに本当に単純なことだけをさせることです。つまり、テーブルまたはビューのすべてのレコードを取得します。
次に、SERVICESレイヤーで(n層のソリューションを使用していますよね?))そこですべての「特別な」クエリを処理します。
わかりました、例の時間。
リポジトリレイヤー
ContactRepository.cs
public IQueryable<Contact> GetContacts()
{
return (from q in SqlContext.Contacts
select q).AsQueryable();
}
すてきでシンプル。 SqlContext
はEF Context
..のインスタンスで、Entity
..という基本的なContacts
があります。これは基本的にsql Contactsクラスです。
これは、そのメソッドが基本的に次のことを実行していることを意味します:SELECT * FROM CONTACTS
...しかし、そのクエリでデータベースにヒットすることはありません..それはただのクエリです。
Ok .. next layer .. KICK ...上へ進みます( Inception だれでも?)
サービスレイヤー
ContactService.cs
public ICollection<Contact> FindContacts(string name)
{
return FindContacts(name, null)
}
public ICollection<Contact> FindContacts(string name, int? year)
{
IQueryable<Contact> query = _contactRepository.GetContacts();
if (!string.IsNullOrEmpty(name))
{
query = from q in query
where q.FirstName.StartsWith(name)
select q;
}
if (int.HasValue)
{
query = from q in query
where q.Birthday.Year <= year.Value
select q);
}
return (from q in query
select q).ToList();
}
完了
では、要約しましょう。まず、単純な「連絡先からすべてを取得」クエリで開始します。ここで、名前が指定されている場合、すべての連絡先を名前でフィルターするフィルターを追加します。次に、年が指定されている場合、誕生日を年でフィルタリングします。など最後に、(この変更されたクエリを使用して)DBにアクセスし、返される結果を確認します。
注:-
- 簡単にするため、依存性注入は省略しました。強くお勧めします。
- これはすべて擬似コードです。テストされていない(コンパイラに対して)が、あなたはアイデアを得る....
テイクアウトポイント
- サービス層はすべてのスマートを処理します。そこで、必要なデータを決定します。
- リポジトリは、単純なSELECT * FROM TABLEまたは単純なTABLEへのINSERT / UPDATEです。
幸運:)
他のヒント
最近、私の現在の仕事を始めてから、このことについてよく考えています。私はリポジトリに慣れていますが、あなたが提案するように、ベアボーンリポジトリだけを使用して完全なIQueryableパスに行きます。
レポパターンは健全で、アプリケーションドメイン内のデータをどのように処理するかを説明するのに半効果的な仕事をしていると感じています。ただし、説明している問題は間違いなく発生します。単純なアプリケーションを超えて、面倒で高速になります。
おそらく、なぜあなたはデータを非常に多くの方法で求めているのかを再考する方法がありますか?そうでない場合は、ハイブリッドアプローチが最善の方法であると本当に感じています。再利用するもののリポジトリメソッドを作成します。実際にそれが理にかなっているもの。乾燥し、すべて。しかし、それらは一度限りですか? IQueryableとそれでできるセクシーなことを活用してみませんか?あなたが言ったように、そのためのメソッドを作成することはばかげていますが、それはあなたがデータを必要としないことを意味しません。 DRYは実際には適用されませんか?
これをうまく行うには規律が必要ですが、私はそれが適切な道だと本当に思います。
これは古いことはわかっていますが、最近この同じ問題に対処しており、Chadと同じ結論に達しました:少し規律があれば、拡張メソッドとリポジトリメソッドのハイブリッドが最適に機能するようです。
(Entity Framework)アプリケーションで従ってきたいくつかの一般的なルール:
クエリの順序
メソッドが順序付けにのみ使用される場合、(基礎となるプロバイダーを活用するために)IQueryable<T>
またはIOrderedQueryable<T>
で動作する拡張メソッドを作成することを好みます。 e.g。
public static IOrderedQueryable<TermRegistration> ThenByStudentName(
this IOrderedQueryable<TermRegistration> query)
{
return query
.ThenBy(reg => reg.Student.FamilyName)
.ThenBy(reg => reg.Student.GivenName);
}
リポジトリクラス内で必要に応じてThenByStudentName()
を使用できるようになりました。
単一のインスタンスを返すクエリ
メソッドがプリミティブパラメータによるクエリを伴う場合、通常はObjectContext
が必要であり、簡単にstatic
にすることはできません。リポジトリに残すこれらのメソッド、 e.g。
public Student GetById(int id)
{
// Calls context.ObjectSet<T>().SingleOrDefault(predicate)
// on my generic EntityRepository<T> class
return SingleOrDefault(student => student.Active && student.Id == id);
}
ただし、メソッドが代わりにナビゲーションプロパティを使用してEntityObject
を照会する場合、通常は非常に簡単にsomeStudent.GetLatestRegistration()
作成でき、拡張メソッドとして実装できます。 e.g。
public static TermRegistration GetLatestRegistration(this Student student)
{
return student.TermRegistrations.AsQueryable()
.OrderByTerm()
.FirstOrDefault();
}
これで、現在のスコープ内のリポジトリインスタンスを必要とせずにIEnumerable
を簡単に記述できます。
コレクションを返すクエリ
メソッドがICollection
、IList
、またはGetAll()
を返す場合、可能な場合はGetTermRegistrationsByTerm(this Term term)
にし、ナビゲーションプロパティを使用している場合でもリポジトリに残しておきます eg
public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
var termReg = term.TermRegistrations;
return (ordered)
? termReg.AsQueryable().OrderByStudentName().ToList()
: termReg.ToList();
}
これは、私の<=>メソッドが既にリポジトリに存在するためであり、拡張メソッドの混乱を避けるのに役立ちます。
これらの<!> quot; collection getters <!> quot;を実装しない別の理由拡張メソッドとしては、戻り値の型が暗示されていないため、意味のあるより冗長な命名が必要になるということです。たとえば、最後の例は<=>になります。
これが役立つことを願っています!
6年後、@ Alexが彼の問題を解決したと確信していますが、受け入れられた答えを読んだ後、2セントを加算したかったです。
リポジトリ内のIQueryable
コレクションを拡張して、柔軟性を提供し、消費者がデータ取得をカスタマイズできるようにする一般的な目的。アレックスがすでにやったことは良い仕事です。
サービス層の主な役割は、懸念の分離の原則を遵守し、ビジネス機能に関連するコマンドロジックに対処することです。
実際のアプリケーションでは、クエリロジックは、リポジトリ自体が提供する検索メカニズム(値の変更、型変換など)を超える拡張機能を必要としないことがよくあります。
次の2つのシナリオを検討してください:
IQueryable<Vehicle> Vehicles { get; }
// raw data
public static IQueryable<Vehicle> OwnedBy(this IQueryable<Vehicle> query, int ownerId)
{
return query.Where(v => v.OwnerId == ownerId);
}
// business purpose
public static IQueryable<Vehicle> UsedThisYear(this IQueryable<Vehicle> query)
{
return query.Where(v => v.LastUsed.Year == DateTime.Now.Year);
}
両方のメソッドは単純なクエリ拡張ですが、微妙な役割が異なります。 1つ目はシンプルなフィルターで、2つ目はビジネスニーズ(メンテナンスや請求など)を意味します。単純なアプリケーションでは、両方をリポジトリに実装できます。より理想的なシステムでは、UsedThisYear
はサービス層に最適であり(通常のインスタンスメソッドとして実装することもできます)、コマンドを分離する CQRS 戦略をより容易にすることもできます em>およびクエリ。
重要な考慮事項は、(a)リポジトリの主な目的、および(b) CQRS および DDD の哲学をどの程度遵守するかです。