14

私は自分の ASP.NET MVC アプリケーションを設計している最中で、いくつかの興味深い考えに出くわしました。

私が見た多くのサンプルは、Repository パターン ( IRepository) を記述して使用しているため、MVC を学習しているときに私が行った方法です。

今、私はそれが何をしているのかを知っており、現在のデザインを見て、それが最善の方法であるかどうかを考え始めています.

現在、、 などIUserRepositoryのメソッドを定義する基本的ながあります。FindById()SaveChanges()

現在、DB のユーザー テーブルをロード/クエリするときはいつでも、次の行に沿って何かを行います。

    private IUserRepository Repository;

    public UserController()
        : this(new UserRepository())
    { }

    [RequiresAuthentication]
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(string ReturnUrl, string FirstRun)
    {
        var user = Repository.FindById(User.Identity.Name);

        var viewModel = Mapper.Map<User, UserEditViewModel>(user);
        viewModel.FirstRun = FirstRun == "1" ? true : false;

        return View("Edit", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")]
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl)
    {
        //Map the ViewModel to the Model
        var user = Repository.FindById(User.Identity.Name);

        //Map changes to the user
        Mapper.Map<UserEditViewModel, User>(viewModel, user);

        //Save the DB changes
        Repository.SaveChanges();

        if (!string.IsNullOrEmpty(ReturnUrl))
            return Redirect(ReturnUrl);
        else
            return RedirectToAction("Index", "User");
    }

現在、ユーザーがリンクを作成するときにコントローラーを作成することに関してMVCがどのように機能するかを完全には理解していません(ユーザーごとに1つのコントローラーがあるのか​​、アプリケーションごとに1つのコントローラーがあるのか​​ わかりません)。アクション。

IRepository<T> ここで、一般的なリポジトリ インターフェイスの使用に関する素晴らしい質問を見つけました。またRepositoryFactory、多くのブログでstatic のアイデアを見つけました。基本的に、リポジトリのインスタンスは 1 つだけ保持され、このファクトリを介して取得されます

したがって、私の質問は、人々がアプリでどのようにそれを行うか、および何が良い習慣と見なされるかについてです。

人々は各テーブルに基づいて個別のリポジトリを持っていますか ( IUserRepository)?
彼らはジェネリックを使用していますIRepository<T>か?
静的リポジトリ ファクトリを使用していますか?
それとも完全に別のものですか?

編集:私はおそらく同様に尋ねる必要があることに気付きました:

各コントローラーにプライベートIRepositoryを設定するのは良い方法ですか? IRepositoryまたは、使用するたびに新しいインスタンスを作成する必要がありますか?

報奨金の編集: より多くの視点を得るために報奨金を開始しています (ティムの意見が役に立たなかったわけではありません)。

私は、人々が MVC アプリで何をしているのか、または彼らが良いアイデアだと思っていることを知りたいと思っています。

4

5 に答える 5

23

ジェネリックの考え方に関するいくつかの非常に明白な問題IRepository<T>:

  • すべてのエンティティが同じタイプのキーを使用することを前提としていますが、これはほとんどの重要なシステムでは当てはまりません。GUID を使用するエンティティもあれば、ある種の自然キーや複合キーを持つエンティティもあります。NHibernate はこれをかなりうまくサポートできますが、Linq to SQL はそれがかなり苦手です。自動キー マッピングを行うには、大量のハック コードを書かなければなりません。

  • これは、各リポジトリが 1 つのエンティティ タイプのみを処理でき、最も単純な操作のみをサポートすることを意味します。リポジトリがこのような単純な CRUD ラッパーに追いやられると、ほとんど役に立たなくなります。IQueryable<T>クライアントにまたはを渡すだけでもかまいませんTable<T>

  • すべてのエンティティに対してまったく同じ操作を実行することを前提としています。実際には、これは真実とはかけ離れたものになるでしょう。確かに、 ID で取得したいかもしれませんが、特定の顧客の特定の日付範囲内Orderのオブジェクトのリストを取得したい可能性が高くなります。Order完全にジェネリックという概念では、IRepository<T>さまざまな種類のエンティティに対してさまざまな種類のクエリをほぼ確実に実行したいという事実が考慮されていません。

リポジトリ パターンの要点は、一般的なデータ アクセス パターンを抽象化することです。一部のプログラマーは、リポジトリの作成に飽きて、「よし、どんなエンティティ タイプでも扱える uber-repository を 1 つ作成してみよう!」と言っていると思います。これは素晴らしいことですが、あなたがやろうとしていることの 80% に対してリポジトリがほとんど役に立たないことを除けば. 基本クラス/インターフェースとしては問題ありませんが、それがあなたが行う作業の全範囲である場合は、単に怠けているだけです (そして、将来の頭痛の種を保証します)。


理想的には、次のような汎用リポジトリから始めることができます。

public interface IRepository<TKey, TEntity>
{
    TEntity Get(TKey id);
    void Save(TEntity entity);
}

これにはor関数がないことに気付くでしょう。これは、コードのどこからでもテーブル全体からデータを一度に取得できると考えるのはばかげているからです。これは、特定のリポジトリへのアクセスを開始する必要がある場合です。ListGetAll

public interface IOrderRepository : IRepository<int, Order>
{
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
    IPager<Order> GetOrdersByProduct(int productID);
}

など - あなたはアイデアを得る。このようにして、信じられないほど単純化されたIDによる取得セマンティクスが実際に必要になった場合に備えて、「汎用」リポジトリがありますが、一般的には、コントローラークラスに渡すことはありません。


コントローラーに関しては、これを正しく行う必要があります。そうしないと、すべてのリポジトリをまとめるために行ったすべての作業をほとんど無効にしてしまいます。

コントローラは外部からリポジトリを取得する必要があります。これらのリポジトリを作成した理由は、ある種の制御の反転を実行できるようにするためです。ここでの最終的な目標は、あるリポジトリを別のリポジトリに交換できるようにすることです。たとえば、単体テストを実行したり、将来のある時点で Linq から SQL、Entity Framework に切り替えることにした場合などです。

この原則の例は次のとおりです。

public class OrderController : Controller
{
    public OrderController(IOrderRepository orderRepository)
    {
        if (orderRepository == null)
            throw new ArgumentNullException("orderRepository");
        this.OrderRepository = orderRepository;
    }

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
    // More actions

    public IOrderRepository OrderRepository { get; set; }
}

言い換えれば、コントローラはリポジトリを作成する方法を知りませんし、そうすべきでもありません。そこでリポジトリの構築が行われている場合、それは本当に望ましくない結合を作成しています。ASP.NET MVC サンプル コントローラーに具体的なリポジトリを作成するパラメーターなしのコンストラクターがある理由は、依存関係の挿入フレームワーク全体をセットアップすることを強制することなく、サイトをコンパイルおよび実行できる必要があるためです。

しかし、実稼働サイトでは、コンストラクターまたはパブリック プロパティを介してリポジトリの依存関係を渡していない場合、コントローラーがデータベース レイヤーに緊密に結合されているため、リポジトリを持つ時間をまったく無駄にしていることになります。次のようなテスト コードを記述できる必要があります。

[TestMethod]
public void Can_add_order()
{
    OrderController controller = new OrderController();
    FakeOrderRepository fakeRepository = new FakeOrderRepository();
    controller.OrderRepository = fakeRepository; //<-- Important!
    controller.SubmitOrder(...);
    Assert.That(fakeRepository.ContainsOrder(...));
}

OrderControllerオフになって独自のリポジトリを作成している場合、これを行うことはできません。このテスト メソッドは、データ アクセスを行うことを想定しておらず、コントローラーがアクションに基づいて正しいリポジトリ メソッドを呼び出していることを確認するだけです。


これはまだ DI ではありません。気を付けてください。これはただの偽物/嘲笑です。DI が登場するのは、Linq to SQL では十分ではなく、本当に NHibernate で HQL が必要であると判断したときですが、すべてを移植するには 3 か月かかり、できるようにしたい場合です。一度に 1 つのリポジトリを実行します。したがって、たとえばNinjectのような DI フレームワークを使用する場合、これを変更するだけで済みます。

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();

に:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();

これで、依存するものはすべてNHibernate バージョンを使用するようになりました。数百行になる可能性があるコードを1 行IOrderRepository変更するだけで済みました。そして、Linq to SQL バージョンと NHibernate バージョンを並行して実行し、途中で何も壊すことなく機能を少しずつ移植しています。


したがって、私が行ったすべてのポイントを要約すると、次のようになります。

  1. IRepository<T>汎用インターフェイスに厳密に依存しないでください。リポジトリに必要な機能のほとんどは固有のものであり、一般的なものではありません。クラス/インターフェイス階層の上位レベルにを含めたい場合は問題ありませんが、コントローラーは特定のIRepository<T>リポジトリに依存する必要があるため、汎用リポジトリが見つかったときに 5 つの異なる場所でコードを変更する必要はありません。重要なメソッドがありません。

  2. コントローラは、独自のリポジトリを作成するのではなく、外部からリポジトリを受け入れる必要があります。これは、結合を排除し、テスト容易性を向上させるための重要なステップです。

  3. 通常、依存関係の挿入フレームワークを使用してコントローラーを接続する必要があり、それらの多くは ASP.NET MVC とシームレスに統合できます。それが多すぎて理解できない場合は、少なくとも何らかの静的サービス プロバイダーを使用して、すべてのリポジトリ作成ロジックを一元化できるようにする必要があります。(長期的には、DI フレームワークを学習して使用する方が簡単であることがわかるでしょう)。

于 2010-03-03T22:32:24.823 に答える
3

人々は各テーブル (IUserRepository) に基づいて個別のリポジトリを持っていますか? 各テーブルではなく、集計ごとにリポジトリを持つ傾向があります。

彼らは一般的な IRepository を使用していますか? 可能であれば、はい

静的リポジトリ ファクトリを使用していますか? IOC コンテナを介した Repository インスタンスの注入を好む

于 2010-02-27T12:53:52.927 に答える
1

これが私がそれを使用している方法です。すべてのリポジトリに共通するすべての操作に IRepository を使用しています。

public interface IRepository<T> where T : PersistentObject
{
    T GetById(object id);
    T[] GetAll();
    void Save(T entity);
}

また、このリポジトリに固有の操作のために、各集計に専用の ITRepository も使用します。たとえば、ユーザーの場合、IUserRepository を使用して、UserRepository とは異なるメソッドを追加します。

public interface IUserRepository : IRepository<User>
{
    User GetByUserName(string username);
}

実装は次のようになります。

public class UserRepository : RepositoryBase<User>, IUserRepository
{
    public User GetByUserName(string username)
    {
        ISession session = GetSession();
        IQuery query = session.CreateQuery("from User u where u.Username = :username");
        query.SetString("username", username);

        var matchingUser = query.UniqueResult<User>();

        return matchingUser;
    }
}


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject
{
    public virtual T GetById(object id)
    {
        ISession session = GetSession();
        return session.Get<T>(id);
    }

    public virtual T[] GetAll()
    {
        ISession session = GetSession();
        ICriteria criteria = session.CreateCriteria(typeof (T));
        return criteria.List<T>().ToArray();
    }

    protected ISession GetSession()
    {
        return new SessionBuilder().GetSession();
    }

    public virtual void Save(T entity)
    {
        GetSession().SaveOrUpdate(entity);
    }
}

UserController よりも次のようになります。

public class UserController : ConventionController
{
    private readonly IUserRepository _repository;
    private readonly ISecurityContext _securityContext;
    private readonly IUserSession _userSession;

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession)
    {
        _repository = repository;
        _securityContext = securityContext;
        _userSession = userSession;
    }
}

リポジトリは、カスタム コントローラー ファクトリを使用した依存性注入パターンを使用してインスタンス化されます。依存性注入レイヤーとしてStructureMapを使用しています。

データベース層はNHibernateです。ISession は、このセッションのデータベースへのゲートウェイです。

CodeCampServer構造を調べることをお勧めします。そこから多くのことを学ぶことができます。

そこから学べるもう 1 つのプロジェクトは、Who Can Help Meです。まだ十分に掘り下げていません。

于 2010-03-03T08:53:33.170 に答える
0

codeplexの WebForms asp:ObjectDataSource として使用できるように作成された優れたGeneric Repopsitoryライブラリを見つけることができます: MultiTierLinqToSql

各コントローラーには、サポートする必要があるアクション用のプライベート リポジトリがあります。

于 2010-03-03T22:37:07.250 に答える
0

人々は各テーブル (IUserRepository) に基づいて個別のリポジトリを持っていますか?

はい、これは 2 つの理由でより良い選択でした。

  • 私の DAL はLinq-to- Sql に基づいています (ただし、私の DTO は LTS エンティティに基づくインターフェイスです)
  • 実行される操作はアトミックです (追加はアトミック操作、保存は別の操作など)。

彼らは一般的な IRepository を使用していますか?

はい、専ら、DDD Entity/Value スキームに触発されて、IRepositoryEntity / IRepositoryValue と、他のもの用の汎用 IRepository を作成しました。

静的リポジトリ ファクトリを使用していますか?

はい、いいえ:静的クラスを介して呼び出されるIOC コンテナーを使用します。うーん… 一種の工場と言えます。

: 私はこのアーキテクチャを自分で設計しましたが、同僚が非常に優れていると判断したため、現在、このモデルで会社全体のフレームワークを作成しています (そうです、若い会社です)。主要なアクターからそのようなフレームワークがリリースされるとは思いますが、これは間違いなく試す価値のあるものです。

于 2010-03-03T11:09:09.633 に答える