⚡ В EF Core чаще всего используют два подхода:
- Database-First — быстрое создание моделей из готовой БД
- Code-First — проектирование схемы через C# классы и миграции
Но оба могут тормозить разработку при сложных связях и настройках.
👉 Есть третий путь — Model-First.
Вы визуально проектируете модель, а она автоматически превращается в EF Core сущности и схему БД.
🛠 Один из лучших инструментов — Entity Developer от Devart:
- drag-and-drop интерфейс вместо ручного кода
- меньше рутины и ошибок в конфигурации
- точная генерация кода и SQL-скриптов
- быстрее дизайн → генерация → обновление моделей
Меньше кода ✍️, больше скорости 🚀.
#dotnet #efcore #csharp #devtools
- Database-First — быстрое создание моделей из готовой БД
- Code-First — проектирование схемы через C# классы и миграции
Но оба могут тормозить разработку при сложных связях и настройках.
👉 Есть третий путь — Model-First.
Вы визуально проектируете модель, а она автоматически превращается в EF Core сущности и схему БД.
🛠 Один из лучших инструментов — Entity Developer от Devart:
- drag-and-drop интерфейс вместо ручного кода
- меньше рутины и ошибок в конфигурации
- точная генерация кода и SQL-скриптов
- быстрее дизайн → генерация → обновление моделей
Меньше кода ✍️, больше скорости 🚀.
#dotnet #efcore #csharp #devtools
🧩 Несколько DbContext в одном приложении на EF Core — когда это нужно
Использовать несколько DbContext в одном приложении — не ошибка, а часто отличное архитектурное решение.
Вот когда это оправдано:
- 🗃 Работа с несколькими базами данных
- 🧠 Разделение областей ответственности
- 🧩 Архитектура modular monolith
- ⚡ Использование read replicas
Автор применил это при создании модульного монолита:
каждый модуль имел собственную схему в БД и отдельный DbContext в коде.
Самое интересное — как EF Core справляется с миграциями и разными схемами баз данных.
Разбор и пошаговое решение — здесь:
🔗 https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application
#DotNet #EFCore #CSharp #Backend #Architecture #DesignPatterns
Использовать несколько DbContext в одном приложении — не ошибка, а часто отличное архитектурное решение.
Вот когда это оправдано:
- 🗃 Работа с несколькими базами данных
- 🧠 Разделение областей ответственности
- 🧩 Архитектура modular monolith
- ⚡ Использование read replicas
Автор применил это при создании модульного монолита:
каждый модуль имел собственную схему в БД и отдельный DbContext в коде.
Самое интересное — как EF Core справляется с миграциями и разными схемами баз данных.
Разбор и пошаговое решение — здесь:
🔗 https://milanjovanovic.tech/blog/using-multiple-ef-core-dbcontext-in-single-application
#DotNet #EFCore #CSharp #Backend #Architecture #DesignPatterns
🐳 «Используй Testcontainers вместо in-memory» - это только половина правды
Все уже выучили: EF Core InMemory provider - не интеграционный тест.
Он не ловит:
- баги в LINQ-трансляции
- ограничения БД
- коллации
- реальные типы колонок
- поведение конкретного SQL-провайдера
Окей, заменили на реальный PostgreSQL через Testcontainers. Победа? Не совсем.
Вот что начинается дальше.
1. Вы получили «медленное враньё» вместо «быстрого»
Поднимать контейнер на каждый тест-класс - быстрый способ превратить CI из 30 секунд в 8 минут.
Нормальный вариант:
- один контейнер на всю тестовую сессию
- изоляция данных между тестами через Respawn
- без пересоздания базы и контейнера каждый раз
Respawn чистит таблицы с учётом графа foreign keys за миллисекунды.
2. Транзакционный откат ≠ реальный сценарий
Трюк «обернули тест в транзакцию и откатили» красиво выглядит, но ломается, когда в коде есть:
- свои транзакции
- несколько SaveChanges
- фоновые операции
- поведение, завязанное на commit
В итоге тестируется сценарий, которого в проде нет.
3. Самая коварная ловушка - общий DbContext
Если тест и код используют один экземпляр DbContext, EF может вернуть данные из change tracker, а не из базы.
Тест зелёный, но он врёт: реальный SQL-запрос мог вообще не выполниться.
Между Act и Assert стоит чистить трекер:
4. Бонус, который теряют 90% команд - тест миграций
Реальная БД позволяет прогнать EF-миграции на чистой схеме.
Если миграция падает или схема разъехалась с моделью, вы узнаёте об этом в CI, а не в проде в пятницу вечером.
Пример базового подхода:
Testcontainers - это не галочка «best practice», а смена философии.
Без нормальной изоляции данных вы просто пересели с быстрого вранья на медленное.
А как вы изолируете состояние БД между интеграционными тестами - Respawn, транзакции или пересоздание контейнера?
#dotnet #csharp #testing #efcore
Все уже выучили: EF Core InMemory provider - не интеграционный тест.
Он не ловит:
- баги в LINQ-трансляции
- ограничения БД
- коллации
- реальные типы колонок
- поведение конкретного SQL-провайдера
Окей, заменили на реальный PostgreSQL через Testcontainers. Победа? Не совсем.
Вот что начинается дальше.
1. Вы получили «медленное враньё» вместо «быстрого»
Поднимать контейнер на каждый тест-класс - быстрый способ превратить CI из 30 секунд в 8 минут.
Нормальный вариант:
- один контейнер на всю тестовую сессию
- изоляция данных между тестами через Respawn
- без пересоздания базы и контейнера каждый раз
Respawn чистит таблицы с учётом графа foreign keys за миллисекунды.
2. Транзакционный откат ≠ реальный сценарий
Трюк «обернули тест в транзакцию и откатили» красиво выглядит, но ломается, когда в коде есть:
- свои транзакции
- несколько SaveChanges
- фоновые операции
- поведение, завязанное на commit
В итоге тестируется сценарий, которого в проде нет.
3. Самая коварная ловушка - общий DbContext
Если тест и код используют один экземпляр DbContext, EF может вернуть данные из change tracker, а не из базы.
Тест зелёный, но он врёт: реальный SQL-запрос мог вообще не выполниться.
Между Act и Assert стоит чистить трекер:
Db.ChangeTracker.Clear();
4. Бонус, который теряют 90% команд - тест миграций
Реальная БД позволяет прогнать EF-миграции на чистой схеме.
Если миграция падает или схема разъехалась с моделью, вы узнаёте об этом в CI, а не в проде в пятницу вечером.
Пример базового подхода:
public class IntegrationTestBase : IAsyncLifetime
{
private static readonly PostgreSqlContainer _db =
new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.Build();
private Respawner _respawner = null!;
protected AppDbContext Db = null!;
public async Task InitializeAsync()
{
await _db.StartAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(_db.GetConnectionString())
.Options;
Db = new AppDbContext(options);
// Реальные миграции - заодно проверяем, что они накатываются
await Db.Database.MigrateAsync();
await using var conn = new NpgsqlConnection(_db.GetConnectionString());
await conn.OpenAsync();
_respawner = await Respawner.CreateAsync(conn, new RespawnerOptions
{
DbAdapter = DbAdapter.Postgres,
SchemasToInclude = ["public"]
});
}
// Сброс данных перед каждым тестом - без пересоздания контейнера
protected async Task ResetAsync()
{
await using var conn = new NpgsqlConnection(_db.GetConnectionString());
await conn.OpenAsync();
await _respawner.ResetAsync(conn);
// Иначе тест может читать из кеша, а не из БД
Db.ChangeTracker.Clear();
}
public Task DisposeAsync() => Task.CompletedTask;
}
Testcontainers - это не галочка «best practice», а смена философии.
Без нормальной изоляции данных вы просто пересели с быстрого вранья на медленное.
А как вы изолируете состояние БД между интеграционными тестами - Respawn, транзакции или пересоздание контейнера?
#dotnet #csharp #testing #efcore