Entity Framework Core — первая программа
Entity Framework Core — первая программа
EF Core — ORM для .NET
Программа на C# часто хранит данные в базе — таблицы с строками и столбцами (как Excel, только для сервера). Писать каждый раз сырой SQL вручную утомительно: легко ошибиться в синтаксисе, забыть экранировать строки, смешать логику приложения с запросами.
Entity Framework Core (EF Core) — официальная библиотека Microsoft, которая называется ORM (Object-Relational Mapping, «отображение объектов на таблицы»). Вы описываете обычные классы C# (Book, Author), а EF Core:
- переводит запросы на языке LINQ в SQL;
- следит, какие объекты вы изменили в памяти, и при сохранении формирует
INSERT/UPDATE/DELETE; - ведёт миграции — версии схемы БД в коде, чтобы коллега или сервер получили те же таблицы, что и у вас.
Если ADO.NET — прямой доступ: вы сами пишете SQL и читаете строки, то EF Core ускоряет типичный CRUD (создать, прочитать, обновить, удалить). При этом полезно иногда смотреть, какой SQL реально уходит в базу — для этого ниже включим логирование.
Мы соберём консольное приложение «Книги» на SQLite — один файл books.db на диске, без установки SQL Server. Тот же DbContext потом подключим к ASP.NET Core. Обзор всех подходов: 44. Язык запросов LINQ: 29.
Словарь перед стартом
| Термин | Простыми словами |
|---|---|
| База данных (БД) | Хранилище таблиц; в нашем примере — файл books.db. |
| Таблица | Набор строк одного типа; у нас — Books с колонками Id, Title, Year. |
| Сущность (entity) | Класс C#, одна строка таблицы — один объект Book. |
| DbContext | «Сессия» работы с БД: набор таблиц (DbSet), настройка подключения, вызов SaveChanges. |
| DbSet<T> | Коллекция сущностей, привязанная к таблице (DbSet<Book> → таблица Books). |
| Миграция | C#-файл с инструкциями «создать таблицу / добавить колонку»; применяется командой database update. |
| Code First | Сначала пишем классы, EF сам предлагает схему таблиц. |
| CRUD | Create, Read, Update, Delete — четыре базовые операции с данными. |
| LINQ | Запросы к коллекциям и DbSet на C# (Where, OrderBy); EF переводит их в SQL. |
| Отложенное выполнение | Запрос LINQ выполняется в БД только когда вызываете ToListAsync, FirstAsync и т.п. |
Что получится
| Шаг | Результат |
|---|---|
| Модель | Класс Book → таблица Books |
| Миграция | Файлы в Migrations/, команда dotnet ef database update |
| CRUD | Добавить, прочитать, изменить, удалить через DbContext |
| Запрос | LINQ Where / OrderBy → SQL на стороне БД |
Требования
- .NET SDK 8 или новее (
dotnet --versionв терминале). - Глобальный инструмент для миграций — отдельная утилита
dotnet-ef, она читает ваш проект и генерирует файлы миграций:
dotnet tool install --global dotnet-ef
dotnet ef --version
Если версия выводится — инструмент установлен. Обновление: dotnet tool update --global dotnet-ef.
Создание проекта
dotnet new console -n BooksEf -o BooksEf
cd BooksEf
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
| Пакет | Зачем |
|---|---|
EntityFrameworkCore.Sqlite | Провайдер: EF знает, как говорить с SQLite. |
EntityFrameworkCore.Design | Нужен только при разработке для команд dotnet ef migrations; в готовом exe на сервере часто достаточно только Sqlite. |
Консольное приложение (console) — программа без окна браузера: весь вывод в терминал. Так проще отладить работу с БД до веба.
Модель и контекст
Класс книги
Models/Book.cs:
namespace BooksEf.Models;
public class Book
{
public int Id { get; set; }
public string Title { get; set; } = "";
public int Year { get; set; }
}
Разбор свойств:
Id— первичный ключ: уникальный номер строки. EF по соглашению считает свойство с именемIdилиBookIdключом таблицы.Title— название книги.= ""задаёт значение по умолчанию, чтобы компилятор не ругался на неинициализированную строку.Year— год издания, целое число.
Один объект Book в памяти соответствует одной строке таблицы Books.
DbContext — точка входа в БД
Data/AppDbContext.cs:
using BooksEf.Models;
using Microsoft.EntityFrameworkCore;
namespace BooksEf.Data;
public class AppDbContext : DbContext
{
public DbSet<Book> Books => Set<Book>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// Файл books.db появится рядом с exe после миграции
options.UseSqlite("Data Source=books.db");
}
}
| Строка | Смысл |
|---|---|
: DbContext | Базовый класс EF: подключение, отслеживание изменений, SaveChanges. |
DbSet<Book> Books | «Таблица книг» в коде: db.Books.Add(...), db.Books.Where(...). |
Set<Book>() | Фабрика набора сущностей внутри контекста. |
OnConfiguring | Здесь указывают строку подключения — куда писать данные. |
UseSqlite("Data Source=books.db") | SQLite хранит всё в файле books.db в рабочей папке. |
В реальном API строку подключения обычно берут из appsettings.json и передают через DI (внедрение зависимостей), а OnConfiguring оставляют пустым или используют только для консольных утилит — см. раздел «EF Core в ASP.NET Core».
Первая миграция
Из папки проекта (BooksEf):
dotnet ef migrations add InitialCreate
dotnet ef database update
Что происходит по шагам:
migrations add InitialCreate— EF сравнивает ваши классы (Book) с текущей БД (пока пусто) и генерирует класс миграции в папкеMigrations/с методамиUp(применить) иDown(откатить).database update— выполняетсяUp: создаётся файлbooks.dbи таблицаBooksс колонками под свойства класса.
Появятся папка Migrations/ и файл books.db. Откат последней неприменённой миграции в разработке: dotnet ef migrations remove (в продакшене откаты планируют отдельно).
:::tip Что делает dotnet ef?
Утилита загружает ваш проект, находит DbContext, строит модель таблиц и пишет C#-код миграции. Без пакета Microsoft.EntityFrameworkCore.Design команда dotnet ef проект не «увидит».
:::
CRUD в Program.cs
Полный пример:
using BooksEf.Data;
using BooksEf.Models;
using var db = new AppDbContext();
// Create
db.Books.Add(new Book { Title = "Clean Code", Year = 2008 });
db.Books.Add(new Book { Title = "CLR via C#", Year = 2012 });
await db.SaveChangesAsync();
// Read
var recent = await db.Books
.Where(b => b.Year >= 2010)
.OrderBy(b => b.Title)
.ToListAsync();
foreach (var b in recent)
Console.WriteLine($"{b.Id}: {b.Title} ({b.Year})");
// Update
var first = await db.Books.OrderBy(b => b.Id).FirstAsync();
first.Year = 2009;
await db.SaveChangesAsync();
// Delete
var toRemove = await db.Books.FirstAsync(b => b.Title == "CLR via C#");
db.Books.Remove(toRemove);
await db.SaveChangesAsync();
Разбор по операциям
using var db = new AppDbContext();
- Создаётся контекст — «сеанс» работы с БД.
usingгарантирует вызовDisposeв конце: соединение освободится, даже если будет исключение.
Create — Add + SaveChangesAsync
db.Books.Add(new Book { Title = "Clean Code", Year = 2008 });
await db.SaveChangesAsync();
Addпомечает объект как новый: в таблице строки ещё нет,Idчасто присваивается после сохранения.SaveChangesAsyncотправляет в БД накопленные изменения одной транзакцией (всё или ничего). Для двухAddперед однимSaveChangesEF сгенерирует дваINSERT.
Read — LINQ и отложенное выполнение
var recent = await db.Books
.Where(b => b.Year >= 2010)
.OrderBy(b => b.Title)
.ToListAsync();
| Часть выражения | Что делает |
|---|---|
db.Books | Обращение к таблице как к коллекции. |
.Where(b => b.Year >= 2010) | Фильтр: в SQL уйдёт WHERE Year >= 2010. |
.OrderBy(b => b.Title) | Сортировка по названию. |
.ToListAsync() | Здесь запрос уходит в БД и возвращается список в память. |
До вызова ToListAsync / FirstAsync / CountAsync запрос только строится — это отложенное выполнение (deferred execution). Так EF может объединить несколько условий в один SQL.
Update — изменить свойство и сохранить
var first = await db.Books.OrderBy(b => b.Id).FirstAsync();
first.Year = 2009;
await db.SaveChangesAsync();
FirstAsyncзагружает одну строку; EF отслеживает этот объект.- Изменение
first.Yearпомечает сущность как изменённую;SaveChangesAsyncсгенерируетUPDATE.
Delete — Remove + сохранение
db.Books.Remove(toRemove);
await db.SaveChangesAsync();
Removeпомечает строку на удаление → SQLDELETE.
Увидеть SQL (для обучения)
Временно в OnConfiguring после UseSqlite:
options.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
Перезапустите программу — в консоли появятся сгенерированные SELECT, INSERT, UPDATE. Так проще связать LINQ из 29 с реальной БД и заметить, например, лишние запросы (проблема N+1 — в 44).
EF Core в ASP.NET Core
В веб-проекте контекст регистрируют в Program.cs — контейнер DI создаёт один DbContext на HTTP-запрос (lifetime Scoped):
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("Default")));
// В контроллере или Minimal API:
public class BooksController(AppDbContext db) : ControllerBase
{
[HttpGet]
public async Task<List<Book>> GetAll() =>
await db.Books.AsNoTracking().ToListAsync();
}
| Элемент | Зачем |
|---|---|
Scoped DbContext | Один контекст на запрос; корректное отслеживание изменений и одна транзакция на действие. |
AsNoTracking() | Только чтение: EF не хранит копию объектов для UPDATE — быстрее и меньше памяти. |
Строка в appsettings.json | Разные БД для разработки и продакшена без пересборки. |
Полный API с PostgreSQL: 453. Первая программа ASP.NET: 4511.
Частые ошибки
| Симптом | Что проверить |
|---|---|
dotnet ef не найден | dotnet tool install --global dotnet-ef |
| Нет design-time factory | Пакет Microsoft.EntityFrameworkCore.Design в .csproj |
Пустая таблица после Add | Забыли await db.SaveChangesAsync() |
| «Cannot translate method X» | В Where вызвали метод C#, который EF не умеет перевести в SQL — см. 291 |
| Много запросов на один экран (N+1) | Для связанных таблиц — .Include() — 44 |
books.db не там, где ждёте | Путь относителен к рабочей папке при dotnet run (часто корень проекта) |
Что попробовать
- Добавьте поле
AuthorвBookи новую миграцию:dotnet ef migrations add AddAuthor→dotnet ef database update. - Замените SQLite на PostgreSQL: пакет
Npgsql.EntityFrameworkCore.PostgreSQLи строка подключения в конфиге. - Реализуйте тот же CRUD на ADO.NET / Dapper и сравните объём кода.
Дальше
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C# как язык платформы .NET - устройство проекта, роль `.cs`-файлов и базовые принципы организации кода. C# — это современный, типизированный язык программирования общего назначения, разработанный корпорацией Microsoft. Справочник-шпаргалка по конфигурациям в C — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Набор советов, правил, принципов и обычаев в разработке на этом языке. Кавычки, точки, запятые, скобки и прочие знаки препинания. Ключевые слова C# - назначение базовых конструкций языка и примеры их применения в типичном коде. Набор функций, которые включены в стандартную библиотеку языка. Пространства имён в C# - организация модулей, `file-scoped namespace` и поддержание чистой структуры кода. манипулировать данными (арифметические, логические, сравнительные операторы). Самый базовый способ ветвления — оператор if. Он проверяет условие и, если оно истинно (true), выполняет блок кода. Обработка исключений в C# - типы исключений, `try/catch/finally` и практики надежного кода. Платформо-зависимые исключения — например, PlatformNotSupportedException используется в кроссплатформенных API, когда функция недоступна на текущей ОС.C# - язык программирования платформы .NET
Что требуется знать перед началом изучения языка программирования C#
Справочник по конфигурациям в C#
Рекомендации по разработке на C#
Синтаксис и пунктуация в C#
Ключевые слова языка C#
Встроенные функции и методы C#
Пространства имён в C#
Управляющие конструкции и логические операторы
Условные выражения и ветвления
Обработка исключений в C#
Иерархия классов исключений в C#