Когда работаешь с идентификаторами, хешами или логами, часто нужно быстро достать последние символы из строки.
Для получения последних 10 символов используется оператор диапазона:
var last10 = text[^10..];
Такой способ делает код короче и легче для чтения.
Но есть один момент — если строка короче десяти символов, программа выбросит ошибку.
Чтобы этого избежать, добавьте проверку:
var last10 = text.Length >= 10 ? text[^10..] : text;
Теперь код работает безопасно даже с короткими строками.
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14🥱3🌚2👾2
Дедлоки редко проявляются в ходе разработки, но часто всплывают под реальной нагрузкой в продакшне. Дедлок — ситуация, когда две или более сессии базы данных блокируют друг друга, ожидая освобождения ресурсов.
В итоге SQL Server прерывает одну из транзакций:
SqlException: Transaction (Process ID xx) was deadlocked on resources...
Пример простого дедлока: два контекста одновременно меняют одну строку и пытаются сохранить изменения. Один запрос блокирует строку, второй вызывает дедлок.
await using var context1 = new AppDbContext();
await using var context2 = new AppDbContext();
var order1 = await context1.Orders.FindAsync(1);
var order2 = await context2.Orders.FindAsync(1);
order1.Status = "Paid";
order2.Status = "Shipped";
await context1.SaveChangesAsync(); // Блокирует строку
await context2.SaveChangesAsync(); // Возможен deadlock
Стратегии предотвращения
• Короткие транзакции
Минимизируйте работу внутри транзакции:
await using var tx = await context.Database.BeginTransactionAsync();
var order = await context.Orders.FindAsync(orderId);
order.Status = "Paid";
await context.SaveChangesAsync();
await tx.CommitAsync();
Избегайте сетевых вызовов и посторонних запросов перед коммитом.
• Правильные индексы
Отсутствие индексов приводит к сканированию таблиц и длительным блокировкам. Используйте SQL Profiler для мониторинга.
• Оптимальный уровень изоляции
ReadCommitted обычно достаточен для большинства операций:
await using var tx = await context.Database.BeginTransactionAsync(
IsolationLevel.ReadCommitted);
• Логика повторов
Используйте встроенную политику retry в EF Core:
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString, sql =>
sql.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null)));
• RowVersion для контроля конкурентности
public class Order
{
public int Id { get; set; }
public string Status { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
EF Core выбросит
DbUpdateConcurrencyException при конфликте изменений.Дедлоки не баги, а следствие паттернов конкурентного доступа.
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍6
Даже самая оптимизированная система не застрахована от deadlock на 100%. При высоких нагрузках, пиковых моментах или редких edge-case сценариях они всё равно могут возникнуть. Важно не предотвращение на 100% (это невозможно), а грамотное восстановление.
• Умный ретрай с экспоненциальной задержкой
Простой повтор без задержки только усугубит ситуацию. Используйте экспоненциальную задержку:
public async Task<bool> SaveWithRetryAsync(DbContext context, int maxRetries = 3)
{
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
await context.SaveChangesAsync();
return true;
}
catch (DbUpdateException ex) when (IsDeadlock(ex))
{
if (attempt == maxRetries - 1)
throw;
// Экспоненциальная задержка: 100ms, 200ms, 400ms
var delay = TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt));
await Task.Delay(delay);
Console.WriteLine($"Deadlock detected, retry {attempt + 1}/{maxRetries}");
}
}
return false;
}
bool IsDeadlock(Exception ex) =>
ex.InnerException is SqlException sqlEx &&
(sqlEx.Number == 1205 || // Deadlock victim
ex.InnerException.Message.Contains("deadlocked"));
• Ограничение параллелизма
Если ваше приложение обрабатывает большие пакеты данных, ограничьте количество одновременных операций:
public class OrderProcessor
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5); // Макс 5 одновременно
public async Task ProcessOrdersAsync(List<int> orderIds)
{
var tasks = orderIds.Select(id => ProcessWithSemaphoreAsync(id));
await Task.WhenAll(tasks);
}
private async Task ProcessWithSemaphoreAsync(int orderId)
{
await _semaphore.WaitAsync();
try
{
await using var context = new AppDbContext();
var order = await context.Orders.FindAsync(orderId);
order.Status = "Processed";
await SaveWithRetryAsync(context);
}
finally
{
_semaphore.Release();
}
}
}
• Логирование и анализ
Недостаточно просто повторить — нужно понять причину:
catch (DbUpdateException ex) when (IsDeadlock(ex))
{
_logger.LogWarning(ex,
"Deadlock on order {OrderId}, attempt {Attempt}/{MaxAttempts}",
orderId, attempt + 1, maxRetries);
// Отправьте метрику в мониторинг
_telemetry.TrackMetric("Deadlocks", 1, new Dictionary<string, string>
{
["Entity"] = "Order",
["Operation"] = "Update"
});
}
• Захват графов блокировок
SQL Server создаёт подробные графы взаимоблокировок. Настройте их захват:
-- Extended Events для захвата deadlock
CREATE EVENT SESSION [DeadlockMonitoring] ON SERVER
ADD EVENT sqlserver.xml_deadlock_report
ADD TARGET package0.event_file(SET filename = N'C:\Logs\Deadlocks.xel')
WITH (MAX_MEMORY = 4096 KB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS);
ALTER EVENT SESSION [DeadlockMonitoring] ON SERVER STATE = START;
Сочетание правильного предотвращения и надежного восстановления создаст устойчивую систему, способную справиться с любыми нагрузками.
Чтобы строить надёжные решения нужно знать архитектуру и уметь её спроектировать. Для этого можно пройти наш интенсив по архитектуре и шаблонам проектирования. Осталось всего 3 дня скидок!
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤17👍5🔥4
Когда файл огромный, главное — не тянуть всё в память.
Пример:
var errors = File.ReadLines(logPath).Where(l => l.Contains("ERROR"));ReadLines читает файл построчно, не загружая всё сразу.Не добавляйте
.ToList(), если хочется сохранить ленивость — иначе всё материализуется в памяти.#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤1
Каждый разработчик сталкивался с необходимостью выполнять долгие операции в фоне: отправка писем, обработка платежей, генерация отчётов, синхронизация данных.
Обычно первая мысль — взять Hangfire, Quartz или Azure Functions. Но .NET 9 предоставляет встроенные примитивы, которые отлично справляются с этой задачей самостоятельно.
Традиционные подходы — блокирующая коллекция и самописные очереди имеют недостатки: требуют ручной синхронизации, рискуют блокировкой потоков, не имеют встроенной поддержки обратного давления.
Реализуем очередь задач с помощью каналов и фоновые сервисы
Интерфейс и реализация очереди:
public interface IBackgroundTaskQueue
{
ValueTask QueueAsync(Func<CancellationToken, Task> workItem);
ValueTask<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue;
public BackgroundTaskQueue(int capacity = 100)
{
var options = new BoundedChannelOptions(capacity)
{
SingleReader = false,
SingleWriter = false,
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, Task>>(options);
}
public async ValueTask QueueAsync(Func<CancellationToken, Task> workItem)
=> await _queue.Writer.WriteAsync(workItem);
public async ValueTask<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
=> await _queue.Reader.ReadAsync(cancellationToken);
}
Фоновые сервисы для обработки:
public class BackgroundWorker : BackgroundService
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger<BackgroundWorker> _logger;
public BackgroundWorker(IBackgroundTaskQueue taskQueue, ILogger<BackgroundWorker> logger)
{
_taskQueue = taskQueue;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Фоновый worker запущен");
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await _taskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка при выполнении задачи");
}
}
_logger.LogInformation("Worker останавливается...");
}
}
Использование в контроллере:
app.MapPost("/send-email", async (IBackgroundTaskQueue queue) =>
{
await queue.QueueAsync(async token =>
{
await Task.Delay(1000, token); // имитация отправки
Console.WriteLine($"Email отправлен в {DateTime.UtcNow}");
});
return Results.Accepted();
});Запрос возвращается немедленно, а работа продолжается в фоне. Queue автоматически буферизирует задачи, управляет нагрузкой и применяет обратное давление, если очередь переполнится.
Когда использовать этот подход
Подходит:
• Внутренняя обработка задач в приложении
• Нет нужды в UI-панели мониторинга
• Self-contained сервисы
Возьмите библиотеку, если:
• Нужна распределённая обработка
• Требуется веб-интерфейс для управления
• Задачи должны пережить перезагрузку приложения
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
Если вы работаете с авторизацией в .NET и используете Identity, но всё ещё не совсем понимаете, как правильно организовать систему разрешений — есть видео, которое стоит посмотреть.
В нём конкретный пример: как взять стандартный ASP.NET Core Identity и использовать его для управления доступом. Ничего экзотического — обычная setup с JWT и кастомным authorization handler.
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13
Бывает, нужно быстро собрать, запустить или опубликовать проект без запуска тяжелой IDE, особенно в пайплайне или на сервере разработчика. Для этого хватает нескольких команд из .NET CLI, которые работают одинаково на всех платформах.
Проверить установку и окружение поможет команда
dotnet --info, она покажет версии SDK и рантаймов и архитектуру хоста.Сборка проекта выполняется командой
dotnet build, по умолчанию в конфигурации Debug и с выводом артефактов в bin, причем команда учитывает инкрементальные изменения для скорости.Запуск приложения через
dotnet run объединяет сборку и старт процесса, что эквивалентно кнопке Run в IDE и удобно для ручных проверок локально.Горячая перезагрузка из терминала через
dotnet watch отслеживает изменения файлов и повторно запускает приложение с Hot Reload без ручного рестарта, что ускоряет цикл правка и проверка.Если сборка ведет себя странно, стоит выполнить
dotnet clean, чтобы удалить выходные артефакты и заставить следующий build собрать все заново.Для развертывания используйте
dotnet publish, который соберет релиз и положит готовый к деплою набор файлов в папку publish, включая веб приложения и сервисы.#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍1
Представьте, что вы пришли в магазин и продавец говорит: «Вот вам что-то. Не знаю что. Может быть, помидоры, может быть, кирпич, может быть, счастье. Узнаете, когда вернётесь домой». Вот такое же ощущение от
IActionResult без типов.Проблема с
IActionResult:[HttpGet("{id:guid}")]
public async Task<IActionResult> GetById(Guid id, ISender mediator, CancellationToken ct)
{
var dto = await mediator.Send(new GetUserQuery(id), ct);
return dto is null ? NotFound() : Ok(dto);
}Возвращаемый тип —
IActionResult. Просто интерфейс. Ничего конкретного. Кто читает этот код, не знает, что именно вернётся. Может быть UserDto, может быть ошибка, может быть что угодно.Вот так выглядит сгенерированная Swagger документация:
{
"responses": {
"200": { "description": "Success" }
}
}Никакой информации о схеме. Кто использует ваш API, не знает, какие поля будут в ответе. IDE не может подсказать структуру. Тесты пишутся вслепую.
Решение:
ActionResult<T>[HttpGet("{id:guid}")]
public async Task<ActionResult<UserDto>> GetById(Guid id, ISender mediator, CancellationToken ct)
{
var dto = await mediator.Send(new GetUserQuery(id), ct);
return dto is null ? NotFound() : Ok(dto);
}Возвращаемый тип —
ActionResult<UserDto>. Конкретно и ясно. Читающий код понимает мгновенно: метод возвращает UserDto при успехе или ошибку. Swagger генератор видит типы и строит правильную документацию.IActionResult можно использовать, когда метод не возвращает тело, к примеру ответ 204.#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍3
Откройте логи ошибки из продакшена. Видите стек-трейс? Он полон
<>c__DisplayClass2_0, d__5``1.MoveNext() и прочих артефактов, которые сгенерировал компилятор. Это не ошибка — это просто то, как .NET преобразует современный C# в IL. Но это делает поиск проблемы медленнее, чем нужно.Demystifier восстанавливает оригинальный вид кода:
• Вместо
ValueTuple``2 param показывает (string val, bool) param• Вместо
Func``1 показывает Func<string>•
async методы помечает как async Task<string>• Локальные функции и лямбды показывает с контекстом
Как применить
Вызовите
exception.Demystify() и передайте результат:try { /* ваш код */ }
catch (Exception ex)
{
logger.LogError(ex.Demystify(), "Ошибка");
}Анализ стека стоит ресурсов. На высоконагруженной системе вызывать Demystify для каждого исключения неэкономно. Используйте его выборочно — для критических путей, для отладки в development окружении или для редких, но важных ошибок.
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1
Нужно посчитать, сколько раз каждое значение встречается в данных. Логично напрашивается проверка:
if (counts.ContainsKey(key))
counts[key]++;
else
counts[key] = 1;
Но есть способ проще:
counts[key] = counts.GetValueOrDefault(key) + 1;
Метод
GetValueOrDefault возвращает значение, если ключ есть, или значение по умолчанию (для int это 0). Затем добавляем единицу. Никаких условных операторов, одна строка.Если можете — используйте LINQ:
var counts = items.GroupBy(x => x.Key)
.ToDictionary(g => g.Key, g => g.Count());
Это явно показывает намерение: группируем по ключу, считаем количество в каждой группе.
Но если вы наполняете словарь в цикле или обрабатываете поток данных, GetValueOrDefault — идеальный выбор.
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Представьте: нужно заполнить макет контентом, чтобы дизайнер видел, как всё выглядит. Берёте Lorem ipsum. Но он выглядит как набор слов. А если нужен текст, который с первого взгляда кажется серьезным, но при внимательном чтении — полная чушь? Вот для этого WaffleGenerator.
Это библиотека, которая генерирует абзацы в стиле научно-популярного бреда. Текст выглядит настоящим: правильная структура, логичные переходы, официальный тон. Но если вы прочитаете внимательно, поймёте, что там просто красивые слова без смысла.
Как применить
Просто:
var text = WaffleEngine.Html(paragraphs: 2, includeHeading: true);
var text = WaffleEngine.Text(paragraphs: 1, includeHeading: true);
var markdown = WaffleEngine.Markdown(paragraphs: 1, includeHeading: true);
Поддерживает три формата: HTML, обычный текст и Markdown.
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤4