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; } // навигационное свойство }