Коли слід створювати новий 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. Як це роблять усі інші?

Мені довелося позначити як дублікат - див. Stackoverflow.com/questions/12871666/linq-and-datacontext для досить гарного обговорення
SAJ14SAJ

2
У цьому випадку я б використав ін’єкцію залежностей (наприклад, Ninject), тому вона створить одну для DbContextкожного запиту. Я б також створив сервісний рівень. Перевірте це SO питання та відповідь
Zbigniew

Відповіді:


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є досить дорогим, але як тільки це було зроблено, багато інформації кешується, щоб подальші екземпляри були набагато швидшими. Ви, швидше за все, побачите проблеми з продуктивністю утримання контексту навколо, ніж у випадку створення такого кожного разу, коли вам потрібен доступ до вашої бази даних.

Як це роблять усі інші?

Це залежить.

Деякі люди вважають за краще використовувати структуру введення залежностей, щоб передавати конкретний екземпляр свого контексту своєму контролеру при його створенні. Обидва варіанти чудово. Mine більше підходить для невеликих додатків, де ви знаєте, що конкретна база даних, що використовується, не зміниться.

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


4
Мені нічого до цього додати, крім того, що приємно бачити, як деякі мої власні уподобання відображаються в публікаціях інших людей. Я ніколи не розумів прикладів сховищ, в результаті яких ще один шар додається до DbContext, не додаючи нічого, і я шанувальник створення базового класу, який відкриває захищений DbContext.
dark_perfect

4
Хотів додати, що ця відповідь чудова, але тепер застаріла з випуском EF6, який автоматично утилізує контекст після кожного використання. Таким чином, існують сценарії, коли створення контексту на сеанс (глобальний) є нормальним. Якщо ви використовуєте EF6 для підключення до збережених процедур, і блокування даних не є проблемою, то, можливо, ідеально створити контекст один раз в базовому контролері та мати всі контролери, яким потрібен доступ до бази даних, успадковувати від бази. Найбільш правильна відповідь - вам потрібно бути в курсі технологій та застосовувати правильний шаблон для своєї архітектури.
Атерс

Що робити, якщо я хочу викликати метод деяких моделей із дії контролера, які використовують DbContext? Як я можу передати його методу моделей?
Р.Мазітов

Тоді ви виконуєте запити до бази даних та бізнес-логіку в контролері, де вони не належать. Вам слід створити службу, яка викликається з вашого контролера. Тобто ваш UserController викликає метод у вашому UserService.
Фред

@Fred, так, і як мені передати DbContext до UserService, за допомогою простого параметра для функціонування чи ні?
Р.Мазітов

10

Я намагаюся відповісти з власного досвіду.

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

Контекст слід вводити шляхом ін’єкції залежності, а не створювати його самостійно. Найкраща практика полягає в тому, щоб створити його як службу з масштабом за допомогою введення залежності. (Див. Мою відповідь на запитання 4)

Будь ласка, також розгляньте можливість використання належної шаруватої структури додатків, наприклад Controller> BusinessLogic> Repository У цьому випадку не так буде, якщо ваш контролер отримає 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 на ім'я класу вашого власного контексту).
Равіор

Якщо контролери отримують ін’єкцію сховища, як вони (контролери) зберігають зміни? Скажімо, я відправляю запит POST, щоб вставити щось у базу даних, контролер обробляє запит, використовує сховище, щоб додати новостворене заперечення .. тоді що? Хто продовжує зміни?
MMalke

@MMalke Сховище робить це. Зазвичай він має функцію "SaveData (Data myData)", а потім сховище створює екземпляр свого брата Entity-Framework для класу даних, додає його до відповідного набору даних у dbcontext, а потім викликає SaveChanges (). Єдине, що робить контролер, це виклик функції Repository.SaveData (myData).
Равіор

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

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.