web-dev-qa-db-ja.com

EntityFrameworkコンテキストの有効期間に関する質問

ASP.NETMVCアプリケーションでのEntityFrameworkコンテキストの望ましい存続期間についていくつか質問があります。コンテキストを可能な限り短い時間存続させるのが最善ではありませんか?

次のコントローラーアクションを検討してください。

public ActionResult Index()
{
    IEnumerable<MyTable> model;

    using (var context = new MyEntities())
    {
        model = context.MyTable;
    }

    return View(model);
}

ビューがページをレンダリングしているときにEntityFrameworkコンテキストがスコープ外になっているため、上記のコードは機能しません。他の人は上記のコードをどのように構成しますか?

22
Jonathan Wood

物議をかもします!

リクエスト全体を通してコンテキストを維持することは、いくつかの理由で良いことであるという一般的なMVC + EFのコンセンサスに同意しません。

パフォーマンスの向上が少ない新しいデータベースコンテキストの作成にどれほどの費用がかかるか知っていますか?ええと... "DataContextは軽量で、作成するのに費用はかかりません" [〜#〜] msdn [〜#〜]

IoCを間違えると、問題ないように見えます。ライブになるまでコンテキストを破棄するようにIoCコンテナを設定し、それを間違えた場合、本当に間違えます。私は2回、IoCコンテナから作成された大規模なメモリリークが常にコンテキストを正しく処理するとは限らないことを確認しました。通常の同時ユーザーレベルでサーバーが崩壊し始めるまで、設定が間違っていることに気付くことはありません。開発では発生しないので、いくつかの負荷テストを実行してください。

偶発的な遅延読み込みホームページにリストできるように、最新の記事のIQueryableを返します。ある日、他の誰かがそれぞれの記事の横にコメントの数を表示するように求められます。したがって、彼らは簡単なコードを追加しますビューにコメント数を次のように表示します...

@foreach(var article in Model.Articles) {
    <div>
        <b>@article.Title</b> <span>@article.Comments.Count() comments</span>
    </div>
}

見た目は問題なく、正常に動作します。ただし、実際には、返されたデータにコメントを含めなかったため、ループ内の各記事に対して新しいデータベース呼び出しが行われます。 SELECT N +1の問題。 10件の記事= 11件のデータベース呼び出し。さて、コードは間違っていますが、それは簡単な間違いなので、それは起こります。

データレイヤーでコンテキストをシャットダウンすることで、これを防ぐことができます。しかし、article.Comments.Count()のNullReferenceExceptionでコードが壊れることはありませんか?はい、そうなるので、ビューレイヤーに必要なデータを取得するためにDataレイヤーを編集する必要があります。これがどうあるべきかです。

コードの臭いビューからデータベースにアクセスするのに何か問題があります。 IQueryableが実際にはまだデータベースにヒットしていないことを知っているので、そのオブジェクトを忘れてください。データベースがデータレイヤーを離れる前に、データベースがヒットしていることを確認してください。

だから答え

あなたのコードは(私の意見では)このようになっているはずです

DataLayer:

public List<Article> GetArticles()
{
    List<Article> model;

    using (var context = new MyEntities())
    {
        //for an example I've assumed your "MyTable" is a table of news articles
        model = (from mt in context.Articles
                select mt).ToList();
        //data in a List<T> so the database has been hit now and data is final
    }

    return model;
}

コントローラ:

public ActionResult Index()
{
    var model = new HomeViewModel(); //class with the bits needed for you view
    model.Articles = _dataservice.GetArticles(); //irrelevant how _dataService was intialised
    return View(model);
}

これを実行してこれを理解したら、おそらくIoCコンテナにコンテキストを処理させる実験を開始できますが、以前は間違いありませんでした。私の警告に頭を向けてください-私は2つの大規模な失敗を見ました:)

しかし、正直に言って、好きなことをしてください。プログラミングは楽しく、好みの問題です。私はあなたに私のことを言っているだけです。ただし、何をするにしても、「すべてのクールな子供たちがやっている」という理由だけで、コントローラーごとまたはリクエストごとにIoCコンテキストの使用を開始しないでください。あなたが本当にそれの利点を気にし、それがどのように正しく行われるかを理解しているので、それをしてください。

51

私はリクエストごとに1つのコンテキストに同意します。通常、Ninjectを使用してコンテキスト.InRequestScopeをバインドすることでこれを行います。これは、非常にうまく機能します。

Bind<MyContext>().ToSelf().InRequestScope();

また、クエリにできるだけ近いセットを列挙することは本当に良い習慣です。

public ActionResult Index()
{
    IEnumerable<MyTable> model;

    using (var context = new MyEntities())
    {
        model = (from mt in context.MyTable
                select mt).ToArray();
    }
    return View(model);
}

これにより、ビューから意図せずにクエリを拡張することを回避できます。

6
Not loved

まず、データベースアクセスを別々のクラスに配置することを検討する必要があります。

次に、これに対する私のお気に入りの解決策は、「リクエストごとに1つのコンテキスト」を使用することです(MVCを使用している場合は、コントローラーごとに1つのコンテキストだと思います)。

要求された編集:

この答えを見てください、多分それはあなたにも役立つでしょう。私はWebフォームを使用しているため、現時点ではMVCで確認できませんが、役立つか、少なくともいくつかのヒントが得られる可能性があることに注意してください。 https://stackoverflow.com/a/10153406/128928

このdbcontextの使用例:

public class SomeDataAccessClass
{
    public static IQueryable<Product> GetAllProducts()
    {
        var products = from o in ContextPerRequest.Current.Products
                       select o;
        return products;
    }
}

次に、次のようなことを行うことができます。

public ActionResult Index()
{
     var products = SomeDataAccessClass.GetProducts();
     return View(products);
}

簡単ですよね?コンテキストを破棄することを心配する必要はありません。本当に必要なコードだけを記述します。

UnitOfWorkパターン、またはIoCコンテナーを追加することで、さらにスパイスを加えることを好む人もいます...しかし、このアプローチは単純であるため、私はこのアプローチがより好きです。

2
walther

LINQの.ToList()拡張メソッドを次のように利用できますか。

public ActionResult Index()
{
    IEnumerable<MyTable> model;

    using (var context = new MyEntities())
    {
        model = (from mt in context.MyTable
                select mt).ToList();
    }
    return View(model);
}
1
Jesse C. Slicer