Когда мне следует создать новый DbContext ()

83

В настоящее время я использую DbContextподобное:

namespace Models
{
    public class ContextDB: DbContext
    {

        public DbSet<User> Users { get; set; }
        public DbSet<UserRole> UserRoles { get; set; }

        public ContextDB()
        {

        }
    }
}

Затем я использую следующую строку в верхней части ВСЕХ моих контроллеров, которым нужен доступ к базе данных. Я также использую его в своем классе UserRepository, который содержит все методы, относящиеся к пользователю (например, получение активного пользователя, проверка его ролей и т. Д.):

ContextDB _db = new ContextDB();

Если подумать ... бывают случаи, когда у одного посетителя может быть несколько активных DbContexts ... т.е. если он посещает контроллер, который использует UserRepository ... это может быть не лучшая идея, и у меня есть несколько вопросов по этому поводу

  1. Когда мне следует создать новый DbContext / должен ли я иметь один глобальный контекст, который я передаю?
  2. Могу ли я иметь один глобальный контекст, который можно использовать повсюду?
  3. Это вызывает снижение производительности?
  4. Как все остальные это делают?
JensB
источник
Мне пришлось
пометить
2
В этом случае я бы использовал инъекцию зависимостей (например, Ninject), поэтому он создавал бы одну для DbContextкаждого запроса. Я бы также создал сервисный слой. Отметьте этот ТАК вопрос и ответ
Збигнев

Ответы:

82

Я использую базовый контроллер, который предоставляет DataBaseсвойство, доступное производным контроллерам.

public abstract class BaseController : Controller
{
    public BaseController()
    {
        Database = new DatabaseContext();
    }

    protected DatabaseContext Database { get; set; }

    protected override void Dispose(bool disposing)
    {
        Database.Dispose();
        base.Dispose(disposing);
    }
}

Все контроллеры в моем приложении являются производными BaseControllerи используются следующим образом:

public class UserController : BaseController
{
    [HttpGet]
    public ActionResult Index()
    {
        return View(Database.Users.OrderBy(p => p.Name).ToList());
    }
}

Теперь отвечу на ваши вопросы:

Когда мне следует создать новый DbContext / должен ли я иметь один глобальный контекст, который я передаю?

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

Не пытайтесь использовать глобальный контекст (веб-приложения так не работают).

Могу ли я иметь один глобальный контекст, который можно использовать повсюду?

Нет, если вы сохраните контекст, он будет отслеживать все обновления, добавления, удаления и т. Д., И это замедлит работу вашего приложения и может даже вызвать некоторые довольно тонкие ошибки в вашем приложении.

Вам, вероятно, следует выбрать либо ваш репозиторий, либо ваш контекст для вашего контроллера, но не то и другое одновременно. Наличие доступа к двум контекстам из одного и того же метода приведет к ошибкам, если они оба имеют разные представления о текущем состоянии приложения.

Лично я предпочитаю раскрывать DbContextнапрямую, поскольку большинство примеров репозиториев, которые я видел, в DbContextлюбом случае просто превращаются в тонкие оболочки .

Это вызывает снижение производительности?

Первое создание a DbContextдовольно дорого, но как только это было сделано, большая часть информации кэшируется, так что последующие экземпляры выполняются намного быстрее. у вас больше шансов увидеть проблемы с производительностью из-за сохранения контекста, чем из-за его экземпляра каждый раз, когда вам нужен доступ к своей базе данных.

Как все остальные это делают?

Это зависит.

Некоторые люди предпочитают использовать структуру внедрения зависимостей для передачи конкретного экземпляра своего контекста своему контроллеру при его создании. Оба варианта подходят. Мой больше подходит для небольшого приложения, когда вы знаете, что конкретная используемая база данных не изменится.

некоторые могут возразить, что вы не можете этого знать, и именно поэтому метод внедрения зависимостей лучше, поскольку он делает ваше приложение более устойчивым к изменениям. Мое мнение по этому поводу состоит в том, что это, вероятно, не изменится (SQL-сервер и Entity Framework вряд ли являются непонятными) и что мое время лучше всего потратить на написание кода, специфичного для моего приложения.

Бенджамин Гейл
источник
4
Мне нечего добавить к этому, кроме как приятно видеть, как некоторые мои собственные предпочтения отражаются в сообщениях других людей. Я никогда толком не разбирался в примерах репозиториев, которые приводят к добавлению еще одного уровня в DbContext без добавления чего-либо, и я фанат создания базового класса, который предоставляет защищенный DbContext.
dark_perfect
4
Хотел добавить, что этот ответ отличный, но теперь он устарел с выпуском EF6, который автоматически удаляет контекст после каждого использования. Таким образом, существуют сценарии, в которых можно создать отдельный (глобальный) контекст для каждого сеанса. Если вы используете EF6 для подключения к хранимым процедурам и блокировка данных не является проблемой, то, возможно, идеально было бы создать контекст один раз в базовом контроллере и унаследовать все контроллеры, требующие доступа к базе данных, от базового. Самый правильный ответ - вам нужно быть в курсе технологий и применять правильный шаблон для вашей архитектуры.
Atters
Что, если я хочу вызвать некоторые методы моделей из действия контроллера, использующего DbContext? Как передать это в метод моделей?
RMazitov
Затем вы выполняете запросы к базе данных и бизнес-логику в контроллере, которому они не принадлежат. Вы должны создать службу, которая вызывается с вашего контроллера. Т.е. ваш UserController вызывает метод в вашем UserService.
Фред
@Fred, да, и как мне передать DbContext в UserService, простым параметром для работы или нет?
РМазитов
10

Я пытаюсь ответить, исходя из собственного опыта.

1. Когда мне следует создать новый DbContext / должен ли я иметь один глобальный контекст, который я передаю?

Контекст должен быть введен инъекцией зависимости и не должен создаваться вами самостоятельно. Лучше всего создать его как службу с ограниченным объемом посредством внедрения зависимостей. (См. Мой ответ на вопрос 4)

Также подумайте об использовании правильной многоуровневой структуры приложения, такой как «Контроллер»> «Бизнес-логика»> «Репозиторий». В этом случае ваш контроллер получит не db-context, а репозиторий. Внедрение / создание экземпляра db-контекста в контроллере говорит мне, что архитектура вашего приложения совмещает множество обязанностей в одном месте, что - ни при каких обстоятельствах - я не могу рекомендовать.

2. Могу ли я иметь один глобальный контекст, который можно использовать повсюду?

Да, можно , но вопрос должен быть: « Следует ли мне…» -> НЕТ. Контекст предназначен для использования в каждом запросе для изменения вашего репозитория, а затем его снова.

3. Вызывает ли это снижение производительности?

Да, потому что DBContext просто не создан для того, чтобы быть глобальным. Он хранит все данные, которые были введены или запрошены в него, до тех пор, пока он не будет уничтожен. Это означает, что глобальный контекст будет становиться все больше и больше, операции с ним будут становиться все медленнее и медленнее, пока вы не получите исключения из памяти или не умрете от старости, потому что все это замедлилось до ползания.

Вы также получите исключения и множество ошибок, когда несколько потоков одновременно обращаются к одному и тому же контексту.

4. Как все остальные это делают?

DBContext, внедренный фабрикой посредством внедрения зависимостей; сфера действия:

services.AddDbContext<UserDbContext>(o => o.UseSqlServer(this.settings.DatabaseOptions.UserDBConnectionString));

Надеюсь, мои ответы помогут.

Равиор
источник
Что, если я хочу использовать DBContext в Startup.cs в самом методе ConfigureServices? У меня есть промежуточное ПО OICD, где мне нужно получить доступ к БД, но я не могу получить доступ к DBContext или я не знаю как?
bbrinck
В методе configureServices ваш DBContext, вероятно, недоступен, потому что вы настроили его там. ServiceProvider, из которого вы фактически получаете свой DBContext во время выполнения, сначала будет доступен в методе Configure () (не «ConfigureServices»!). Там вы можете запросить контекст с помощью ApplicationBuilder, набрав «app.ApplicationServices.GetRequiredService <MyDbContext> ();» (Замените MyDbContext именем класса вашего собственного контекста).
Ravior
Если в контроллеры внедряется репозиторий, как они (контроллеры) сохраняют изменения? Скажем, я отправляю запрос POST для вставки чего-либо в базу данных, контроллер обрабатывает запрос, использует репозиторий для добавления вновь созданного объекта возражения ... что дальше? Кто сохранит изменения?
MMalke
@MMalke Репозиторий делает это. Обычно он имеет функцию «SaveData (Data myData)», а затем репозиторий создает экземпляр своего брата Entity-Framework для класса данных, добавляет его в соответствующий dbset в dbcontext, а затем вызывает SaveChanges (). Единственное, что делает контроллер, это вызывает функцию Repository.SaveData (myData).
Ravior
1

Прямо сейчас я пробую этот подход, который позволяет избежать создания экземпляра контекста, когда вы вызываете действия, которые его не используют.

public abstract class BaseController : Controller
{
    public BaseController() { }

    private DatabaseContext _database;
    protected DatabaseContext Database
    {
        get
        {
            if (_database == null)
                _database = new DatabaseContext();
            return _database;
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (_database != null)
            _database.Dispose();
        base.Dispose(disposing);
    }
}
Андрей
источник
2
Хотя в вашем подходе нет ничего плохого, я считаю, что контекст Entity Framework будет делать все лениво, и никакая реальная работа не выполняется, пока вы не получите доступ к базе данных. Таким образом, накладные расходы на создание контекста EF должны быть очень небольшими.
Martin Liversage
Я провел небольшое исследование, и мне кажется, что это правильно. Я провел простой тест, сравнив то, что GC.GetTotalMemory()вернулось (не идеально, но это то, что я нашел), и разница никогда не превышала 8 КБ.
Эндрю
0

Это, очевидно, более старый вопрос, но если вы используете DI, вы можете сделать что-то вроде этого и охватить все свои объекты на время жизни запроса

 public class UnitOfWorkAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var context = IoC.CurrentNestedContainer.GetInstance<DatabaseContext>();
            context.BeginTransaction();
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionContext)
        {
            var context = IoC.CurrentNestedContainer.GetInstance<DatabaseContext>();
            context.CloseTransaction(actionContext.Exception);
        }
    }
CSharper
источник
0

Вы должны удалить контекст сразу после каждой операции Save (). В противном случае каждое последующее сохранение займет больше времени. У меня был проект, который создавал и сохранял сложные объекты базы данных в цикле. К моему удивлению, операция стала в три раза быстрее после того, как я переместил "using (var ctx = new MyContext ()) {...}" внутрь цикла.

Григорий Храпунович
источник