Перейти к основному содержимому

Entity Framework Core — первая программа

Разработчику

Play ITЗагрузка интерактивного демо…


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. Обзор всех подходов: Работа с базами данных и ORM в C#. Язык запросов LINQ: LINQ - язык интегрированных запросов. Полный API операторов: Справочник по LINQ.

Как читать эту статью

Идите по шагам в порядке разделов — сначала модель и DbContext, затем миграции, потом CRUD. После каждого шага запускайте проект, чтобы закрепить результат в реальном выводе терминала.


Словарь перед стартом

ТерминПростыми словами
База данных (БД)Хранилище таблиц; в нашем примере — файл books.db.
ТаблицаНабор строк одного типа; у нас — Books с колонками Id, Title, Year.
Сущность (entity)Класс C#, одна строка таблицы — один объект Book.
DbContext"Сессия" работы с БД: набор таблиц (DbSet), настройка подключения, вызов SaveChanges.
DbSet<T>Коллекция сущностей, привязанная к таблице (DbSet<Book> → таблица Books).
МиграцияC#-файл с инструкциями "создать таблицу / добавить колонку"; применяется командой database update.
Code FirstСначала пишем классы, EF сам предлагает схему таблиц.
CRUDCreate, 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 --version
  • Глобальный инструмент для миграций — отдельная утилита dotnet-ef, она читает ваш проект и генерирует файлы миграций:
dotnet tool install --global dotnet-ef
dotnet ef --version

Если версия выводится — инструмент установлен. Обновление: dotnet tool update --global dotnet-ef.

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:

Код ITЗагрузка примера кода…

СтрокаСмысл
: 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

Что происходит по шагам:

  1. migrations add InitialCreate — EF сравнивает ваши классы (Book) с текущей БД (пока пусто) и генерирует класс миграции в папке Migrations/ с методами Up (применить) и Down (откатить).
  2. database update — выполняется Up: создаётся файл books.db и таблица Books с колонками под свойства класса.

Появятся папка Migrations/ и файл books.db. Откат последней неприменённой миграции в разработке: dotnet ef migrations remove (в продакшене откаты планируют отдельно).

dotnet ef migrations remove
Что делает dotnet ef?

Утилита загружает ваш проект, находит DbContext, строит модель таблиц и пишет C#-код миграции. Без пакета Microsoft.EntityFrameworkCore.Design команда dotnet ef проект не "увидит".


CRUD в Program.cs

Полный пример:

Код ITЗагрузка примера кода…


Разбор по операциям

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 перед одним SaveChanges EF сгенерирует два 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 помечает строку на удаление → SQL DELETE.

Увидеть SQL (для обучения)

Временно в OnConfiguring после UseSqlite:

options.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);

Перезапустите программу — в консоли появятся сгенерированные SELECT, INSERT, UPDATE. Так проще связать LINQ из LINQ - язык интегрированных запросов с реальной БД и заметить, например, лишние запросы (проблема N+1 — в Работа с базами данных и ORM в C#).


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: Приложение с S3, PostgreSQL и ASP.NET Core Web API. Первая программа ASP.NET: Первая программа на ASP.NET Core.


Частые ошибки

СимптомЧто проверить
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 — см. Справочник по LINQ
Много запросов на один экран (N+1)Для связанных таблиц — .Include()Работа с базами данных и ORM в C#
books.db не там, где ждётеПуть относителен к рабочей папке при dotnet run (часто корень проекта)

Что попробовать

  1. Добавьте в модель Book новое поле Author, затем создайте и примените миграцию:
dotnet ef migrations add AddAuthorToBook
dotnet ef database update
  1. Замените SQLite на PostgreSQL: пакет Npgsql.EntityFrameworkCore.PostgreSQL и строка подключения в конфиге.
  2. Реализуйте тот же CRUD на ADO.NET / Dapper и сравните объём кода.

Частые ошибки новичков в EF Core

  • Изменили модель, но забыли создать новую миграцию.
  • Выполнили Add, Update или Remove, но не вызвали SaveChangesAsync.
  • Путают IQueryable и IEnumerable, из-за чего часть фильтрации уходит из SQL в память.
  • Оставляют лог SQL включённым в production.

Дальше


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.


В подборках

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Первые шаги (маршрут подборки) — Первая программа на JavaFX, ADO.NET / Dapper — первая программа, Первая программа на Spring Framework, Первая программа на ASP.NET Core, Первая программа на JavaBean, Blazor — первая программа.