C# (C Sharp) programming
18.1K subscribers
980 photos
52 videos
8 files
782 links
По всем вопросам- @notxxx1

Реестр РКН: https://clck.ru/3Fk3kb

#VRHSZ
Download Telegram
🖥Первая предварительная версия dotnet 9 уже здесь, наряду с обновлениями для #aspnetcore, #dotnetmaui, #efcore и других!

Почитать
больше о предварительной версии 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
🔥 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
🧠 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
💡 Soft delete в EF Core без лишней логики в сервисах

Удалять данные физически — не всегда хорошая идея.
Логи, аудит, восстановление, аналитика — всё это требует 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 стоит чистить трекер:


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-проекты начинают с отдельных репозиториев:


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 продолжает переводиться в SQL

Repository Pattern полезен, когда действительно изолирует сложную инфраструктуру или несколько источников данных. Но создавать отдельный репозиторий для каждой сущности только ради шаблона часто означает лишнее усложнение архитектуры.

#dotnet #csharp #efcore #architecture