Подтвердить что ты не робот

Вопросы об объекте Entity Framework Context Lifetime

У меня есть некоторые вопросы о желаемом времени жизни контекста Entity Framework в приложении ASP.NET MVC. Не лучше ли поддерживать контекст в кратчайшие сроки?

Рассмотрим следующее действие контроллера:

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

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

    return View(model);
}

Приведенный выше код не будет работать, потому что контекст Entity Framework вышел из области видимости, в то время как представление отображает страницу. Как другие структурируют код выше?

4b9b3361

Ответ 1

Позвольте получить противоречивое предложение!

Я не согласен с общим мнением MVC + EF о том, что сохранение контекста в течение всего запроса - это хорошо по ряду причин:

Низкое повышение производительности Знаете ли вы, как дорогое создание нового контекста базы данных? Ну... "DataContext - легкий и не дорогого создать", что из MSDN

Получить IoC неправильно, и все будет хорошо.. пока вы не перейдете жить Если вы настроили свой контейнер IoC, чтобы избавиться от своего контекста, и вы ошибаетесь, вы действительно действительно ошибаетесь. Я уже дважды видел массивные утечки памяти, созданные из контейнера 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 запросов к базе данных. Хорошо, так что код неправильный, но легко сделать так, чтобы это произошло.

Это можно предотвратить, отключив контекст в вашем слое данных. Но не будет ли разрыв кода с исключением NullReferenceException в статье. Comments.Count()? Да, это заставит вас отредактировать слой Данные, чтобы получить данные, необходимые для слоя "Вид". Вот как это должно быть.

Запах кода Есть только что-то не так, как попасть в базу данных из вашего представления. Вы знаете, что 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, но определенно не раньше. Направьте мое предупреждение - я видел два больших сбоя:)

Но честно делайте то, что вам нравится, программирование - это весело и должно быть вопросом предпочтения. Я просто говорю тебе свое. Но что бы вы ни делали, не начинайте использовать контекст IoC для каждого контроллера или для запроса только потому, что "все классные дети делают это". Сделайте это, потому что вы действительно по-настоящему заботитесь об этом и понимаете, как это делается правильно.

Ответ 2

Я согласен с одним контекстом для каждого запроса, мы обычно делаем это, связывая контекст. InRequestScope с использованием Ninject, который работает очень хорошо:

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);
}

это поможет вам избежать непреднамеренного дополнения запроса из вашего представления.

Ответ 3

Во-первых, вам следует рассмотреть возможность доступа к базе данных для отдельных классов.

Во-вторых, моим любимым решением для этого является использование "одного контекста для запроса" (если вы используете MVC, я считаю, что это один контекст для каждого контроллера).

Запрошенное редактирование:

Посмотрите на этот ответ, может быть, это тоже поможет. Обратите внимание, что я использую webforms, поэтому не могу проверить его в MVC на данный момент, но это может быть полезно для вас или, по крайней мере, дать вам несколько указателей. fooobar.com/questions/111872/...

Пример использования этого 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... Но мне нравится этот подход больше из-за его простоты.

Ответ 4

Можно ли использовать метод расширения 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);
}