Entity Framework Core Flashcards
EF Core Основы
EF Core - Microsoft opensourse .NET ORM-технология (object-relational mapping - отображения данных на реальные объекты) для доступа к данным, позволяя абстрагироваться от самой базы данных и ее таблиц и работать с данными как с объектами классом независимо от типа хранилища и использовать LINQ для выборки данных.
Управление БД - осуществляется через свойство Database (тип DatabaseFacade) класса DbContext.
Основные компоненты и возможности EF:
- DbContext (управляет взаимодействием с БД, выполнение запросов)
- DbSet (Это коллекции объектов определенного класса, которые представляют собой таблицы в базе данных)
- LINQ to Entities (для написания запросов LINQ к базе данных, которые затем транслируются в SQL-запросы)
- Миграции (ля управления изменениями в структуре БД)
- Конвенции и настройки (EF использует набор конвенций для автоматического маппинга классов на таблицы и их свойств на столбцы (с помощью аннотаций или Fluent API))
Подход DB First
Подход DB First - БД существует изначально:
- Нужно добавить классы, соотв. таблицам (вручную или автоматически (Scaffolding(Reverse Engineering)))
Подход Code First
Подход Code First - начала пишется код, а потом по нему создается база данных и ее таблицы.
Подход Model First
Подход Model First - изначально создается модель данных (Empty EF Designer Model), а по ней создается БД:
- нужно создать классы таблиц
- нужно реализовать DbContext
EF Core CRUD (Основные операции с данными)
1) Create:
db.Users.Add(tom);
db.SaveChanges();
2) Read:
var users = db.Users.ToList();
3) Update:
User? user = db.Users.FirstOrDefault();
user.Name = “Bob”;
db.SaveChanges();
4) Delete:
User? user = db.Users.FirstOrDefault();
db.Users.Remove(user);
db.SaveChanges();
EF Core Конфигурация подключения
Способы установки конфигурации подключения:
1) через конструктор DbContext
public ApplicationContext(DbContextOptions<ApplicationContext> options): base(options) {...}
2) Переопределение метода OnConfiguring() класса DbContext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.SetBasePath(Directory.GetCurrentDirectory())
.Build();</ApplicationContext>
optionsBuilder.UseSqlite(config.GetConnectionString("DefaultConnection")); }
EF Core Логирование операций
Установка логирования в методе OnConfiguring:
//в консоль
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
//в файл
optionsBuilder.LogTo(_fileStream.WriteLine, LogLevel.Information);
Категории сообщений логирования:
- Database.Command (выполняемый код SQL)
- Database.Connection
- Database.Transaction
- Migration
- Infrastructure
Установка категории логирования:
optionsBuilder.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name });
Провайдеры БД
- PostgreSQL (Npgsql.EntityFrameworkCore.PostgreSQL):
optionsBuilder.UseNpgsql(“Host=localhost;Port=5432;Database=usersdb2;Username=postgres;Password=123456789”); - MS SQL Server (Microsoft.EntityFrameworkCore.SqlServer):
optionsBuilder.UseSqlServer(@”Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;”); - SQLite (Microsoft.EntityFrameworkCore.Sqlite):
optionsBuilder.UseSqlite(“Data Source=helloapp.db”); - MySQL (Pomelo.EntityFrameworkCore.MySql):
optionsBuilder.UseMySql(“server=localhost;user=root;password=123456789;database=usersdb;”, new MySqlServerVersion(new Version(8, 0, 25)));
EF Core LINQ to Entities
LINQ to Entities - технология, основанная на LINQ, для выборки данных, транслирует выражения в sql-запросы, соотв. провайдеру БД.
Способы написать запрос:
1) через операторы LINQ:
var users = (from user in db.Users.Include(p=>p.Company)
where user.CompanyId == 1
select user).ToList();
2) через методы расширения:
var users = db.Users.Include(p=>p.Company).Where(p=> p.CompanyId == 1);
EF Core Отслеживание объектов и AsNoTracking
Отслеживание объектов - это когда контекст данных извлекает данные из БД и EF Core помещает их в кэш и отслеживает изменения с этими данными до тех пор, пока не будет вызван метод SaveChanges(), который фиксирует все изменения в БД (по умолчанию все отслеживается).
AsNoTracking - настройка, чтобы данные не кэшировались (применяется к IQueryable), запрос происходит быстрее:
var users = db.Users.AsNoTracking();
ChangeTracker - позволяет отключить отслеживание в целом для объекта контекста, а не только для запроса:
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
EF Core Как происходит выполнение запросов
Как происходит выполнение запросов:
1) Выражения LINQ преобразуются EF Core в объект запроса (понятный для конкретного провайдера БД)
2) объект запроса кэшируется (чтобы не пересоздавать его при повторном его выполнении)
3) объект запроса передается провайдеру БД, который транслирует его в SQL запрос
4) БД обрабатывает SQL запрос и возвращает результат
5) EF Core получает результат:
a) если запрос отслеживаемый, тогда:
- если данные уже есть в контексте, EF Core возвращает то, что уже есть в контексте
- если данных нет в контексте, то EF Core создает по этим данным новые объекты, добавляет в контекст, начинает их отслеживать и возвращает их пользователю
b) если запрос не отслеживаемый, то EF Core создает по этим данным объекты и возвращает их пользователю
EF Core IEnumerable и IQueryable
Выполнение LINQ-запроса начинается, когда обращаемся к результатам запроса (в цикле, ToList(), Count()).
IEnumerable и IQueryable (наследует IEnumerable ) - это результаты LINQ-запросов, наборы данных.
1) IEnumerable:
- запрос выполняется сразу и полностью, поэтому получение данных происходит быстро
- загружает все данные, а фильтрует уже на стороне клиента
2) IQueryable:
- предоставляет удаленный доступ к БД
- фильтрация на стороне БД, т.е. на стороне клиента получаем не все данные, а только нужные
- тратится меньше памяти
- чуть медленнее
EF Core Фильтры запросов уровня модели
Фильтры запросов уровня модели (Model-level query filters) - позволяют определить предикат (фильтр) запроса LINQ непосредственно в метаданных модели. Данный фильтр будет применяться ко всем запросам:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasQueryFilter(u => u.Age > 17 && u.RoleId == RoleId);
}</User>
EF Core Синхронизация DbContext и БД
Контекст данных по умолчанию отслеживает изменения в загруженных сущностях, а затем отправляет запросы на обновление в БД при вызове методаSaveChange()
.
Запросы на обновление отправляются только для тех свойств, которые действительно изменились. Также отслеживаемые объекты синхронизируются с изменениями, отправленными в БД (при этом изменения группируются, чтобы уменьшить количество обращений к БД).
EF Core Выполнение SQL запросов
Методы DbSet для выполнения SQL запроса:
1) FromSqlRaw (для получения данных, не должно извлекать связанные данные):
var comps = db.Companies.FromSqlRaw(“SELECT * FROM Companies”).ToList();
2) ExecuteSqlRaw (осуществляет выборку, удаление, обновление, вставку):
int numberOfRowInserted = db.Database.ExecuteSqlRaw(“INSERT INTO Companies (Name) VALUES ({0})”, newComp);
3) FromSqlInterpolated/ExecuteSqlInterpolated (позволяют использовать интерполяцию строк для передачи параметров):
var name = “%Tom%”;
var age = 30;
var users = db.Users.FromSqlInterpolated($”SELECT * FROM Users WHERE Name LIKE {name} AND Age > {age}”).ToList();
Получится SQL-запрос:
@p0=’%Tom%’
@p1=30
SELECT * FROM Users WHERE Name LIKE @p0 AND Age > @p1
EF Core Хранимые функции
Способы вызвать хранимую функцию:
1) Через выполнение SQL-запроса (FromSqlRaw, ExecuteSqlRaw):
SqlParameter param = new SqlParameter(“@age”, 30);
var users = db.Users.FromSqlRaw(“SELECT * FROM GetUsersByAge (@age)”, param).ToList();
2) Через проецирование хранимой функции на метод класса:
public IQueryable<User> GetUsersByAge(int age) => FromExpression(() => GetUsersByAge(age));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//регистрируем метод класса ApplicationContext - GetUsersByAge()
modelBuilder.HasDbFunction(() => GetUsersByAge(default));
}</User>
EF Core Хранимые процедуры
Вызов хранимой процедуры - через выполнение SQL-запроса (FromSqlRaw, ExecuteSqlRaw):
SqlParameter param = new(“@name”, “Microsoft”);
var users = db.Users.FromSqlRaw(“GetUsersByCompany @name”, param).ToList();
EF Core Миграции
Если мы меняем модели в Entity Framework, то таблицы в БД тоже должны измениться.
Способы изменить БД под новые модели:
- ручной
- Database.EnsureCreated + Database.EnsureDeleted (если данные в БД неважны)
- Миграция (данные сохраняются)
Миграция - это план перехода базы данных от старой схемы к новой.
Создание миграции в .NET CLI:
dotnet ef migrations add InitialCreate
//применение миграции
dotnet ef database update
После выполнения этих команд в проект будет добавлена папка Migrations с классом миграции:
- содержит все применяемые действия
- содержит текущее состояние модели (ModelSnapshot - нужен для последующих миграций)
Миграция при использовании контекста данных, принимающим параметр в конструкторе:
- будет ошибка Unable to create an object of type 'ApplicationContext'
(т.к. EF Core при выполнении миграции ищет класс, реализующий IDesignTimeDbContextFactory который задает конфигурацию контекста).
- нужно создать реализацию IDesignTimeDbContextFactory:
public class SampleContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
public ApplicationContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<ApplicationContext>();</ApplicationContext></ApplicationContext>
// получаем конфигурацию из файла appsettings.json ConfigurationBuilder builder = new ConfigurationBuilder(); builder.SetBasePath(Directory.GetCurrentDirectory()); builder.AddJsonFile("appsettings.json"); IConfigurationRoot config = builder.Build(); // получаем строку подключения из файла appsettings.json string connectionString = config.GetConnectionString("DefaultConnection"); optionsBuilder.UseSqlite(connectionString); return new ApplicationContext(optionsBuilder.Options); } }
EF Core Бандлы миграций
EF Core 6 позволяет объединить миграции в бандл (в виде одного файла .exe, запуск которого вызовет последовательное применение добавленных миграций):
dotnet ef migrations bundle
EF Core Подходы наследования
Подходы наследования:
1) TPH (Table Per Hierarchy / Таблица на одну иерархию классов) - исп. по умолчанию
2) TPT (Table Per Type):
- Таблица User будет хранить все свойства класса User
- Таблица Employee будет хранить только свои дополнительные данные
//с помощью атрибутов [Table("Employees")] public class Employee : User {...} //с помощью Fluent API modelBuilder.Entity<Employee>().ToTable("Employees");
3) TPC (Table Per Class):
- Столбцы в каждой таблице создаются по всем свойствам, в том числе и унаследованным
- все сущности в иерархии имеют уникальный первичный ключ
//с помощью Fluent API
modelBuilder.Entity<User>().UseTpcMappingStrategy();</User>
EF Core Настройка свойств модели
Переопределение свойств модели:
1) Сопоставление имени свойства и столбца (по умолчанию они должны совпадать):
modelBuilder.Entity<User>().Property(u => u.Id).HasColumnName("user_id");
2) Сопоставление имени таблицы и сущности (по умолчанию должны совпадать):
modelBuilder.Entity<User>().ToTable("People");
3) NULL / NOT NULL:
modelBuilder.Entity<User>().Property(b => b.Name).IsRequired();
4) Настройка имени первичного ключа (по умолчанию исп. id или [имя_класса]Id):
modelBuilder.Entity<User>().HasKey(u => u.Ident);
5) Настройка составного ключа (только с помощью Fluent API):
modelBuilder.Entity<User>().HasKey(u => new { u.PassportSeria, u.PassportNumber});
6) Настройка альтернативных ключей (это свойства, которые должны иметь уникальные значения):
modelBuilder.Entity<User>().HasAlternateKey(u => u.Passport);
7) Настройка индексов (с помощью аннотаций и Fluent API):
modelBuilder.Entity<User>().HasIndex(u => u.Passport);
8) Переопределение генерации значений первичного ключа:
modelBuilder.Entity<User>().Property(b => b.Id).ValueGeneratedNever();
9) Переопределение значения по умолчанию для свойств:
modelBuilder.Entity<User>().Property(u => u.Age).HasDefaultValue(18);
//с помощью функции SQL
modelBuilder.Entity<User>().Property(u => u.CreatedAt).HasDefaultValueSql("DATETIME('now')");
10) Настройка вычисляемых столбцов:
modelBuilder.Entity<User>().Property(u => u.Name).HasComputedColumnSql("FirstName || ' ' || LastName");
11) Установка ограничений для столбцов:
modelBuilder.Entity<User>().Property(b => b.Name).HasMaxLength(50);
12) Исключение свойства из таблицы:
modelBuilder.Entity<User>().Ignore(u=>u.Address);
13) Инициализация БД начальными данными:
//Это сработает при миграции и при создании новой БД
modelBuilder.Entity<User>().HasData(
new User { Id = 1, Name = "Tom", Age = 23 },
new User { Id = 2, Name = "Alice", Age = 26 },
new User { Id = 3, Name = "Sam", Age = 28 }
);</User></User></User></User></User></User></User></User></User></User></User></User></User></User>
14) Собственные типы: свойства вспомогательного объекта прикрепляются к главному
//таблица User будет иметь столбцы, соотв. сущности Profile
modelBuilder.Entity<User>().OwnsOne(u => u.Profile);
15) Комплексные типы (в EF Core 8) - они не имеют первичного ключа, существуют только как часть другой сущности:
modelBuilder.Entity<User>(
builder =>
{
builder.ComplexProperty(user => user.Language);
}
);</User></User>
EF Core Аннотации и Fluent API
Аннотации и Fluent API - предоставляют настройку классов сущностей.
Аннотации - с помощью атрибутов.
Fluent API - с помощью методов
//аннотации
[Column(“user_id”)]
public int Id { get; set; }
//FluentAPI
modelBuilder.Entity<User>().Property(u => u.Id).HasColumnName("user_id");</User>
EF Core Вынесение конфигурации моделей
Вынесение конфигурации моделей - чтобы не загромождать метод OnModelCreating:
- Нужно реализовать EntityTypeConfiguration<T></T>
Пример:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("People").Property(p => p.Name).IsRequired();
builder.Property(p => p.Id).HasColumnName("user_id");</User></User>
} }
//использование
modelBuilder.ApplyConfiguration(new UserConfiguration());
EF Core Внешние ключи и навигационные свойства
Для связей между сущностями в EF Core применяются внешние ключи и навигационные свойства:
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public int CompanyId { get; set; } // внешний ключ public Company? Company { get; set; } // навигационное свойство }
EF Core Настройка отношений между моделями
Fluent API обеспечивает настройку отношений между моделями:
- HasOne (устанавливает навигационное свойство (одиночный объект
) для сущности, к которой применяется настройка)
- HasMany (устанавливает навигационное свойство (коллекцию объектов
) для сущности, к которой применяется настройка)
- WithOne (устанавливает навигационное свойство (одиночный объект
) на стороне связанной сущности)
- WithMany (устанавливает навигационное свойство (коллекцию объектов
) на стороне связанной сущности)
- HasForeignKey (устанавливает внешний ключ)
- HasPrincipalKey (связывает внешний ключ с другим свойством)
Примеры:
- Отношение один к одному:
modelBuilder
.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserKey);</UserProfile></User>
- Отношение один ко многим (1 компания - много пользователей)
modelBuilder.Entity<User>()
.HasOne(u => u.Company)
.WithMany(c => c.Users)
.HasForeignKey(u => u.CompanyInfoKey);</User> - Отношение многие ко многим:
modelBuilder.Entity<Course>()
.HasMany(c => c.Students)
.WithMany(s => s.Courses)
.UsingEntity(j => j.ToTable("Enrollments")); //нужна промежуточная таблица</Course>
EF Core Каскадное удаление
Каскадное удаление - автоматическое удаление зависимой сущности после удаления главной (применяется по умолчанию, если наличие связанной сущности обязательно - NOT NULL).
Настройка каскадного удаления с помощью Fluent API:
- Cascade (зависимая сущность удаляется вместе с главной)
- SetNull (свойство-внешний ключ в зависимой сущности получает значениеnull
)
- Restrict (зависимая сущность никак не изменяется при удалении главной сущности)
//отключение каскадного удаления
modelBuilder.Entity<User>()
.HasOne(u => u.Company)
.WithMany(c => c.Users)
.OnDelete(DeleteBehavior.SetNull);</User>
EF Core Загрузка связанных данных
Связанные данные можно загружать через навигационное свойство.
Стратегии загрузки связанных данных:
- Eager loading(жадная загрузка): с помощью Include(), ThenInclude() и навигац. свойство:
var users = db.Users
.Include(u => u.Company) // подгружаем данные по компаниям LEFT JOIN
.ToList();
- Explicit loading(явная загрузка): с помощью
- Load() (Load может загружать выборочно (не все данные):
db.Users.Where(u => u.CompanyId == company.Id).Load();
- Collection() (применяется, если навигационное свойство представляет коллекцию):
db.Entry(company).Collection(c => c.Users).Load();
- Reference
- Lazy loading(ленивая загрузка - неявная автоматическая загрузка (1 раз, потом из контекста) связанных данных при обращении к навигационному свойству):
optionsBuilder
.UseLazyLoadingProxies() // подключение lazy loading
.UseSqlite(“Data Source=helloapp.db”);
EF Core Проблема параллелизма
Проблема параллелизма - множество пользователей одновременно имеют доступ к одинаковому набору данных и могут эти данные изменять.
Способы решения проблемы параллелизма:
1) Токен параллелизма (позволяет решить проблему параллелизма, когда с одной и той же записью в таблице могут работать одновременно несколько пользователей):
1.1) Атрибут ConcurrencyCheck: при вставке EF Core смотрит не только на Id, но и на исходное значение свойства с атрибутом ConcurrencyCheck, и если оно совпадает, значит никто еще не обновил эту запись, то данные обновляются, иначе DbUpdateConcurrencyException
1.2) IsConcurrencyToken (Метод Fluent API, )
modelBuilder.Entity<User>().Property(b => b.Name).IsConcurrencyToken();
2) Timestamp:
2.1) Атрибут Timestamp (указывает, что значение свойства будет включаться в создаваемое SQL-выражение WHERE при отправке в базу данных команд на обновление и удаление. Значение свойства Timestamp генерируется при добавлении объекта, а также каждый раз, когда пользователь будет обновлять его)
2.1) modelBuilder.Entity<User>().Property(b => b.Timestamp).IsRowVersion();</User></User>
EF Core Провайдеры логирования
Создание провайдера логирования:
- Нужно создать реализацию ILoggerProvider и вложенную в него реализацию ILogger
Использование своего провайдера логирования:
//локальное в рамках контекста данных
db.GetService<ILoggerFactory>().AddProvider(new MyLoggerProvider());</ILoggerFactory>
//глобальное
// устанавливаем фабрику логгера
public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder =>
{
// указываем наш провайдер логгирования
builder.AddProvider(new MyLoggerProvider());
//можно добавить фильтр категории сообщений builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name) .AddProvider(new MyLoggerProvider()); });
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(“Data Source=helloapp.db”);
//регистрируем фабрику логгера
optionsBuilder.UseLoggerFactory(MyLoggerFactory);
}
EF Core Работа с представлениями БД (TableView)
Для классов C#, сопоставляющихся с представлениями БД:
- не надо определять первичный ключ
- изменения в них не отслеживаются
- можно только получать такие данные
- их нельзя использовать как навигационные свойства
Пример:
//коллекция для представления
public DbSet<CompanyProductsGroup> ProductsByCompany { get; set; } = null!;</CompanyProductsGroup>
modelBuilder.Entity<CompanyProductsGroup>(pc => { //указывается, что сущность CompanyProductsGroup не будет иметь первичный ключ pc.HasNoKey(); //указывается, с каким представлением БД будет сопоставляться pc.ToView("View_ProductsByCompany"); });
EF Core Скомпилированные запросы
Скомпилированные запросы - позволяют повысить производительность приложения, путем кэширования запроса в форма, понятную для источника данных (БД) (EF Core может автоматически компилировать и кэшировать запросы на основании хэш-представления выражений LINQ, однако в данном случае будет тратиться время на вычисление хэша), исп. метод CompileQuery.
Пример:
Func<ApplicationContext, int, User?> userById =
EF.CompileQuery((ApplicationContext db, int id) =>
db.Users.Include(c => c.Company).FirstOrDefault(c => c.Id == id));
//использование скомпилированного запроса
var user = userById(db, 1);
EF Core Оптимизации запросов
Оптимизации запросов:
1) Выборка только нужных свойств сущности:
var selected = context.Blogs.Select(b => b.Url);
- это преобразуется в SQL:
SELECT [b].[Url]
FROM [Blogs] AS [b]
2) Ограничение размера набора результатов (данных может быть очень много, поэтому нужно их получать порциями)
3) Не использовать отложенную (Lazy Loading) неявную загрузку связанных сущностей:
- Будет каждый раз при обращении к связанной сущности создаваться запрос к БД для получения связанной сущности с соотв. id
- Лучше загрузить сразу все записи связанной сущности в контекст (исп. Include)
4) Нужно использовать потоковую передачу вместо буферизации в память, если запрос может возвращать большое количество записей:
- т.е. не исп. ToList, ToArray
5) Не отслеживать экземпляры сущностей, если нужно только читать данные, а не обновлять:
- AsNoTracking
6) Использование запросов SQL:
- в некоторых случаях для запроса существует более оптимизированный SQL, который EF не создает
- FromSqlRaw
7) Использовать асинхронные запросы EF Core
8) Правильное использование индексов (нужно анализировать скорость работы и получаемый SQL)
Fluent API в Entity Framework Core для конфигурации сущностей?
Fluent API в EF Core используется для конфигурации сущностей, предлагая больше функциональности, чем аннотации данных, нужно переопределить в классе контекста метод OnModelCreating класса DbContext (в этом методе вы можете настраивать сущности, используя методы ModelBuilder):
//задание первичного ключа:
modelBuilder.Entity().HasKey(e => e.Id);
//задание отношения между таблицами
modelBuilder.Entity<User>()
.HasOne(u => u.Company)
.WithMany(c => c.Users)
.HasForeignKey(u => u.CompanyInfoKey);</User>
//сделать поле обязательным
modelBuilder.Entity().Property(e => e.Name).IsRequired()
Различия между Entity Framework 6 и EF Core
- EF Core кроссплатформенный
- EF Core поддерживает более широкий диапазон поставщиков БД
- EF Core обеспечивает лучшую поддержку асинхронного программирования
- EF Core имеет улучшенную производительность
Как работает отложенная (ленивая) загрузка в EF Core
Ленивая загрузка в EF Core — это процесс, в котором связанные данные автоматически загружаются из базы данных при доступе к свойству навигации.
В сценариях с небольшими объемами данных ленивая загрузка может быть полезной, поскольку она загружает данные только по мере необходимости, сокращая начальное время загрузки. Однако для больших наборов данных или сложных запросов этот метод может привести к проблемам с производительностью из-за множественных обращений к базе данных, известных как проблема «N+1».
заполнение начальных данных в EF Core
Заполнение данных в Entity Framework Core (EF Core) может быть реализовано с помощью метода HasData ModelBuilder в функции OnModelCreating. Это позволяет указать сущности, которые будут добавлены в контекст базы данных при запуске миграций.
DbContext Pooling в EF Core
AddDbContextPool - это метод (ASP.NET Core), используемый для повышения производительности путем повторного использования экземпляров DbContext. Когда приложение запрашивает экземпляр DbContext, вместо создания нового, оно получает его из пула, если он доступен. После этого экземпляр сбрасывается и возвращается в пул:
- снижает накладные расходы на инициализацию и утилизацию экземпляров DbContext
- DbContext по сути становится синглтоном
Транзакции в EF Core
EF Core) обрабатывает транзакции двумя основными методами: неявным и явным. Неявные транзакции автоматически управляются EF Core во время метода SaveChanges(), который оборачивает несколько операций в одну транзакцию.
Явные транзакции, с другой стороны, обеспечивают больший контроль над тем, когда транзакция начинается, фиксируется или откатывается. Это делается с помощью методов BeginTransaction(), Commit() и Rollback() DbContext.Database:
//пример явной транзакции
using(var context = new MyDbContext())
{
using(var transaction = context.Database.BeginTransaction())
{
try
{
// Perform operations here
context.SaveChanges();
transaction.Commit();
}
catch(Exception)
{
transaction.Rollback();
}
}
}
Shadow Properties в EF Core
Shadow Properties в EF Core - это поля, которые не определены в классе сущности .NET, но определены в модели и являются частью схемы базы данных.
Некоторые области применения Shadow Properties:
1) Аудит. Хранение информации, например, времени создания и модификации.
2) Контроль параллельности. Управление параллельностью с помощью полей, таких как время или номера версий.
3) Отслеживание дополнительной информации. Хранение метаданных или флагов, связанных с сущностями.
Shadow Properties настраиваются в методе OnModelCreating() класса контекста.
Доступ к Shadow Properties возможен:
1) через запросы LINQ (через EF.Property):
var blogs = context.Blogs
.OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));</DateTime>
2) через Fluent API:
context.Entry(myBlog).Property(“LastUpdated”).CurrentValue = DateTime.Now;
В каких случаях создается Shadow Properties:
1) Если есть навигационное свойство, но не указан внешний ключ (Неявно):
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
// Since there is no CLR property which holds the foreign // key for this relationship, a shadow property is created. public Blog Blog { get; set; } }
2) Явное указание свойства, которого нет в модели через Fluent API:
modelBuilder.Entity<Blog>()
.Property<DateTime>("LastUpdated");</DateTime></Blog>
шаги по настройке связи «многие ко многим» в EF Core
- Отношение многие ко многим:
modelBuilder.Entity<Course>()
.HasMany(c => c.Students)
.WithMany(s => s.Courses)
.UsingEntity(j => j.ToTable("Enrollments")); //нужна промежуточная таблица</Course>
//в старой версии EF Core нужно было так:
modelBuilder.Entity<StudentCourse>()
.HasKey(t => new { t.StudentId, t.CourseId });</StudentCourse>
modelBuilder.Entity<StudentCourse>() .HasOne(sc => sc.Student) .WithMany(s => s.StudentCourses) .HasForeignKey(sc => sc.StudentId); modelBuilder.Entity<StudentCourse>() .HasOne(sc => sc.Course) .WithMany(c => c.StudentCourses) .HasForeignKey(sc => sc.CourseId);
Можете ли вы объяснить использование метода AsNoTracking в EF Core
Метод AsNoTracking используется для повышения производительности запросов путем предотвращения отслеживания изменений сущностей (т.е. SaveChanges ничего не будет делать).
Когда контекст базы данных извлекает сущности с помощью стандартного запроса, он автоматически отслеживает любые изменения, внесенные в эти сущности. Это позволяет выполнять автоматические обновления при вызове SaveChanges().
- следует проявлять осторожность, поскольку игнорирование отслеживания изменений может привести к устареванию данных, если в течение жизненного цикла контекста где-либо будут внесены изменения.
Метод SaveChanges в EF Core
Метод SaveChanges - сохраняет все изменения, внесенные в этом контексте, в базу данных.
Какова роль ChangeTracker в EF Core
ChangeTracker - важный компонент, который отслеживает и управляет изменениями, происходящими в сущностях, отслеживает все извлеченные сущности, определяя любые изменения, внесенные в них с момента их извлечения из базы данных. Когда вызывается метод SaveChanges(), Change Tracker определяет, какие SQL-выражения следует сгенерировать для обновления базы данных на основе этих отслеживаемых изменений.
Он работает через различные состояния: Добавлено, Неизмененное, Изменено и Удалено. Каждая отслеживаемая сущность имеет одно из этих состояний.
Как бы вы обрабатывали несколько поставщиков баз данных в EF Core
- Нужно создать отдельные классов DbContext для каждого поставщика
Перехватчики команд в EF Core
Перехватчики команд - предоставляют способ перехвата операций с БД, должны реализовать ICommandInterceptor или IDbCommandInterceptor.
Основное применение:
- логирование
- диагностика производительности
Операции JOIN в EF Core
Операции JOIN с помощью Linq:
1) INNER JOIN (внутреннее соединение):
var result = context.Users
.Join(context.UserProfiles,
user => user.UserId,
userProfile => userProfile.UserId,
(user, userProfile) => new { User = user, UserProfile = userProfile })
.ToList();
2) OUTER JOIN (внешнее соединение):
- используется комбинация методов ‘GroupJoin’ и ‘SelectMany’ или ‘DefaultIfEmpty’
var result = context.Users
.GroupJoin(context.UserProfiles,
user => user.UserId,
userProfile => userProfile.UserId,
(user, profiles) => new { user, profiles })
.SelectMany(z => z.profiles.DefaultIfEmpty(),
(x, y) => new { User = x.user, UserProfile = y ?? null })
.ToList();