Что такое Entity Framework Core и зачем он нужен
Entity Framework Core (EF Core) — это современная объектно-реляционная карта (ORM) для платформы .NET, разработанная компанией Microsoft. Она позволяет разработчикам работать с базами данных, используя объекты .NET, и избавляет от необходимости писать большую часть кода доступа к данным.
EF Core — это легковесная, расширяемая и кроссплатформенная версия Entity Framework. Она поддерживает множество СУБД, включая SQL Server, SQLite, PostgreSQL, MySQL, Oracle и другие.
Основные возможности EF Core:
- Автоматическое создание и обновление схемы базы данных на основе модели (Code First)
- Выполнение запросов к базе данных с помощью LINQ (Language Integrated Query)
- Автоматическое отслеживание изменений объектов и генерация SQL-команд для обновления данных
- Поддержка миграций для управления изменениями схемы базы данных
- Поддержка отношений между сущностями (один-к-одному, один-ко-многим, многие-ко-многим)
- Ленивая, жадная и явная загрузка связанных данных
Установка Entity Framework Core
Установка EF Core осуществляется через NuGet Package Manager. В отличие от Python, здесь используется dotnet add package или менеджер пакетов Visual Studio.
Установка через .NET CLI:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
Установка для PostgreSQL (Npgsql):
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
Установка для SQLite:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Установка для MySQL:
dotnet add package Pomelo.EntityFrameworkCore.MySql
Быстрый старт с примером кода
Создадим простое консольное приложение для работы с базой данных книг.
1. Создаем модель данных:
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
}
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public List<Book> Books { get; set; } = new();
}
2. Создаем контекст базы данных:
public class LibraryContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=library.db");
}
}
3. Используем EF Core для работы с данными:
using var context = new LibraryContext();
context.Database.EnsureCreated(); // Создаем БД, если её нет
// Добавление данных
var author = new Author { Name = "Лев Толстой" };
author.Books.Add(new Book { Title = "Война и мир", Year = 1869, Price = 1500m });
author.Books.Add(new Book { Title = "Анна Каренина", Year = 1877, Price = 1200m });
context.Authors.Add(author);
context.SaveChanges();
// Чтение данных
var books = context.Books
.Where(b => b.Year > 1850)
.OrderBy(b => b.Year)
.ToList();
foreach (var book in books)
{
Console.WriteLine($"{book.Title} ({book.Year})");
}
Основные методы и функции Entity Framework Core
1. Add
Описание: Добавляет новую сущность в контекст в состоянии Added. При вызове SaveChanges() будет выполнена INSERT-команда.
Параметры:
object entity— экземпляр сущности для добавления
Пример:
var book = new Book { Title = "Преступление и наказание", Author = "Ф. Достоевский", Year = 1866, Price = 800m };
context.Books.Add(book);
context.SaveChanges();
2. AddRange
Описание: Добавляет коллекцию сущностей в контекст за один вызов. Эффективнее, чем множественные вызовы Add.
Параметры:
IEnumerable entities— коллекция сущностей для добавления
Пример:
var books = new List<Book>
{
new Book { Title = "Идиот", Year = 1869, Price = 900m },
new Book { Title = "Братья Карамазовы", Year = 1880, Price = 1100m }
};
context.Books.AddRange(books);
context.SaveChanges();
3. Find
Описание: Находит сущность по первичному ключу. Сначала проверяет, не загружена ли уже сущность в контекст (избегает лишнего запроса к БД).
Параметры:
params object[] keyValues— значения первичного ключа
Пример:
var book = context.Books.Find(1);
if (book != null)
{
Console.WriteLine($"Найдена книга: {book.Title}");
}
4. FirstOrDefault / First
Описание: Возвращает первый элемент последовательности, удовлетворяющий условию. FirstOrDefault возвращает null, если элемент не найден, First выбрасывает исключение.
Параметры:
Expression<Func<T, bool>> predicate— условие фильтрации (опционально)
Пример:
var book = context.Books.FirstOrDefault(b => b.Title == "Война и мир");
if (book != null)
{
Console.WriteLine($"Цена: {book.Price}");
}
// Без условия — первый элемент
var firstBook = context.Books.First();
5. Where
Описание: Фильтрует последовательность сущностей по заданному условию. Возвращает IQueryable, который можно дальше уточнять.
Параметры:
Expression<Func<T, bool>> predicate— условие фильтрации
Пример:
var expensiveBooks = context.Books
.Where(b => b.Price > 1000m)
.OrderByDescending(b => b.Price)
.ToList();
foreach (var book in expensiveBooks)
{
Console.WriteLine($"{book.Title} — {book.Price} руб.");
}
6. OrderBy / OrderByDescending
Описание: Сортирует результаты запроса по возрастанию (OrderBy) или убыванию (OrderByDescending).
Параметры:
Expression<Func<T, TKey>> keySelector— выражение для выбора ключа сортировки
Пример:
var sortedBooks = context.Books
.OrderBy(b => b.Year)
.ThenBy(b => b.Title)
.ToList();
var sortedDesc = context.Books
.OrderByDescending(b => b.Price)
.ToList();
7. Include
Описание: Выполняет жадную загрузку (eager loading) связанных данных. Позволяет загрузить связанные сущности вместе с основной.
Параметры:
Expression<Func<T, TProperty>> navigationPropertyPath— навигационное свойство для загрузки
Пример:
var authorsWithBooks = context.Authors
.Include(a => a.Books)
.ToList();
foreach (var author in authorsWithBooks)
{
Console.WriteLine($"Автор: {author.Name}");
foreach (var book in author.Books)
{
Console.WriteLine($" - {book.Title}");
}
}
8. ThenInclude
Описание: Позволяет загружать связанные данные второго уровня (например, книги автора и их жанры).
Параметры:
Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath— навигационное свойство для загрузки
Пример:
var authorsWithBooksAndGenres = context.Authors
.Include(a => a.Books)
.ThenInclude(b => b.Genre) // Предполагаем, что у Book есть свойство Genre
.ToList();
9. Update
Описание: Помечает сущность как измененную. При вызове SaveChanges() будет выполнена UPDATE-команда для всех свойств сущности.
Параметры:
object entity— экземпляр сущности для обновления
Пример:
var book = context.Books.Find(1);
if (book != null)
{
book.Price = 2000m;
context.Books.Update(book);
context.SaveChanges();
}
10. Remove
Описание: Помечает сущность для удаления. При вызове SaveChanges() будет выполнена DELETE-команда.
Параметры:
object entity— экземпляр сущности для удаления
Пример:
var book = context.Books.Find(1);
if (book != null)
{
context.Books.Remove(book);
context.SaveChanges();
}
11. Any
Описание: Проверяет, существует ли хотя бы один элемент, удовлетворяющий условию. Эффективнее, чем Count() > 0, так как не загружает все данные.
Параметры:
Expression<Func<T, bool>> predicate— условие проверки (опционально)
Пример:
bool hasExpensiveBooks = context.Books.Any(b => b.Price > 5000m);
Console.WriteLine(hasExpensiveBooks ? "Есть дорогие книги" : "Нет дорогих книг");
12. Count
Описание: Возвращает количество элементов, удовлетворяющих условию. Выполняется на стороне сервера БД.
Параметры:
Expression<Func<T, bool>> predicate— условие подсчета (опционально)
Пример:
int bookCount = context.Books.Count();
int oldBooksCount = context.Books.Count(b => b.Year < 1900);
Console.WriteLine($"Всего книг: {bookCount}, старых книг: {oldBooksCount}");
Реальный практический пример использования
Создадим приложение для управления библиотекой с полным CRUD (Create, Read, Update, Delete).
using Microsoft.EntityFrameworkCore;
// Модели
public class Reader
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<Loan> Loans { get; set; } = new();
}
public class Loan
{
public int Id { get; set; }
public int BookId { get; set; }
public Book Book { get; set; }
public int ReaderId { get; set; }
public Reader Reader { get; set; }
public DateTime LoanDate { get; set; }
public DateTime? ReturnDate { get; set; }
}
// Контекст
public class LibraryContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author>