Почитать больше о предварительной версии 1 и присоединяйтесь к обсуждению на GitHub Discussions: https://github.com/dotnet/core/discussions/9167
#dotnet #csharp
@csharp_ci
Please open Telegram to view this post
VIEW IN TELEGRAM
🎊 Сегодня были выпущены новые версии .NET 9.0.1 и 8.0.12
https://github.com/dotnet/announcements/issues/341
#dotnet #aspnetcore #efcore #csharp
@csharp_ci
https://github.com/dotnet/announcements/issues/341
#dotnet #aspnetcore #efcore #csharp
@csharp_ci
🔥 EF Core 10 принес нормальные JOIN'ы в LINQ
Больше не нужно вспоминать, как извращаться с GroupJoin + DefaultIfEmpty, чтобы сделать обычный LEFT JOIN.
Теперь есть прямые методы:
✅ LeftJoin
✅ RightJoin
И они делают ровно то, что ты пишешь:
«Оставь все из левой таблицы и подтяни правые записи, если есть совпадения».
Плюсы
- Читаемость выше
- Код короче и очевиднее
- Транслируется в тот же SQL, что и раньше, но без боли
Примерно так LINQ наконец становится ближе к привычному SQL-пониманию разработчика: пишешь join — получаешь join, без магии и обходных путей.
Подробнее про LeftJoin и RightJoin в EF Core 10
#dotnet #efcore #csharp #linq #backend #devtools
Больше не нужно вспоминать, как извращаться с GroupJoin + DefaultIfEmpty, чтобы сделать обычный LEFT JOIN.
Теперь есть прямые методы:
✅ LeftJoin
✅ RightJoin
И они делают ровно то, что ты пишешь:
«Оставь все из левой таблицы и подтяни правые записи, если есть совпадения».
Плюсы
- Читаемость выше
- Код короче и очевиднее
- Транслируется в тот же SQL, что и раньше, но без боли
Примерно так LINQ наконец становится ближе к привычному SQL-пониманию разработчика: пишешь join — получаешь join, без магии и обходных путей.
Подробнее про LeftJoin и RightJoin в EF Core 10
#dotnet #efcore #csharp #linq #backend #devtools
🧠 EF Core и Repository: когда паттерн мешает, а не помогает
👶 Junior: использует EF Core прямо в контроллере
🧑 Middle: строит BaseRepository, IUnitOfWork, IOrderRepository, IOrderDataAccess...
🧓 Senior: снова использует EF Core — без репозиториев
Почему так?
Сначала Repository Pattern кажется удобным:
4 метода на CRUD — всё аккуратно.
Но как только домен растёт, появляются проблемы:
- Репозиторий на каждую сущность
- Общая логика между сущностями? Куда её девать?
- Репозитории раздуваются до 10+ методов
- Тестируемость становится фикцией: мокаем абстракцию от абстракции
А что насчёт "мы вдруг сменим базу"?
В 99% случаев — не смените.
EF Core и так абстрагирует SQL.
А при переходе на NoSQL придётся переписывать модели, запросы и подход целиком.
А что с "это улучшает разделение ответственности"?
На деле:
- В сервисах висит куча репозиториев
- Общая логика размыта
- Больше обвязки, больше боли, меньше пользы
✅ DbContext уже реализует Repository и Unit of Work.
И это официально указано в исходниках EF Core.
🔥 17 000+ разработчиков уже ушли от репозиториев к практичному использованию EF Core в:
- N-Layered архитектуре
- Clean Architecture
- Vertical Slice
- Specification Pattern
- Интеграционных тестах с in-memory
📖 Подробнее:
https://antondevtips.com/blog/why-you-dont-need-a-repository-in-ef-core
#dotnet #efcore #architecture #backend #repositorypattern
👶 Junior: использует EF Core прямо в контроллере
🧑 Middle: строит BaseRepository, IUnitOfWork, IOrderRepository, IOrderDataAccess...
🧓 Senior: снова использует EF Core — без репозиториев
Почему так?
Сначала Repository Pattern кажется удобным:
4 метода на CRUD — всё аккуратно.
Но как только домен растёт, появляются проблемы:
- Репозиторий на каждую сущность
- Общая логика между сущностями? Куда её девать?
- Репозитории раздуваются до 10+ методов
- Тестируемость становится фикцией: мокаем абстракцию от абстракции
А что насчёт "мы вдруг сменим базу"?
В 99% случаев — не смените.
EF Core и так абстрагирует SQL.
А при переходе на NoSQL придётся переписывать модели, запросы и подход целиком.
А что с "это улучшает разделение ответственности"?
На деле:
- В сервисах висит куча репозиториев
- Общая логика размыта
- Больше обвязки, больше боли, меньше пользы
✅ DbContext уже реализует Repository и Unit of Work.
И это официально указано в исходниках EF Core.
🔥 17 000+ разработчиков уже ушли от репозиториев к практичному использованию EF Core в:
- N-Layered архитектуре
- Clean Architecture
- Vertical Slice
- Specification Pattern
- Интеграционных тестах с in-memory
📖 Подробнее:
https://antondevtips.com/blog/why-you-dont-need-a-repository-in-ef-core
#dotnet #efcore #architecture #backend #repositorypattern
💡 Soft delete в EF Core без лишней логики в сервисах
Удалять данные физически — не всегда хорошая идея.
Логи, аудит, восстановление, аналитика — всё это требует soft delete.
Вот удобный способ реализовать его через EF Core interceptor.
Что делает перехватчик:
- Проверяет
- Если состояние сущности —
- Меняет его на
- Устанавливает:
-
-
В итоге:
Вы вызываете обычный:
А в базе:
- запись не удаляется
- просто помечается как удалённая
Плюсы подхода:
- никакой логики soft delete в сервисах и репозиториях
- единая точка обработки
- чистый доменный код
- безопасное удаление по всему приложению
Важно:
Если у вас есть связанные сущности (navigation properties),
перехватчик нужно дополнительно расширить — каскадное soft-удаление EF Core не делает автоматически.
Soft delete через interceptor — это один из самых чистых production-подходов для EF Core.
#dotnet #EFCore #Backend #Architecture #CSharp
Удалять данные физически — не всегда хорошая идея.
Логи, аудит, восстановление, аналитика — всё это требует soft delete.
Вот удобный способ реализовать его через EF Core interceptor.
Что делает перехватчик:
- Проверяет
ChangeTracker на сущности с интерфейсом ISoftDeletable- Если состояние сущности —
Deleted- Меняет его на
Modified- Устанавливает:
-
IsDeleted = true-
DeletedOnUtc = DateTime.UtcNowВ итоге:
Вы вызываете обычный:
context.Remove(entity);
А в базе:
- запись не удаляется
- просто помечается как удалённая
Плюсы подхода:
- никакой логики soft delete в сервисах и репозиториях
- единая точка обработки
- чистый доменный код
- безопасное удаление по всему приложению
Важно:
Если у вас есть связанные сущности (navigation properties),
перехватчик нужно дополнительно расширить — каскадное soft-удаление EF Core не делает автоматически.
Soft delete через interceptor — это один из самых чистых production-подходов для EF Core.
#dotnet #EFCore #Backend #Architecture #CSharp
🐳 «Используй 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
⚡️ Почему Repository поверх EF Core часто превращается в лишний слой
Многие .NET-проекты начинают с отдельных репозиториев:
Сначала всё выглядит аккуратно. Затем появляются новые фильтры, сортировки и бизнес-правила, а репозиторий разрастается до десятков похожих методов.
Проблема в том, что
Один из вариантов решения — Specification Pattern.
Каждая спецификация описывает отдельное условие выборки:
Спецификации можно переиспользовать и комбинировать:
Что это даёт:
- фильтры не размножаются по репозиториям
- запросы остаются рядом с бизнес-правилами
- спецификации проще тестировать
- условия можно комбинировать
-
Repository Pattern полезен, когда действительно изолирует сложную инфраструктуру или несколько источников данных. Но создавать отдельный репозиторий для каждой сущности только ради шаблона часто означает лишнее усложнение архитектуры.
#dotnet #csharp #efcore #architecture
Многие .NET-проекты начинают с отдельных репозиториев:
GetPostsByUser(...)
GetPopularPosts(...)
GetPostsByCategory(...)
GetRecentViralPosts(...)
Сначала всё выглядит аккуратно. Затем появляются новые фильтры, сортировки и бизнес-правила, а репозиторий разрастается до десятков похожих методов.
Проблема в том, что
DbContext уже предоставляет возможности, близкие к Repository и Unit of Work. Дополнительный слой часто усложняет запросы, скрывает возможности EF Core и создаёт абстракцию поверх абстракции.Один из вариантов решения — Specification Pattern.
Каждая спецификация описывает отдельное условие выборки:
var specification =
new ViralPostSpecification(minLikesCount: 150);
var posts = await dbContext.Posts
.ApplySpecification(specification)
.Select(post => post.ToDto())
.ToListAsync(cancellationToken);
Спецификации можно переиспользовать и комбинировать:
var combinedSpec =
recentSpec.And(highEngagementSpec);
Что это даёт:
- фильтры не размножаются по репозиториям
- запросы остаются рядом с бизнес-правилами
- спецификации проще тестировать
- условия можно комбинировать
-
IQueryable продолжает переводиться в SQLRepository Pattern полезен, когда действительно изолирует сложную инфраструктуру или несколько источников данных. Но создавать отдельный репозиторий для каждой сущности только ради шаблона часто означает лишнее усложнение архитектуры.
#dotnet #csharp #efcore #architecture