Entity Framework (EF) — это инструмент абстракции уровня базы данных Microsoft для .NET applications. Платформа использует технологию объектно-реляционного отображения для манипулирования данными в базах данных. Это позволяет вам быстро создавать прототипы, предоставлять их клиентам, получать обратную связь и улучшать там, где это необходимо. Если требуются значительные изменения в ваших прикладных уровнях, EF избавляет вас от необходимости перестраивать и повторно подключать уровень данных. Он также скрывает конфигурацию базы данных и детали реализации, позволяя вам сосредоточиться на данных и остальных уровнях приложения.
Вы можете использовать EF на любой платформе, поддерживающей .NET Framework.Вам не нужно создавать разные версии приложений для обслуживания других целевых платформ.
Алгоритм обнаружения изменений EF проверяет, изменился ли объект, сравнивая его текущие и прошлые значения. Эта проверка определяет, какие значения приложению необходимо обновить в базе данных. Вызовы базы данных обычно обходятся системе дорого, но такой подход делает вызовы более эффективными. EF также имеет процедуры для обработки конфликтов параллелизма и обеспечивает эффективное выполнение запросов с помощью Language Integrated Query (LINQ).
Несмотря на различные преимущества использования EF, при его неправильном использовании могут возникнуть узкие места в производительности. В этом практическом руководстве Entity Framework вы ознакомитесь с этими узкими местами и узнаете, как их предотвратить и устранить.
Чтобы следовать этой статье, у вас должно быть следующее:
Для получения дополнительных указаний загрузите код из руководства.
В этом разделе подробно рассматриваются три узких места производительности, которые могут возникнуть при неправильном использовании EF. Узкие места возникают в результате неэффективных запросов, отсутствия кэширования и неправильного использования отслеживания. Здесь также демонстрируется, как избежать создания этих узких мест.
Неэффективные запросы могут быть результатом неправильного использования индексов таблиц, ненужного поиска записей и отсутствия разбивки на страницы при извлечении больших объемов данных. В разделе “Способы повышения производительности Entity Framework” этой статьи описываются методы, которым вы должны следовать, чтобы сделать ваши запросы более эффективными.
Вы можете реализовать стратегию кэширования, чтобы избежать частых и дорогостоящих обращений к базе данных. Тремя встроенными формами кэширования являются кэширование объектов, кэширование плана запроса и кэширование метаданных. В дополнение к этим трем типам первого уровня существует также кэширование второго уровня, которое вы можете реализовать, расширив EF.
По умолчанию EF реализует отслеживание всех видов данных. Но для больших объемов данных отслеживание их состояний может занимать много памяти, что приводит к замедлению работы. Лучше всего использовать отслеживание только для данных, которые, вероятно, будут обновлены. Это означает, что вам не следует использовать отслеживание изменений для данных, доступных только для чтения, поскольку изменения не могут быть внесены в данные, доступные только для чтения.
Кроме того, крайне важно постоянно отслеживать производительность своих приложений на основе EF. Для этого вы можете использовать такие инструменты, как SQL Server Profiler и Glimpse. Вы также можете использовать счетчики производительности.
Теперь, когда вы знакомы с распространенными узкими местами в EF, читайте дальше, чтобы узнать о методах, которые можно использовать для повышения производительности EF.
LINQ — это технология, которая включает возможности запроса данных в C #. Ее роль в EF заключается в обеспечении проверок во время компиляции при написании запросов и загрузке данных из нескольких источников при их преобразовании.
Запросы LINQ помогают минимизировать объем данных, извлекаемых с помощью прогнозов. С помощью LINQ вы также можете повысить производительность и свести к минимуму циклические обращения к базам данных за счет быстрой и отложенной загрузки. Для загрузки больших объемов данных вы можете использовать разбивку на страницы, чтобы уменьшить потребление памяти и повысить производительность запросов.
В следующем примере демонстрируется использование проекции и выбора определенных столбцов с помощью LINQ для минимизации извлекаемых данных.
В терминале выполните эту команду, чтобы создать новое консольное приложение под названием EFDemo:
dotnet new console -o EFDemo && cd EFDemo
Затем выполните следующую команду, чтобы сначала установить поставщика базовых баз данных SQLite Entity Framework:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Затем добавьте класс модели с именем Model.cs и включите следующий код. В этом коде хранятся имена домашних животных и их владельцев. Приложение будет имитировать хранение данных для службы домашних животных и ветеринарии.
using Microsoft.EntityFrameworkCore; public class PetContext : DbContext { public virtual DbSet Owners { get; set; } public virtual DbSet Pets { get; set; } public string DbLocation { get; } public PetContext() { var folder = Environment.SpecialFolder.LocalApplicationData; var folderPath = Environment.GetFolderPath(folder); DbLocation = System.IO.Path.Join(folderPath, "demoDB.db"); } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite($"Data Source={DbLocation}"); } public class Owner{ public int ownerID { get; set; } public string ownerName { get; set; } public string city { get; set; } public string email {get; set;} public List OwnedPets { get; } = new(); } public class Pet { public int petId { get; set; } public string animalType { get; set; } public int ownerID { get; set; } public Owner Owner { get; set; } }
Теперь запустите миграцию для этой модели, используя следующую команду:
dotnet ef migrations add PetApp && dotnet ef database update
Затем обновите файл Program.cs следующим кодом:
using var db = new PetContext(); var owners = new List<Owner> { new Owner { ownerID = 76755, ownerName = "AndreyEx", city = "Moscow", email = "AndreyEx@AndreyEx.ru" }, new Owner { ownerID = 7676, ownerName = "Maxim", city = "Sochi", email = "Maxim@AndreyEx.ru" }, db.AddRange(owners); db.SaveChanges(); var owner1 = (from owner in db.Owners where owner.ownerID == 76755 select owner).FirstOrDefault<Owner>(); Console.WriteLine(owner1.ownerName); var owner1pets = new List<Pet> { new Pet { animalType = "Dog" }, new Pet { animalType = "Cat" }, new Pet { animalType = "Dog" }, new Pet { animalType = "Parrot" }, new Pet { animalType = "Dog" }, new Pet { animalType = "Squirrel" }, new Pet { animalType = "Cat" } }; owner1.OwnedPets.AddRange(owner1pets); db.SaveChanges(); var owner2 = (from owner in db.Owners where owner.ownerID == 7676 select owner.ownerName); Console.WriteLine(owner2.FirstOrDefault()); Console.ReadKey();
Обратите внимание, что в этом примере для краткости используется всего несколько записей.
Теперь взгляните на эти два фрагмента:
var owner1 = (from owner in db.Owners where owner.ownerID == 76755 select owner).FirstOrDefault<Owner>();
var owner2 = (from owner in db.Owners where owner.ownerID == 7676 select owner.ownerName);
Когда вы запускаете программу с помощью команды dotnet run в вашем терминале, она находит имя владельца с идентификатором 76755 и имя другого владельца с идентификатором 7676. Два запроса выполняют одно и то же, но первый извлекает больше данных, чем второй. Второй запрос извлекает только имя владельца, в то время как первый возвращает все столбцы.
Второй запрос является более эффективным, поскольку он извлекает только имя владельца без остальных строк таблицы. Этот пример иллюстрирует, как незначительные проблемы могут вызывать узкие места в производительности по мере роста приложения.
Вы также можете использовать быструю и отложенную загрузку для повышения производительности. Быстрая загрузка извлекает связанные данные в начальном запросе. Для реализации быстрой загрузки используйте метод Include , который предписывает запросу включать связанные данные при извлечении объекта. Это позволяет избежать более чем одного отключения базы данных.
Чтобы получить данные owner1 с информацией о домашних животных, измените запрос следующим образом:
var owner1 = (from owner in db.Owners where owner.ownerID == 76755 select owner) .Include(pets => pets.Pets) .FirstOrDefault();
Отложенная загрузка загружает связанные данные при обращении к навигационному свойству в модели данных, что означает, что связанные данные загружаются только по вашему запросу.
Чтобы использовать отложенную загрузку в демонстрационном приложении, измените оператор OnConfiguring в классе model следующим образом:
protected override void OnConfiguring(DbContextOptionsBuilder options) => options. .UseLazyLoadingProxies() // Это недавно добавленный метод .UseSqlite($"Data Source={DbLocation}"); }
Затем, чтобы лениво загружать OwnedPets, вы можете сделать его виртуальным, как показано ниже:
public virtual List<Pet> OwnedPets { get; set; };
Ключевое слово virtual сообщает, что EF OwnedPets — это свойство навигации, которое оно может переопределять. Это позволяет избежать перегрузки памяти данными, которые вы, возможно, не используете, в отличие от быстрой загрузки, которая загружает связанные данные независимо от того, используете вы их или нет. Какой метод вам следует использовать, будет зависеть от того, какой из них наиболее подходит в соответствии с вашими потребностями.
Другой способ использования LINQ для повышения производительности запросов — разбиение на страницы. Это полезно для загрузки больших объемов данных или отображения их в пользовательском интерфейсе. Существует два типа разбивки на страницы: смещение и набор ключей.
В этом примере демонстрируется смещенная разбивка на страницы для отображения десятой страницы, где количество результатов, отображаемых на странице, равно 25.
var position = 9 * 25; var nextPage = db.Pets .OrderBy(b => b.petID) .Skip(position) .Take(25) .ToList();
После рассмотрения использования запросов LINQ в следующем разделе рассматривается использование кэширования для повышения производительности EF.
Кэширование снижает частоту обращений к базе данных, помогая предотвратить нагрузку на ресурсы системы. В следующих разделах рассматриваются три типа кэширования.
Кэширование запросов в EF включает настройку максимального размера кэшей и времени ожидания очистки кэшей. .NET Core Entity Framework имеет встроенное кэширование запросов, которое кэширует результаты запросов LINQ-to-Entities. Тем не менее, он ограничен тем, что не предоставляет явного контроля над политиками удаления кэша или сроками действия.
Вы можете использовать менеджер пакетов, такой как Entity Framework Plus, чтобы обойти это. Затем вы можете легко использовать метод FromCache для извлечения записей в последующих вызовах из кэша.
Например, вы можете заменить запрос owner1 в своем коде на этот:
using Z.EntityFramework.Plus; /*Эта строка должна быть добавлена в начало файла, чтобы включить пакет Entity Framework Plus*/
var owner1 = (from owner in db.Owners where owner.ownerID == 76755 select owner). FromCache().ToList()
Кэширование второго уровня недоступно в Entity Framework Core по умолчанию. EF 6 поддерживает кэширование второго уровня с помощью сторонних поставщиков упаковки, таких как NCache или Entity Framework Plus. У этих поставщиков есть методы и инструменты для настройки кэшей и их политик удаления, синхронизации кэша с данными в базах данных и использования тегов кэша для удаления кэшей. Чтобы добавить Entity Framework Plus в свой проект, выполните приведенную ниже команду в вашем терминале:
dotnet add package Z.EntityFramework.Plus.EFCore --version 7.21.0
Для NCache вы можете воспользоваться инструкциями, размещенными на их официальном веб-сайте.
Удаление кэша удаляет старые и неиспользуемые данные из кэш-памяти, освобождая кэш-память для хранения новых и обновленных данных. Вы должны установить время истечения срока действия для удаления данных. Например, при использовании Entity Framework Plus приведенный ниже код устанавливает срок действия кэша через две минуты относительно текущей временной метки.
using Z.EntityFramework.Plus; /*Эта строка должна быть добавлена в начало файла, чтобы включить пакет Entity Framework Plus*/
var options = new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2.0) };
var owner1 = (from owner in db.Owners where owner.ownerID == 76755 select owner). FromCache().ToList()
В этом руководстве Entity Framework рассмотрены преимущества использования EF, проблемы, которые могут возникнуть при его использовании, и способы повышения производительности фреймворка. Проблемами были неэффективные стратегии запросов, неправильное отслеживание и отсутствие кэширования. Вы увидели, что можете использовать функции, предоставляемые LINQ, для улучшения ваших запросов, отслеживать только там, где это необходимо, и использовать кэширование в ваших запросах.
Использовать EF для оптимизации производительности базы данных просто. Используя функции, предоставляемые LINQ, вы можете реализовать проекцию, ускоренную загрузку, отложенную загрузку и разбивку на страницы, чтобы сделать ваши запросы максимально эффективными. Это быстро повысит производительность ваших приложений на основе EF.