Как заставить правила по данным применяться к КАЖДОМУ запросу, не копипастя одно и то же?
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
Как маппить параметры query string в Minimal APIs?
(и простые, и сложные)
Query-параметры это опциональная часть URL, которую можно использовать, чтобы фильтровать или менять ответ в зависимости от переданных значений.
В Minimal APIs это делается довольно просто.
Minimal APIs поддерживают два типа query string параметров:
▪️ Простые
▪️ Сложные
Для простых параметров тебе достаточно, чтобы имена совпадали.
Для сложных типов нужно использовать атрибут
Начиная с .NET 7 также появилась поддержка массивов в query-параметрах.
👉 @KodBlog
(и простые, и сложные)
Query-параметры это опциональная часть URL, которую можно использовать, чтобы фильтровать или менять ответ в зависимости от переданных значений.
В Minimal APIs это делается довольно просто.
Minimal APIs поддерживают два типа query string параметров:
Для простых параметров тебе достаточно, чтобы имена совпадали.
Для сложных типов нужно использовать атрибут
[AsParameters], он замаппит свойства из запроса на класс.Начиная с .NET 7 также появилась поддержка массивов в query-параметрах.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🤔1
Я много лет писал в проде и горизонтальный C#, и вертикальный. В итоге пришел вот к чему.
Горизонтальный стиль:
- Пишется быстрее
- Выглядит плотным и типа профессиональным
- Норм для совсем простых выражений
- После 100+ символов превращается в кашу
- Отлично прячет баги на виду (я лично пропустил 3 ошибки с неправильными аргументами в горизонтальном коде)
Вертикальный стиль:
- Занимает на пару секунд больше
- Выглядит слишком очевидно, прям до простоты
- Каждая операция читается отдельно, без напряга
- Дифы чище (одно изменение = одна строка в git)
- Ревью реально ускоряется
Мой дефолт: вертикально. Всегда. Но переключаюсь на горизонтальный, когда выражение реально простое: null-coalesce, базовый ternary, один метод в LINQ.
Правило, по которому решаю: если мне нужно провести глазами по строке горизонтально больше одного раза, значит надо делать вертикально.
Это не теория. Мы перелопатили 100+ .NET сервисов и сравнили время ревью PR до и после. В среднем ревью стало быстрее примерно на 30% после того, как ввели вертикальные стандарты форматирования.
👉 @KodBlog
Горизонтальный стиль:
- Пишется быстрее
- Выглядит плотным и типа профессиональным
- Норм для совсем простых выражений
- После 100+ символов превращается в кашу
- Отлично прячет баги на виду (я лично пропустил 3 ошибки с неправильными аргументами в горизонтальном коде)
Вертикальный стиль:
- Занимает на пару секунд больше
- Выглядит слишком очевидно, прям до простоты
- Каждая операция читается отдельно, без напряга
- Дифы чище (одно изменение = одна строка в git)
- Ревью реально ускоряется
Мой дефолт: вертикально. Всегда. Но переключаюсь на горизонтальный, когда выражение реально простое: null-coalesce, базовый ternary, один метод в LINQ.
Правило, по которому решаю: если мне нужно провести глазами по строке горизонтально больше одного раза, значит надо делать вертикально.
Это не теория. Мы перелопатили 100+ .NET сервисов и сравнили время ревью PR до и после. В среднем ревью стало быстрее примерно на 30% после того, как ввели вертикальные стандарты форматирования.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19❤4
Lazy Loading по умолчанию выключен в Entity Framework (Core), так что тебе реально нужно целенаправленно включать и искать вот этот вариант N+1 ⬇️
👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🥴3
Делегаты без накладных расходов в .NET 10
JIT в .NET 10 в некоторых случаях может полностью убрать стоимость абстракции при использовании делегатов.
👉 @KodBlog
JIT в .NET 10 в некоторых случаях может полностью убрать стоимость абстракции при использовании делегатов.
Enumerable.Any может быть таким же эффективным, как вручную написанный цикл, который достает Span из ListPlease open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤1
Новый
И ты будешь им пользоваться. Звучит стремно, да?
Но тут другая история.
Официально продвигаемое предложение для C# 15 добавляет labeled
Это решает проблему, на которую все натыкаются в вложенных циклах.
И обычно мы решаем ее криво:
- Используем
- Заводим флаги, которые “протекают” состоянием и прячут намерение.
Оба варианта делают код хуже для чтения и поддержки.
А теперь представь так:
→ Ты задаешь имя циклу, из которого хочешь выйти.
→ Потом пишешь ровно то, что имеешь в виду.
“Выйти из внешнего цикла.”
“Продолжить внешний цикл.”
И да, в других языках это уже есть:
→ Java, Kotlin, Rust, Go, Swift.
C# наконец догоняет. Эту фичу просили годами.
Сейчас она уже официально поддержана (championed), а значит, очень вероятно, что она действительно доедет.
И когда доедет, ты будешь ее использовать.
Не потому что это обязательно. А потому что так просто лучше.
Лично мне идея нравится. А тебе как эта фича в C#?🚬
👉 @KodBlog
goto может появиться в C# 15И ты будешь им пользоваться. Звучит стремно, да?
Но тут другая история.
Официально продвигаемое предложение для C# 15 добавляет labeled
break и continue.Это решает проблему, на которую все натыкаются в вложенных циклах.
И обычно мы решаем ее криво:
- Используем
goto со странными метками где-то внизу цикла.- Заводим флаги, которые “протекают” состоянием и прячут намерение.
Оба варианта делают код хуже для чтения и поддержки.
А теперь представь так:
→ Ты задаешь имя циклу, из которого хочешь выйти.
→ Потом пишешь ровно то, что имеешь в виду.
“Выйти из внешнего цикла.”
“Продолжить внешний цикл.”
И да, в других языках это уже есть:
→ Java, Kotlin, Rust, Go, Swift.
C# наконец догоняет. Эту фичу просили годами.
Сейчас она уже официально поддержана (championed), а значит, очень вероятно, что она действительно доедет.
И когда доедет, ты будешь ее использовать.
Не потому что это обязательно. А потому что так просто лучше.
Лично мне идея нравится. А тебе как эта фича в C#?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍37❤7👎6
C# Primary Constructor пример:
👉 @KodBlog
// ДО .NET 8 (традиционный подход с конструктором)
public class UserService
{
private readonly HospitalDBContext _dbContext;
private readonly ILogger<UserService> _logger;
private readonly IEmailService _emailService;
public UserService(
HospitalDBContext dbContext,
ILogger<UserService> logger,
IEmailService emailService)
{
_dbContext = dbContext;
_logger = logger;
_emailService = emailService;
}
// Методы бизнес-логики здесь...
}
// ПОСЛЕ .NET 8 (с использованием первичного конструктора)
public class UserService(
HospitalDBContext _dbContext,
ILogger<UserService> _logger,
IEmailService _emailService)
{
// Методы бизнес-логики здесь...
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13
CQS и CQRS часто путают.
CQS = Command Query Separation
Это про дизайн классов: метод либо меняет состояние , либо возвращает значение
CQRS = Command Query Responsibility Segregation
CQRS логически разделяет потоки чтения и потоки записи данных. Хотя это не взаимоисключающие вещи, CQRS можно воспринимать как более строгую версию CQS.
👉 @KodBlog
CQS = Command Query Separation
Это про дизайн классов: метод либо меняет состояние , либо возвращает значение
CQRS = Command Query Responsibility Segregation
CQRS логически разделяет потоки чтения и потоки записи данных. Хотя это не взаимоисключающие вещи, CQRS можно воспринимать как более строгую версию CQS.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Это довольно приятное улучшение в Visual Studio.
Строки, состоящие только из пробелов, а также строки, где только скобки или знаки пунктуации, можно отображать меньшим шрифтом. Это немного возвращает вертикальное пространство и заодно снижает визуальный шум.
Эту настройку можно найти здесь:
Options -> Text Editor -> Advanced -> Compress Blank Lines
👉 @KodBlog
Строки, состоящие только из пробелов, а также строки, где только скобки или знаки пунктуации, можно отображать меньшим шрифтом. Это немного возвращает вертикальное пространство и заодно снижает визуальный шум.
Эту настройку можно найти здесь:
Options -> Text Editor -> Advanced -> Compress Blank Lines
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15❤2🥴2🤔1
Только что словил неприятный нюанс в
На Windows по умолчанию используется
Итог: для директорий никогда не используйте *.*, если только вы явно не ищете папки, в имени которых есть ..
Раньше я всегда использовал *.*, что, вероятно, на самом деле никогда не было корректным вариантом, но это работало и возвращало все папки при MatchType.Win32.
👉 @KodBlog
GetDirectories: когда ты явно передаешь EnumerationOptions, режим по умолчанию становится Simple.На Windows по умолчанию используется
Win32, когда запускаешь без этого, из-за чего старый синтаксис *.* начинает ломатьсяИтог: для директорий никогда не используйте *.*, если только вы явно не ищете папки, в имени которых есть ..
Раньше я всегда использовал *.*, что, вероятно, на самом деле никогда не было корректным вариантом, но это работало и возвращало все папки при MatchType.Win32.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Blazor Minimum Project Templates v11.0.100-preview.1 получили поддержку таргета net11.0.
Если хочется стартовать с максимально чистого Blazor-шаблона, это как раз тот случай: без JS, без CSS-библиотек, с упором на чистый C#.
Можно ставить и пробовать уже сейчас для .NET 11 preview.
https://github.com/jsakamoto/BlazorMinimumTemplates
👉 @KodBlog
Если хочется стартовать с максимально чистого Blazor-шаблона, это как раз тот случай: без JS, без CSS-библиотек, с упором на чистый C#.
Можно ставить и пробовать уже сейчас для .NET 11 preview.
https://github.com/jsakamoto/BlazorMinimumTemplates
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤1
Чувак сделал намеренно уязвимое dotnet приложение 👀
Многие советы по безопасности в .NET остаются слишком абстрактными, пока ты не увидишь баг прямо в коде.
Поэтому этот проект, где всё намеренно сделано неправильно. Это намеренно уязвимое .NET-приложение, в котором собрано более 50 распространённых реальных ошибок, которые могут проскочить в обычный бизнес-код.
Ссылка - https://github.com/AlexGoOn/the-most-vulnerable-dotnet-app
Некоторые из включённых вещей:
👉 @KodBlog
Многие советы по безопасности в .NET остаются слишком абстрактными, пока ты не увидишь баг прямо в коде.
Поэтому этот проект, где всё намеренно сделано неправильно. Это намеренно уязвимое .NET-приложение, в котором собрано более 50 распространённых реальных ошибок, которые могут проскочить в обычный бизнес-код.
Ссылка - https://github.com/AlexGoOn/the-most-vulnerable-dotnet-app
Некоторые из включённых вещей:
Инъекционные атаки (SQL, command, template, LDAP, XML, logs)
Cross-Site Scripting (stored, reflected, в атрибутах, в SVG)
Небезопасная загрузка файлов (path traversal, Zip Slip, произвольная запись файлов)
Проблемы с криптографией (hashing, ECB, предсказуемый random)
Сериализация (XXE, XML bomb, binary, YAML)
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🤔1
8 лет опыта научили меня, что кидать исключения дорого.
Поэтому я начал делать так:
Раньше я бросал исключения, когда:
- пользователь не найден
- переданы невалидные параметры
- результат метода
И всю логику обработки я выносил на вызывающую сторону, которая теперь должна знать, какое именно исключение прилетело и как на него реагировать.
Но реальность такая: исключения отлично подходят для ошибок, с которыми ты не знаешь, что делать.
К счастью, большинство ошибок можно обработать, и вот как:
Паттерн Result.
Вместо того чтобы кидать исключения, методы возвращают объект
Ещё стоит подумать о своей кастомной
В своих проектах я люблю иметь две версии: не-дженерик (для
Я предпочитаю свою реализацию, но если тебе это не заходит, можно взять готовые библиотеки, например FluentResults.
👉 @KodBlog
Поэтому я начал делать так:
Раньше я бросал исключения, когда:
- пользователь не найден
- переданы невалидные параметры
- результат метода
nullИ всю логику обработки я выносил на вызывающую сторону, которая теперь должна знать, какое именно исключение прилетело и как на него реагировать.
Но реальность такая: исключения отлично подходят для ошибок, с которыми ты не знаешь, что делать.
К счастью, большинство ошибок можно обработать, и вот как:
Паттерн Result.
Вместо того чтобы кидать исключения, методы возвращают объект
Result, который явно показывает успех/провал и содержит либо успешное значение, либо информацию об ошибке.Ещё стоит подумать о своей кастомной
Error-модели, где ты будешь нормально, описательно расписывать все типы ошибок и использовать её внутри Result.В своих проектах я люблю иметь две версии: не-дженерик (для
void-методов) и дженерик (обёртка для конкретного результата). Так намерение кода читается сразу и без гаданий.Я предпочитаю свою реализацию, но если тебе это не заходит, можно взять готовые библиотеки, например FluentResults.
Please open Telegram to view this post
VIEW IN TELEGRAM
🥴12❤9👍7🤔3
Оператор LINQ FullJoin() появится в dotnet 11?
https://github.com/dotnet/runtime/issues/124787
👉 @KodBlog
https://github.com/dotnet/runtime/issues/124787
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Когда стоит использовать паттерн Outbox?
Паттерн Outbox хранит исходящие сообщения в таблице базы данных, а потом фоновый процесс публикует их в брокер сообщений.
Зачем вообще заморачиваться?
Потому что в распределённых системах всё ломается: падают downstream-сервисы, отваливается сеть и т.д.
Если публиковать сообщения прямо внутри обработки запроса, есть риск их потерять, когда что-то пойдёт не так.
Outbox опирается на атомарные транзакции БД:
✅ Сохранить состояние приложения
✅ Сохранить сообщение в Outbox (либо оба шага, либо ни один)
А дальше фоновый процесс уже безопасно доставляет сообщение.
Важно помнить: Outbox решает надёжность публикации.
А на стороне консюмера всё равно нужна идемпотентность, чтобы нормально переживать ретраи.
Хочешь глубже?
Полная статья тут: https://milanjovanovic.tech/blog/implementing-the-outbox-pattern
👉 @KodBlog
Паттерн Outbox хранит исходящие сообщения в таблице базы данных, а потом фоновый процесс публикует их в брокер сообщений.
Зачем вообще заморачиваться?
Потому что в распределённых системах всё ломается: падают downstream-сервисы, отваливается сеть и т.д.
Если публиковать сообщения прямо внутри обработки запроса, есть риск их потерять, когда что-то пойдёт не так.
Outbox опирается на атомарные транзакции БД:
А дальше фоновый процесс уже безопасно доставляет сообщение.
Важно помнить: Outbox решает надёжность публикации.
А на стороне консюмера всё равно нужна идемпотентность, чтобы нормально переживать ретраи.
Хочешь глубже?
Полная статья тут: https://milanjovanovic.tech/blog/implementing-the-outbox-pattern
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤3
C# records как value objects?
Records по задумке иммутабельные, и у них структурное сравнение (равенство по значениям).
А именно это обычно и нужно от value object.
Плюс сверху получаем pattern matching, короткий синтаксис и deconstruction.
👉 @KodBlog
Records по задумке иммутабельные, и у них структурное сравнение (равенство по значениям).
А именно это обычно и нужно от value object.
Плюс сверху получаем pattern matching, короткий синтаксис и deconstruction.
public class Booking
{
public Address Address { get; init; }
public DateRange Period { get; init; }
}
public record Address(
string Street,
string City,
string State,
string Country,
string ZipCode);
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9😁1
Каждый раз, когда ты пишешь очередной новенький
ты на самом деле принимаешь решение по производительности.
Большинство разработчиков воспринимают структуры данных как контейнеры. А это не так. Это контракты по перформансу.
Вот как полезно думать о самых популярных структурах в .NET, по-практически:
Array
Бери, когда размер фиксирован и нужен честный O(1) доступ по индексу.
Самый быстрый вариант. Нет ресайза. Нет сюрпризов.
List<T>
Отличный дефолтный выбор, пока не упираешься в ресайз.
Под капотом при росте capacity копируется весь массив.
Dictionary<TKey, TValue>
В среднем O(1) на поиск.
Но плохое распределение хеша -> коллизии -> деградация скорости.
Stack<T>
Идеален для рекурсии, парсинга, undo/redo.
Но глубокая рекурсия? Привет, StackOverflow (сайт) или stack overflow (ошибка).
Queue<T>
FIFO. Отлично для фоновой обработки и пайплайнов.
Типа планирования задач, обработки сообщений.
Tree / Graph
Используются чаще, чем кажется:
роутинг, индексация, разрешение зависимостей, поиск.
Смысл не в том, чтобы заучивать Big-O.
Смысл понимать:
- как выделяется память
- как работает ресайз
- что происходит под нагрузкой
- на какой компромисс ты соглашаешься
Хорошие инженеры пишут код, который работает.
Классные инженеры знают, почему он работает и в какой момент перестанет.
👉 @KodBlog
List<>,ты на самом деле принимаешь решение по производительности.
Большинство разработчиков воспринимают структуры данных как контейнеры. А это не так. Это контракты по перформансу.
Вот как полезно думать о самых популярных структурах в .NET, по-практически:
Array
Бери, когда размер фиксирован и нужен честный O(1) доступ по индексу.
Самый быстрый вариант. Нет ресайза. Нет сюрпризов.
List<T>
Отличный дефолтный выбор, пока не упираешься в ресайз.
Под капотом при росте capacity копируется весь массив.
Dictionary<TKey, TValue>
В среднем O(1) на поиск.
Но плохое распределение хеша -> коллизии -> деградация скорости.
Stack<T>
Идеален для рекурсии, парсинга, undo/redo.
Но глубокая рекурсия? Привет, StackOverflow (сайт) или stack overflow (ошибка).
Queue<T>
FIFO. Отлично для фоновой обработки и пайплайнов.
Типа планирования задач, обработки сообщений.
Tree / Graph
Используются чаще, чем кажется:
роутинг, индексация, разрешение зависимостей, поиск.
Смысл не в том, чтобы заучивать Big-O.
Смысл понимать:
- как выделяется память
- как работает ресайз
- что происходит под нагрузкой
- на какой компромисс ты соглашаешься
Хорошие инженеры пишут код, который работает.
Классные инженеры знают, почему он работает и в какой момент перестанет.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤6