This media is not supported in your browser
VIEW IN TELEGRAM
Как можно использовать состояние при работе с MediatR?
Популярный nuget-пакет MediatR по умолчанию не делает хендлеры stateful. То есть при обработке сообщения каждый раз создается новый инстанс хендлера.
Само по себе это не проблема, и я уверен, что это осознанное архитектурное решение... Но бывают ситуации, когда хочется иметь хотя бы какое-то состояние.
Например, что если тебе нужен кэш?
Самое простое решение: вынести ответственность за состояние в отдельный компонент. А дальше пусть dependency injection подсовывает этот компонент в хендлер.
Проблема решена✌️
Полное видео тут
👉 @KodBlog
Популярный nuget-пакет MediatR по умолчанию не делает хендлеры stateful. То есть при обработке сообщения каждый раз создается новый инстанс хендлера.
Само по себе это не проблема, и я уверен, что это осознанное архитектурное решение... Но бывают ситуации, когда хочется иметь хотя бы какое-то состояние.
Например, что если тебе нужен кэш?
Самое простое решение: вынести ответственность за состояние в отдельный компонент. А дальше пусть dependency injection подсовывает этот компонент в хендлер.
Проблема решена
Полное видео тут
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🥴1
Git Tip: юзай команду
👉 @KodBlog
git checkout -, чтобы быстро переключаться на предыдущую ветку, с которой ты только что работал.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🥴7😁2🔥1😐1
Начиная с Entity Framework 10 у нас появилась поддержка именованных (и нескольких) query filters 🥳
👉 @KodBlog
// Entity Framework 10 will support Named query filters:
modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDeletionFlter", b => !b.IsDeleted)
.HasQueryFilter("TenantFilter", b => b.TenantId == tenantId);
// this allows us to remove explicit query filters:
var allBlogs = await
context.Blogs.IgnoreQueryFilters(["SoftDeletionFlter"]).ToListAsync();
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
API Gateway как RESTful-микросервис (версия 6.0)
Эта новая версия поддерживает .NET 10.
То есть теперь поддерживаются .NET 8/9/10.
hubs.li/Q041B9js0
👉 @KodBlog
Эта новая версия поддерживает .NET 10.
То есть теперь поддерживаются .NET 8/9/10.
hubs.li/Q041B9js0
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🤔2
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🔥3
Если ты все еще сидишь на Swashbuckle в .NET 10, то вот что ты пропустил.
Swashbuckle нормально не обновлялся уже больше года. Поэтому Microsoft убрали его из шаблонов .NET 9+ и сделали свой встроенный вариант.
Замена:
Встроенный, легче, поддерживается и развивается.
UI: Scalar лучше, чем Swagger UI. Быстрее, выглядит приятнее, и темная тема есть из коробки.
Миграция реально на 5 минут: убираешь Swashbuckle, добавляешь
Статья, которая поможет переехать: https://codewithmukesh.com/blog/dotnet-swagger-alternatives-openapi/
👉 @KodBlog
Swashbuckle нормально не обновлялся уже больше года. Поэтому Microsoft убрали его из шаблонов .NET 9+ и сделали свой встроенный вариант.
Замена:
Microsoft.AspNetCore.OpenApiВстроенный, легче, поддерживается и развивается.
UI: Scalar лучше, чем Swagger UI. Быстрее, выглядит приятнее, и темная тема есть из коробки.
Миграция реально на 5 минут: убираешь Swashbuckle, добавляешь
builder.Services.AddOpenApi(), и готово.Статья, которая поможет переехать: https://codewithmukesh.com/blog/dotnet-swagger-alternatives-openapi/
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20🔥4👎2
Асинхронное общение делает интеграционные тесты болью.
Ты шлёшь событие из одного модуля и... ждёшь?
Если опираться на
Не жди. Опрашивай.
Я использую паттерн “retry assertion” для проверки логики между модулями в модульных монолитах:
▪️ Выполняем команду (модуль A)
▪️ Поллим запрос (модуль B), пока
▪️ Делаем ассерты по результату
Так флейковый, медленный тест превращается в детерминированный и быстрый.
Почитай про эту стратегию в статье
👉 @KodBlog
Ты шлёшь событие из одного модуля и... ждёшь?
Если опираться на
Thread.sleep, твой CI/CD быстро начнёт страдать.Не жди. Опрашивай.
Я использую паттерн “retry assertion” для проверки логики между модулями в модульных монолитах:
Result != nullТак флейковый, медленный тест превращается в детерминированный и быстрый.
Почитай про эту стратегию в статье
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤5
Статические абстрактные члены интерфейсов появились в C# 11. Основной мотивацией для них является поддержка обобщённой математики. Но они могут быть полезны и в других сценариях. Например, упростить код регистрации и использования типов разделов пользовательской конфигурации.
Например, вы хотите настроить клиент API в appSettings.json:
Хорошей практикой является связать раздел с отдельным типом:
Упростим регистрацию через статические члены интерфейсов. Сначала определим интерфейс:
Все реализации этого интерфейса должны иметь статическое свойство SectionName:
Добавим методы расширения для регистрации секций конфигурации:
Теперь можно регистрировать различные секции конфигурации так:
👉 @KodBlog
Например, вы хотите настроить клиент API в appSettings.json:
{
…
"GoogleAPI": {
"ApiKey": "…",
"OrganizationId": "…",
…
}
}Хорошей практикой является связать раздел с отдельным типом:
public class GoogleOptions {
public string? ApiKey { get; init; }
public string? OrganizationId { get; init; }
…
}
…
// добавляем в Program.cs
builder.Configuration
.Configure<GoogleOptions>(
builder.Configuration.GetSection("GoogleAPI"));
…
// внедряем через DI
public class GoogleClient(
IOptions<GoogleOptions> options) {
// …
}Упростим регистрацию через статические члены интерфейсов. Сначала определим интерфейс:
public interface IConfigOptions
{
static abstract string SectionName { get; }
}
Все реализации этого интерфейса должны иметь статическое свойство SectionName:
public class GoogleOptions: IConfigOptions {
public static string SectionName => "GoogleAPI";
// остальные члены
}Добавим методы расширения для регистрации секций конфигурации:
public static class OptionsExtensions {
public static IHostApplicationBuilder
Configure<TOptions>(
this IHostApplicationBuilder builder)
where TOptions : class, IConfigOptions
{
var sect = builder.Configuration
.GetSection(TOptions.SectionName);
builder.Services.Configure<TOptions>(sect);
return builder;
}
}Теперь можно регистрировать различные секции конфигурации так:
builder.Configure<GoogleOptions>()
.Configure<GitHubOptions>()
.Configure<WeatherOptions>()
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🥴5
Все еще держишь бизнес-логику в сервисах Application-слоя?
Это обычно знак, что доменная модель почти ничего не делает.
Если твои entity выглядят как голые DTO с полями, то у тебя анемичная доменная модель.
Как рефакторнуться в сторону behavior-driven дизайна:
✅ Проталкивай бизнес-логику в домен
✅ Делай методы, которые жестко держат инварианты
✅ Делай так, чтобы невалидные состояния были невозможны в принципе
✅ Пересмотри границы сервисов
В итоге код проще сопровождать, тестировать и расширять.
Пошаговый разбор рефакторинга
👉 @KodBlog
Это обычно знак, что доменная модель почти ничего не делает.
Если твои entity выглядят как голые DTO с полями, то у тебя анемичная доменная модель.
Как рефакторнуться в сторону behavior-driven дизайна:
В итоге код проще сопровождать, тестировать и расширять.
Пошаговый разбор рефакторинга
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
This media is not supported in your browser
VIEW IN TELEGRAM
Чувак собрал терминальный осциллограф для OPC UA на чистом C#, с рендерингом через брайлевские символы для 8x “пиксельного” разрешения.
Пара C# моментов, из-за которых это было прям кайфово:
1. Брайлевские символы как пиксельный движок
Вид осциллографа рисует осциллограммы через Unicode braille (U+2800–U+28FF). Каждая ячейка терминала становится матрицей точек 2×4, то есть дает в 8 раз больше разрешения по сравнению с обычным “символ-в-ячейке” рендерингом. Кастомный класс BrailleCanvas делает маппинг координат, алгоритм линий Брезенхэма для связных трасс, и композитинг слоев, чтобы сигнал всегда был поверх точек сетки. Около 270 строк, без графических зависимостей.
2. Terminal.Gui v2 для UI
TreeView, TableView, диалоги, меню, поддержка мыши. Осциллограф это кастомный View, который переопределяет OnDrawingContent и общается с ConsoleDriver для брайлевого рендера.
3. Подписки OPC UA вместо polling
Значения пушатся через события MonitoredItem.Notification, а в UI прокидываются через Application.Invoke(). Данные прилетают настолько быстро, насколько их публикует сервер.
4. Кроссплатформа
Windows/Linux/macOS без условной компиляции.
Исходники
👉 @KodBlog
Пара C# моментов, из-за которых это было прям кайфово:
1. Брайлевские символы как пиксельный движок
Вид осциллографа рисует осциллограммы через Unicode braille (U+2800–U+28FF). Каждая ячейка терминала становится матрицей точек 2×4, то есть дает в 8 раз больше разрешения по сравнению с обычным “символ-в-ячейке” рендерингом. Кастомный класс BrailleCanvas делает маппинг координат, алгоритм линий Брезенхэма для связных трасс, и композитинг слоев, чтобы сигнал всегда был поверх точек сетки. Около 270 строк, без графических зависимостей.
2. Terminal.Gui v2 для UI
TreeView, TableView, диалоги, меню, поддержка мыши. Осциллограф это кастомный View, который переопределяет OnDrawingContent и общается с ConsoleDriver для брайлевого рендера.
3. Подписки OPC UA вместо polling
Значения пушатся через события MonitoredItem.Notification, а в UI прокидываются через Application.Invoke(). Данные прилетают настолько быстро, насколько их публикует сервер.
4. Кроссплатформа
Windows/Linux/macOS без условной компиляции.
Исходники
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17
Поддержка LeftJoin в Entity Framework 10+ 🥳
Круто, что наконец-то это добавили в 10-й версии.
👉 @KodBlog
Круто, что наконец-то это добавили в 10-й версии.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🤣3
Типы связей в EF Core, по-простому:
1:N -> HasOne().WithMany()
1:1 -> HasOne().WithOne().HasForeignKey<T>()
M:N -> HasMany().WithMany()
M:N с данными в связи -> отдельная join-entity (явная сущность для таблицы-связки)
И да: всегда задавай OnDelete явно. Дефолтный Cascade угробил больше баз, чем кривые запросы.
Полный гайд с примерами кода
👉 @KodBlog
1:N -> HasOne().WithMany()
1:1 -> HasOne().WithOne().HasForeignKey<T>()
M:N -> HasMany().WithMany()
M:N с данными в связи -> отдельная join-entity (явная сущность для таблицы-связки)
И да: всегда задавай OnDelete явно. Дефолтный Cascade угробил больше баз, чем кривые запросы.
Полный гайд с примерами кода
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤4
Ты можешь комбинировать raw SQL-запросы и LINQ в EF Core.
Вот пример: сначала выполняем SELECT через SQL, а потом докидываем цепочкой
Вот код с картинки (как есть):
Обрати внимание: raw SQL-запрос здесь корректно параметризован, хотя на вид кажется, что мы используем интерполяцию строк.
👉 @KodBlog
Вот пример: сначала выполняем SELECT через SQL, а потом докидываем цепочкой
OrderBy + Skip + Take уже через LINQ.Вот код с картинки (как есть):
var startDate = new DateOnly(2023, 1, 1);
var ordersIn2023 = await dbContext
.Database
.SqlQuery<OrderSummary>(
$"SELECT * FROM OrderSummaries AS o WHERE o.CreatedOn >= {startDate}")
.OrderBy(o => o.Id)
.Skip(10)
.Take(5)
.ToListAsync();
-- SQL sent to the DB
SELECT s.Id, s.CustomerId, s.TotalPrice, s.CreatedOn
FROM (
SELECT * FROM OrderSummaries AS o WHERE o.CreatedOn >= @p0
) AS s
ORDER BY s.Id
OFFSET @p1 ROWS FETCH NEXT @p2 ROWS ONLY
Обрати внимание: raw SQL-запрос здесь корректно параметризован, хотя на вид кажется, что мы используем интерполяцию строк.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤3👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
Улучшения по производительности таймзон в dotnet 11
В .NET 11 добавили кэш переходов таймзон по годам (per-year cache) для
По бенчмаркам ускорение такое:
▪️ Windows: примерно в 2.4–3.9 раза быстрее (например,
▪️ Linux: примерно в 1.6–4.7 раза быстрее (например,
Плюс новый путь с кэшем заменил старую “слоеную” логику конвертации (которую годами патчили) и заодно поправил несколько багов, где конвертация могла давать неправильный результат.
👉 @KodBlog
В .NET 11 добавили кэш переходов таймзон по годам (per-year cache) для
TimeZoneInfo, из-за чего конвертация времени стала заметно быстрее. Кэш хранит все переходы за конкретный год в UTC и убирает повторные lookup-и правил при каждом пересчете. По бенчмаркам ускорение такое:
ConvertTimeFromUtc с 48.0 нс до 12.2 нс) ConvertTimeToUtc с 63.8 нс до 13.6 нс) Плюс новый путь с кэшем заменил старую “слоеную” логику конвертации (которую годами патчили) и заодно поправил несколько багов, где конвертация могла давать неправильный результат.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤2👍1
Твой GET-эндпоинт возвращает все строки из таблицы. На 100 записей ок. На 100К это уже катастрофа.
3 вещи, которые нужны любому list-эндпоинту в .NET:
- Пагинация (
- Динамическая сортировка (по нескольким колонкам)
- Поиск (без учета регистра)
И все это одним SQL-запросом через EF Core 10.
https://codewithmukesh.com/blog/pagination-sorting-searching-aspnet-core-webapi/
👉 @KodBlog
3 вещи, которые нужны любому list-эндпоинту в .NET:
- Пагинация (
Skip + Take)- Динамическая сортировка (по нескольким колонкам)
- Поиск (без учета регистра)
И все это одним SQL-запросом через EF Core 10.
https://codewithmukesh.com/blog/pagination-sorting-searching-aspnet-core-webapi/
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯6👍3
Два варианта N+1 в Entity Framework.
👉 @KodBlog
// ================================
// ПРОБЛЕМА N+1 — ВАРИАНТ 1
// Ленивый (Lazy) loading навигационного свойства
// (в EF Core Lazy Loading по умолчанию ВЫКЛЮЧЕН)
// ================================
//
// Что происходит:
// - 1 запрос загружает всех пользователей
// - Затем КАЖДОЕ обращение к user.Orders автоматически триггерит отдельный запрос
// - Итого = 1 + N запросов
var users = context.Users.ToList(); // Запрос #1
foreach (var user in users)
{
// Запрос #2, #3, #4... #N+1 (по одному на пользователя, триггерится автоматически)
var orders = user.Orders.ToList();
}
// ================================
// ПРОБЛЕМА N+1 — ВАРИАНТ 2
// Явный запрос к дочерним данным внутри цикла
// ================================
//
// Что происходит:
// - 1 запрос загружает всех пользователей
// - Затем КАЖДАЯ итерация выполняет отдельный запрос за заказами этого пользователя
// - Итого = 1 + N запросов
var users2 = context.Users.ToList(); // Запрос #1
foreach (var user in users2)
{
// Запрос #2, #3, #4... #N+1 (по одному на пользователя)
var orders = context.Orders
.Where(o => o.UserId == user.Id)
.ToList();
}
Please open Telegram to view this post
VIEW IN TELEGRAM
😐15❤1👍1
Как заставить правила по данным применяться к КАЖДОМУ запросу, не копипастя одно и то же?
Global Query Filters в EF Core. Что важно знать:
1. Определяешь фильтр один раз в OnModelCreating, и EF Core сам добавляет WHERE ко всем запросам
2. Soft delete: HasQueryFilter(p => !p.IsDeleted) прячет удаленные записи без физического удаления
3. Multi-tenancy: ссылайся на поле DbContext для динамической фильтрации по tenant, запросы будут параметризованные
4. Named Filters (EF Core 10): можно задать НЕСКОЛЬКО фильтров на одну сущность и отключать их независимо
5. IgnoreQueryFilters(["SoftDelete"]): выключаешь ОДИН фильтр, остальные остаются активными
6. Паттерн с интерфейсом ISoftDelete: применяешь фильтры ко ВСЕМ сущностям, которые реализуют интерфейс, через expression trees
7. Индексируй колонки, по которым фильтруешь: IsDeleted и TenantId должны быть с индексами, потому что фильтры срабатывают на каждом запросе
👉 @KodBlog
Global Query Filters в EF Core. Что важно знать:
1. Определяешь фильтр один раз в OnModelCreating, и EF Core сам добавляет WHERE ко всем запросам
2. Soft delete: HasQueryFilter(p => !p.IsDeleted) прячет удаленные записи без физического удаления
3. Multi-tenancy: ссылайся на поле DbContext для динамической фильтрации по tenant, запросы будут параметризованные
4. Named Filters (EF Core 10): можно задать НЕСКОЛЬКО фильтров на одну сущность и отключать их независимо
5. IgnoreQueryFilters(["SoftDelete"]): выключаешь ОДИН фильтр, остальные остаются активными
6. Паттерн с интерфейсом ISoftDelete: применяешь фильтры ко ВСЕМ сущностям, которые реализуют интерфейс, через expression trees
7. Индексируй колонки, по которым фильтруешь: IsDeleted и TenantId должны быть с индексами, потому что фильтры срабатывают на каждом запросе
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🤔2❤1