Pull to refresh

Entity Framework Code First на практике

Reading time 4 min
Views 99K
Здравствуйте!

В этой публикации хочу поделиться личным опытом использования Entity Framework (EF) в реальном приложении и дать несколько полезных советов использования данного фреймворка.

Я занимаюсь разработкой enterprise приложений на платформе .NET больше 7 лет, за это время перепробовал несколько ORM библиотек, но сейчас для новых проектов использую Entity Framework Code First.

Изначально, как следует из названия, данный подход предполагал, что база данных, хранящая данные приложения, описывается сначала с помощью кода, а затем фреймворк сам создает или обновляет её. Однако многие разработчики предпочли использовать прямо противоположный подход, когда сначала создается база данных, а затем уже к ней мапятся объекты. Это особенно удобно для enterprise приложений, где данные почти всегда ставятся во главу угла и используется довольно продвинутая СУБД типа Oracle или MSSQL Server. Мне не нравится дизайнер EF, когда в нем количество таблиц переваливает за сотню. Поэтому Code First был воспринят лично мной как нечто безумно удобное и крайне полезное.

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

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

Совет №1. Генерация класса на основе таблицы


Cкрипт T-SQL, который можно взять отсюда, реально работает и очень удобен.

Совет №2. Прегенерация view


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

Прегенерация view — это нечто, что происходит внутри фреймворка при выполнении первого запроса. Этот процесс можно перенести на этап компиляции вашего приложения, что существенно сократит скорость выполнения первого запроса. Для разных версий EF способы несколько отличаются. Я использовал этот подход и полностью остался им доволен.

Совет №3. Массовое обновление данных с помощью DetectChanges


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

var dbContext = new MyDbContext();
//Добавляем большое число записей в некоторую таблицу
for(var i=0;i<1000;i++)
{   
    dbContext.People.Add(new Person());//!!! ОЧЕНЬ ДОЛГО РАБОТАЕТ
}
//Сохраняем изменения в БД
dbContext.SaveChanges();


Запустив его, многие наверняка будут удивлены, что основное время будет потрачено не собственно на запрос к базе данных, а на вставку объектов в сам dbContext. Дело в том, что при изменении данных внутри DbContext-а происходит масса проверок и других малоизученных вещей, подробнее о которых можно прочесть здесь. Чтобы этого избежать можно отключить слежение за изменениями в классе DbContext, а затем явно вызвать метод DetectChanges(), который сам эти изменения обнаружит. Так, данный код будет работать значительно быстрее:

var dbContext = new MyDbContext();
//Отключаем автоматическое слежение за изменениями
dbContext.Configuration.AutoDetectChangesEnabled = false;
//Добавляем большое число записей в некоторую таблицу
for(var i=0;i<1000;i++)
{   
    dbContext.People.Add(new Person());//теперь этот метод работает значительно быстрее
}
dbContext.ChangeTracker.DetectChanges(); //Обновляем сведения об изменениях. Работает быстро
//Сохраняем изменения в БД
dbContext.SaveChanges();


Совет №4. Следите за sql-запросами, которые формирует фреймворк


Это довольно универсальный совет, применимый, наверное, ко всем ORM, но многие почему-то о нем забывают. И после внедрения приложения, открыв профайлер и увидев 50 запросов к БД для показа довольно простой формы, бывают крайне удивлены.

Между тем, фреймворк предлагает механизмы для некоторой оптимизации. Среди этих механизмов наиболее известный — метод Include, который «вытягивает» дочерние объекты в том же запросе. Например:

var dbContext = new MyDbContext();
//Наряду с объектом Person в этом запросе также будет получен дочерний объект Address
var person = dbContext.People.Include(p=>p.Address).FirstOrDefault();
//Запроса к БД не будет
var address = person.Address;


Совет №5. Изолируйте логику работы с базой данных


Хотя в названии EF есть слово «фреймворк», его можно использовать и как обычную библиотеку просто для упрощения работы с базой данных.
В большинстве книг и презентаций мы видим код, похожий на этот:

//Контроллер MVC 
public class PersonController: Controller
{
    //Контекст EF приложения
    AppDbContext dbContext;
    public PersonController(AppDbContext dbContext)
    {
        this.dbContext = dbContext;
    }
    
    public IActionResult ViewPerson(int id)
    {
        var person = dbContext.People.First(p=>p.Id == id);
        return View(person);
    }
}


Проблема этого кода в том, что он плотно привязывает все части приложения к Entity Framework. Это не хорошо и не плохо само по себе, но при этом вы должны понимать, что существенно осложняете себе жизнь, если архитектуру доступа к данным придется менять. А вариантов тут может быть масса:

  • Вы внезапно обнаружите, что определенный запрос работает крайне медленно и решите заменить его на хранимую процедуру или View
  • Вы с удивлением обнаружите, насколько сложно сохранять данные, полученные в разных DbContext-ах
  • Вы с удивлением будете наблюдать классы TransactionScope, DbConnection внутри контроллера, когда понадобится нетривиальная логика обновления данных
  • Вы задумаетесь о повторном использовании кода для работы с БД
  • Вы решите использовать вместо EF NHibernate
  • А почему бы не использовать SOA архитектуру, и не начать получать и сохранять данные через веб-сервисы?

Мне нравится подход, когда EF используется ТОЛЬКО внутри проекта с названием DAL (DataAccess и т.п.) в классах с названием Repository, например:

//Репозиторий для получения объектов класса Person
public class PersonRepository: IPersonRepository
{
    public Person GetPerson(int personId)
    {
        var dbContext = new AppDbContext();
        var person = dbContext.People.First(p=>p.Id == id);
        return person;
    }
    
    public void SavePerson(Person person)
    {
        var dbContext = new AppDbContext();
        var dbPerson = dbContext.People.First(p=>p.Id == person.Id);
        dbPerson.LastName = person.LastName;
        .....
        dbContext.SaveChanges();
        return person;
    }
}


Этот подход хорош тем, что ваше приложение будет знать только о ваших объектах и о том, как их можно получить из БД и как сохранить в БД. Поверьте, этим вы существенно облегчите себе жизнь.
Tags:
Hubs:
+18
Comments 22
Comments Comments 22

Articles