web-dev-qa-db-ja.com

汎用リポジトリ、依存性注入、SoCを備えたEF6 Code First

_Entity Framework_最新の安定版(6.1.1)で多くのことを読んで試してみました。

EFは既にリポジトリを提供しており、DbContextはすぐにDbSetを提供するので、一般的に_EF6_またはUoWでリポジトリを使用するかどうかについて多くの矛盾を読んでいます。

まず、プロジェクトの観点からソリューションに含まれるものを説明してから、矛盾に戻りましょう。

クラスライブラリプロジェクトと_asp.net-mvc_プロジェクトがあります。データアクセスであり、_Code First_に対してMigrationsが有効になっているクラスライブラリプロジェクト。

私のクラスlibプロジェクトには、一般的なリポジトリがあります。

_public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> Get();

    TEntity GetByID(object id);

    void Insert(TEntity entity);

    void Delete(object id);

    void Update(TEntity entityToUpdate);
}
_

そして、ここにそれの実装があります:

_public class Repository<TEntity> where TEntity : class
{
    internal ApplicationDbContext context;
    internal DbSet<TEntity> dbSet;

    public Repository(ApplicationDbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get()
    {
        IQueryable<TEntity> query = dbSet;
        return query.ToList();
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
}
_

そして、ここにいくつかのエンティティ:

_public DbSet<User> User{ get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<UserOrder> UserOrders { get; set; }
public DbSet<Shipment> Shipments { get; set; }
_

私は何を繰り返すべきではありませんが、_EF6_を使用すると、リポジトリを渡さなくなり、代わりにDbContextを渡します。 DIの場合、Ninjectを使用して_asp-net-mvc_プロジェクトで以下を設定しました。

_private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}
_

そして、これは、必要に応じて、コンストラクター注入を介して上位層クラスにApplicationDbContextを注入します。

矛盾に戻りましょう。

EFがすぐに提供するため、リポジトリが不要になった場合、どのように_Separation of Concern_(タイトルではSoCと省略されます)を提供しますか?

間違っている場合は修正しますが、すべてのデータアクセスロジック/計算(追加、取得、更新、削除、およびカスタムロジック/計算(エンティティ固有)など)を行うだけでいいように思えます_asp.net-mvc_プロジェクトで、リポジトリを追加しない場合。

この問題に関するすべての光は本当に感謝しています。

26
Quoter

少し説明することで混乱が解消されることを願っています。リポジトリパターンは、データベース接続とクエリロジックを抽象化するために存在します。 ORM(EFのようなオブジェクトリレーショナルマッパー)は、SQLクエリとステートメントで散らばったスパゲッティコードを扱うことの大きな喜びと喜びを忘れてしまったか、まったく経験したことがないほど長い間存在しています。データベースにクエリを実行したい場合、実際には接続を開始したり、実際にエーテルからSQLステートメントを構築したりするなどのクレイジーなことを担当していました。リポジトリー・パターンのポイントは、すべてのこの厄介さを、美しい原始的なアプリケーション・コードから遠ざける単一の場所を提供することでした。

2014年に早送りすると、Entity Frameworkと他のORMがリポジトリになります。すべてのSQLロジックは、pr索好きな目からすっきりと詰め込まれ、代わりに、コードで使用するニースのプログラムAPIがあります。ある点では、それは十分な抽象化です。それがカバーしていない唯一のものは、ORM自体への依存関係です。後で、NHibernateやWeb APIのようなもののためにEntity Frameworkを切り替えることを決定した場合、そのためにはアプリケーションで手術を行う必要があります。結果として、抽象化の別の層を追加することは依然として良い考えですが、リポジトリではなく、少なくとも少なくとも典型的なリポジトリを考えてみましょう。

所有しているリポジトリはtypicalリポジトリです。 Entity Framework APIメソッドのプロキシを作成するだけです。 _repo.Add_を呼び出し、リポジトリは_context.Add_を呼び出します。率直に言ってばかげているので、私を含む多くの人がEntity Frameworkでリポジトリを使用しないと言っています。

それで、あなたは何をすべきか?サービスを作成するか、おそらく「サービスのようなクラス」と言われるのが一番です。サービスが.NETに関連して議論され始めると、突然、ここで議論していることとはまったく関係のないあらゆる種類のことについて話していることになります。サービスのようなクラスは、特定のデータのセットを返すエンドポイントまたはデータのセットで非常に特定の機能を実行するという点で、サービスに似ています。たとえば、典型的なリポジトリでは、次のようなことをしている自分を見つけるでしょう:

_articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
_

サービスクラスは次のように機能します。

_service.GetPublishedArticles();
_

「公開された記事」として適格なもののすべてのロジックは、エンドポイントメソッドにきちんと含まれています。また、リポジトリを使用すると、基礎となるAPIを公開しています。ベースデータストアが抽象化されているため、他の何かに切り替えるのはeasierですが、そのデータストアへのクエリ用のAPIが変更された場合でも、クリークになります。

[〜#〜] update [〜#〜]

セットアップは非常に似ています。違いは、主にサービスとリポジトリの使用方法にあります。すなわち、私はそれをエンティティに依存させません。言い換えると、エンティティごとではなく、コンテキストごとにサービスを提供することになります。

いつものように、インターフェースから始めます。

_public interface IService
{
    IEnumerable<Article> GetPublishedArticles();

    ...
}
_

次に、実装:

_public class EntityFrameworkService<TContext> : IService
    where TContext : DbContext
{
    protected readonly TContext context;

    public EntityFrameworkService(TContext context)
    {
        this.context = context;
    }

    public IEnumerable<Article> GetPublishedArticles()
    {
        ...
    }
}
_

その後、物事は少し毛むくじゃらになり始める。メソッドの例では、単にDbSetを直接参照できます。つまり、_context.Articles_ですが、これはコンテキスト内のDbSet名に関する知識を意味します。柔軟性を高めるために、context.Set<TEntity>()を使用することをお勧めします。しかし、列車をジャンプしすぎる前に、このEntityFrameworkServiceという名前を付けた理由を指摘したいと思います。コードでは、IServiceインターフェイスのみを参照します。次に、依存性注入コンテナを介して、その代わりに_EntityFrameworkService<YourContext>_を使用できます。これにより、おそらくWebApiServiceなどのような他のサービスプロバイダーを作成することができます。

現在、すべてのサービスメソッドが利用できるクエリ可能オブジェクトを返す単一の保護されたメソッドを使用するのが好きです。これは、var dbSet = context.Set<YourEntity>();を介して毎回DbSetインスタンスを初期化する必要があるなど、多くの問題を取り除きます。次のようになります。

_protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = null,
    int? skip = null,
    int? take = null)
    where TEntity : class
{
    includeProperties = includeProperties ?? string.Empty;
    IQueryable<TEntity> query = context.Set<TEntity>();

    if (filter != null)
    {
        query = query.Where(filter);
    }

    foreach (var includeProperty in includeProperties.Split
        (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
    {
        query = query.Include(includeProperty);
    }

    if (orderBy != null)
    {
        query = orderBy(query);
    }

    if (skip.HasValue)
    {
        query = query.Skip(skip.Value);
    }

    if (take.HasValue)
    {
        query = query.Take(take.Value);
    }

    return query;
}
_

このメソッドは、まず保護されていることに注意してください。サブクラスはそれを利用できますが、これは完全にパブリックAPIの一部ではないはずです。この演習の全体的なポイントは、クエリ可能オブジェクトを公開しないことです。第二に、それは一般的です。言い換えれば、コンテキストに何かがある限り、それはあなたがそれに投げるあらゆるタイプを処理することができます。

次に、この小さなメソッド例では、次のようなことをすることになります。

_public IEnumerable<Article> GetPublishedArticles()
{
    return GetQueryable<Article>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}
_

このアプローチのもう1つの巧妙なトリックは、インターフェイスを利用する汎用サービスメソッドを持つ機能です。たとえば、公開されたanythingを取得する方法を1つ用意したいとします。私は次のようなインターフェースを持つことができます:

_public interface IPublishable
{
    PublishStatus Status { get; set; }
    DateTime PublishDate { get; set; }
}
_

その後、公開可能なエンティティはすべてこのインターフェイスを実装します。これを適切に行うと、次のことができるようになります。

_public IEnumerable<TEntity> GetPublished<TEntity>()
    where TEntity : IPublishable
{
    return GetQueryable<TEntity>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}
_

そして、アプリケーションコードで:

_service.GetPublished<Article>();
_
40
Chris Pratt