C# (C Sharp) programming – Telegram
C# (C Sharp) programming
18.7K subscribers
883 photos
47 videos
8 files
737 links
По всем вопросам- @haarrp

C# - обучающий канал Senior C# разработчика.

@ai_machinelearning_big_data - Machine learning

@itchannels_telegram - 🔥лучшие ит-каналы

@csharp_ci - C# академия

@pythonlbooks- книги📚

Реестр РКН: https://clck.ru/3Fk3kb
Download Telegram
⚡️ КАК СДЕЛАТЬ ИНТЕГРАЦИИ БЕЗ ПОТЕРИ ДАННЫХ

Вы регистрируете пользователя и сразу вызываете внешние сервисы:

- сохраняете пользователя в БД
- отправляете welcome-email
- публикуете событие в event bus


public async Task RegisterUserAsync(User user, CancellationToken token)
{
_userRepository.Insert(user);

await _unitOfWork.SaveChangesAsync(token);

await _emailService.SendWelcomeEmailAsync(user, token);
await _eventBus.PublishAsync(new UserRegisteredEvent(user.Id), token);
}

Вопрос
Что здесь может пойти не так?

Проблемы

— Пользователь сохранён, но email не отправился
— Email отправился, но событие не опубликовалось
— Событие ушло, а транзакция в БД откатилась
— При ретраях возможны дубли

Чем больше внешних вызовов, тем больше точек отказа и сложнее обработка ошибок.

Решение — Outbox Pattern

Сохраняем пользователя и событие в одной атомарной транзакции:


public async Task RegisterUserAsync(User user, CancellationToken token)
{
_userRepository.Insert(user);

_outbox.Insert(new UserRegisteredEvent(user.Id));

await _unitOfWork.SaveChangesAsync(token);
}


Дальше отдельный процесс:

• читает сообщения из Outbox
• отправляет email
• публикует события
• помечает как обработанные

Результат

— Нет рассинхронизации между БД и внешними сервисами
— Гарантированная доставка событий
— Возможность безопасных повторов
— Простая и надёжная архитектура для распределённых систем
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет на экран этот код?
Anonymous Quiz
14%
1,2,3 1,2,3
6%
1,2,3 0,0,0
42%
42,2,3 0,0,0
32%
42,2,3 42,2,3
7%
🥒
🔥На Stepik вышел курс: Linux: полный апгрейд твоих скиллов

Хочешь реально понимать, что происходит под капотом твоей системы, а не просто кликать по GUI?

Без глубокого знания базы ты не инженер - ты просто пользователь.

🔹 В курсе ты:

- Освоишь bash, grep, sed, awk - инструменты, которыми живут админы.
- Разберёшь права, процессы, сеть, файловую систему и научишься чинить всё, что падает.
- Настроишь SSH, firewall, systemd, crontab, демоны и автозапуск.
- Научишься анализировать логи, следить за нагрузкой, и не паниковать при 100% CPU.

💡 Формат: пошаговое объяснение базы и разбор важных практик по работе с Linux.

🎯 После курса ты: будешь чувствовать Linux как родную среду и забудешь, что такое “permission denied”.

🚀 24 часа действует скидка 30%

👉 Учиться со скидкой
⚡️ Guard Clauses в .NET: проверяй ошибки одной строкой

Хочешь упростить код и избавиться от вложенных if? Используй guard clauses — быстрые проверки входных данных в самом начале метода.

Вместо длинной логики и вложенности ты сразу останавливаешь выполнение, если данные некорректны. Код становится чище, короче и легче для чтения.

В новых версиях .NET появилось много встроенных проверок:

.NET 6:
- ArgumentNullException.ThrowIfNull

.NET 7:
- ArgumentException.ThrowIfNullOrEmpty

.NET 8:
- ThrowIfNullOrWhiteSpace
- ThrowIfNullOrEmpty (для коллекций)
- ThrowIfZero
- ThrowIfNegative
- ThrowIfGreaterThan

Когда это особенно полезно:
- в сервисах и API для валидации входных данных
- в доменных моделях — чтобы не допустить невалидного состояния
- в конструкторах и фабриках

Главная мысль:
Guard clauses — это не про «больше проверок»,
а про ранний отказ и чистую архитектуру.


public class Order
{
public Order(string customerName, decimal amount)
{
ArgumentException.ThrowIfNullOrWhiteSpace(customerName);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);

CustomerName = customerName;
Amount = amount;
}

public string CustomerName { get; }
public decimal Amount { get; }
}
✔️ Unity обещает создание игр через текстовые команды.

CEO Unity Мэтт Бромберг определил "AI-driven authoring" как приоритетное направление развития компании на 2026 год. Компания готовит инструмент, который сможет собирать казуальные игры с нуля, используя только естественный язык.

Бета-версию Unity AI покажут на GDC в марте 2026 года. Технология будет работать нативно внутри платформы, Unity возьмет топовые внешние модели и доучит их понимать контекст движка.

В компании уверены, что такой подход даст результат лучше, чем попытки заставить универсальные LLM писать сложный игровой код. Цель - окончательная демократизация геймдева, порог входа в который, похоже, скоро исчезнет совсем.
wccftech.com
Please open Telegram to view this post
VIEW IN TELEGRAM
Что выведет на экран этот код?
Anonymous Quiz
19%
enum, enum, enum
14%
enum, enum, object
52%
enum, object, object
7%
object, object, object
8%
🥒
⚡️ КАСТОМИЗАЦИЯ ИСТОРИИ МИГРАЦИЙ В ENTITY FRAMEWORK

В Entity Framework можно гибко настроить таблицу истории миграций и это полезно в реальных проектах.

По умолчанию EF создаёт таблицу __EFMigrationsHistory в стандартной схеме (dbo).
Но часто требуется:

- вынести её в отдельную схему
- изменить имя таблицы
- разделить миграции между модулями
- привести структуру БД к корпоративным стандартам

Это можно сделать прямо при настройке DbContext через метод MigrationsHistoryTable.



services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(
connectionString,
sqlOptions =>
{
sqlOptions.MigrationsHistoryTable(
tableName: "MigrationHistory",
schema: "infrastructure"
);
}));


// Теперь EF будет хранить историю миграций в:
// infrastructure.MigrationHistory
Please open Telegram to view this post
VIEW IN TELEGRAM
⚡️ Async-коммуникация ломает интеграционные тесты.

Вы отправляете событие в одном модуле… и что дальше? Ждать?

Если используете Thread.Sleep — готовьтесь к:
- медленным тестам
- flaky-поведению
- падениям в CI/CD

Решение — не ждать. Опрос (Polling).

Я использую паттерн Retry Assertion для тестирования асинхронного взаимодействия между модулями:

Как это работает:

1. Выполняем команду (Модуль A)
2. Периодически выполняем запрос (Модуль B)
3. Ждём, пока результат появится
4. Делаем assert

Пример:


var userId = await Sender.Send(new RegisterUserCommand(...));

var result = await Poller.WaitAsync(
TimeSpan.FromSeconds(15),
async () =>
{
return await Sender.Send(new GetCustomerQuery(userId));
});

result.Should().NotBeNull();
result.IsSuccess.Should().BeTrue();
Динамическая строка подключения в EF Core - когда и зачем это нужно

Факт
В EF Core можно задать connection string динамически при регистрации DbContext. Для этого используется делегат, который выполняется в момент получения контекста из DI.

Как это работает

При регистрации DbContext вы передаёте функцию, которая получает IServiceProvider.
Внутри неё можно:
- получить любой сервис
- определить текущего пользователя или tenant
- выбрать нужную базу данных
- и только после этого установить строку подключения

Пример


builder.Services.AddDbContext<OrdersDbContext>((sp, o) =>
{
var tenantProvider = sp.GetRequiredService<TenantProvider>();
var connectionString = tenantProvider.GetConnectionString();

o.UseSqlServer(connectionString);
});


Dynamic DbContext configuration - ключевой паттерн для масштабируемых multi-tenant систем.
Если приложение растёт и базы начинают разделяться по клиентам или регионам, этот подход становится стандартом архитектуры.
🖥 Асинхронщина ломает интеграционные тесты? Есть простой паттерн

Проблема:

Вы отправляете событие в одном модуле…
И начинаете ждать.

Если используете Thread.Sleep
готовьтесь к:
- медленным тестам
- flaky-падениям
- ненависти от CI/CD

Решение — Retry Assertion (Polling)

Как работает паттерн:

1. Выполняем команду (Module A)
2. Периодически опрашиваем состояние (Module B)
3. Ждём, пока результат появится
4. Делаем assert

Схема:

- Execute Command (Module A)
- Poll Query (Module B) until result != null
- Assert

Почему это лучше:

- Нет случайных падений
- Тесты работают быстрее
- Полностью детерминированное поведение
- Отлично подходит для async, событий и modular monolith

Главная идея:

Не жди фиксированное время — жди состояние.

Такой подход превращает нестабильные интеграционные тесты в надёжные и быстрые.
Please open Telegram to view this post
VIEW IN TELEGRAM
💡 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
🖥 Разделяйте код с помощью Domain Events

Без событий сервисы быстро превращаются в «комбайн»:
— бизнес-логика
— отправка email
— аналитика
— интеграции
— и всё в одном методе

Каждая новая функция добавляет прямые зависимости.
Метод растёт. Связанность увеличивается. Поддержка усложняется.

С Domain Events подход меняется:

Вы выполняете бизнес-действие и просто публикуете событие.


await _domainEventsDispatcher.DispatchAsync(
[new UserRegisteredDomainEvent(user.Id, user.Email)]
);


Дальше система реагирует сама:

- отправка приветственного письма
- трекинг в аналитике
- интеграции
- любые новые сценарии

И всё это без изменения UserService.

Преимущества:

- никаких жёстких зависимостей
- сервисы меньше и чище
- легче тестировать
- проще масштабировать
- новые фичи добавляются без переписывания существующего кода

Публикуешь один раз - система расширяется сама.
Please open Telegram to view this post
VIEW IN TELEGRAM
⚡️ Exceptions - это для действительно исключительных ситуаций.

Но в реальном приложении они все равно будут случаться, и их нужно обрабатывать предсказуемо.

Самый удобный способ в ASP.NET Core - сделать глобальный обработчик через middleware:

- перехватываем любые необработанные исключения
- логируем ошибку
- возвращаем клиенту единый JSON-ответ (ProblemDetails) с 500

Пример:


public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;

public ExceptionHandlingMiddleware(
RequestDelegate next,
ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception exception)
{
_logger.LogError(
exception, "Exception occurred: {Message}", exception.Message);

var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "Server Error"
};

context.Response.StatusCode =
StatusCodes.Status500InternalServerError;

await context.Response.WriteAsJsonAsync(problemDetails);
}
}
}
В .NET 8 появился простой способ сделать HttpClient устойчивым к сбоям — буквально одной строкой.

Microsoft добавила библиотеку Microsoft.Extensions.Http.Resilience, в которой уже есть готовые pipeline’ы для обработки ошибок при HTTP-запросах.

Что это даёт из коробки:
- Retry при временных сбоях
- Timeout
- Circuit Breaker
- Rate limiting
- Защиту от перегрузки

Подключается максимально просто:


services.AddHttpClient<GitHubService>(static httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
})
.AddStandardResilienceHandler();