.NET Разработчик – Telegram
.NET Разработчик
6.66K subscribers
458 photos
4 videos
14 files
2.17K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2508. #ЗаметкиНаПолях
DbContext не Является Потокобезопасным. Распараллеливаем Запросы EF Core Правильно. Начало
Все мы создавали конечные точки вроде «Панель управления для мониторинга» или «Сводка по пользователю». Это конечная точка, которой необходимо получить несколько совершенно не связанных между собой наборов данных, чтобы составить полную картину для пользователя. Например, последние 50 заказов, текущие логи состояния системы, настройки профиля пользователя и, возможно, количество уведомлений.

Итак, вы пишете код стандартным способом:
var orders = await GetRecentOrdersAsync(id);
var logs = await GetSystemLogsAsync();
var stats = await GetUserStatsAsync(id);

return new DashboardDto(orders, logs, stats);

Это чисто, читаемо, работает. Но есть проблема. Если GetRecentOrdersAsync занимает 300мс, GetSystemLogsAsync — 400мс, а GetUserStatsAsync — 300мс, данные будут загружаться целую секунду (300+400+300).

В распределённой системе задержка ухудшает пользовательский опыт. Поскольку эти наборы данных не связаны между собой, мы должны иметь возможность выполнять их параллельно. Тогда общее время составило бы только длительность самого медленного запроса (400 мс). Это 60% улучшение производительности просто за счёт изменения способа выполнения кода. Но наивный подход с EF Core не сработает.

Проблема с Task.WhenAll
Самая распространённая ошибка – обернуть эти вызовы репозитория в задачи и ожидать их выполнения одновременно:
//  НЕ ДЕЛАЙТЕ ТАК
public async Task<DashboardData>
GetDashboardData(int id)
{
// Методы используют один _dbContext
var orders = _repo.GetOrdersAsync(id);
var logs = _repo.GetLogsAsync();
var stats = _repo.GetStatsAsync(id);

await Task.WhenAll(orders, logs, stats);

return new DashboardData(
orders.Result,
logs.Result,
stats.Result);
}

Если вы выполните это, вы столкнётесь с таким исключением: «A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.» («Вторая операция в этом контексте началась до завершения предыдущей. Обычно это происходит из-за того, что разные потоки используют один и тот же экземпляр DbContext, однако потокобезопасность членов экземпляра не гарантируется.»)

Почему это происходит?
DbContext в EF Core не является потокобезопасным. Это объект с состоянием, предназначенный для управления одной единицей работы. Он поддерживает «отслеживание изменений» для контроля за загруженными сущностями и инкапсулирует единственное базовое соединение с БД.

Протоколы баз данных (например, TCP-поток для PostgreSQL или SQL Server) обычно синхронны на уровне соединения. Вы не можете отправить два разных SQL-запроса по одному и тому же каналу в одну и ту же миллисекунду. При использовании Task.WhenAll несколько потоков пытаются одновременно захватить это единственное соединение, и EF Core вмешивается, чтобы сгенерировать исключение и предотвратить повреждение данных.

Таким образом, у нас возникает дилемма: нам нужна скорость параллелизма, но DbContext вынуждает нас использовать последовательное выполнение.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way
👍30
День 2509. #ЗаметкиНаПолях
DbContext не Является Потокобезопасным. Распараллеливаем Запросы EF Core Правильно. Окончание

Начало

Решение
Начиная с .NET 5, EF Core предоставляет решение для этого сценария: IDbContextFactory<T>. Вместо внедрения экземпляра контекста с ограниченной областью видимости (который существует на протяжении всего HTTP-запроса), вы внедряете фабрику, которая позволяет создавать легковесные, независимые экземпляры DbContext там, где это нужно.

Примечание: хотя использование фабрики является наиболее чистым подходом для внедрения зависимостей, вы также можете вручную создать экземпляр контекста (используя var context = new AppDbContext(options)), если у вас есть доступ к DbContextOptions.

Сначала нужно зарегистрировать фабрику в Program.cs:
// Это регистрирует фабрику как Singleton
// Также регистрирует AppDbContext как Scoped
builder.Services.AddDbContextFactory<AppDbContext>(
opts =>
{
opts.UseNpgsql(
builder.Configuration.GetConnectionString("db"));
});

Теперь изменим конечную точку, используя фабрику контекстов. Внутри нашего метода мы запускаем отдельную задачу для каждого запроса. Внутри каждой задачи создаём отдельный контекст, выполняем запрос, а затем немедленно освобождаем его:
using Microsoft.EntityFrameworkCore;

public class DashboardService(
IDbContextFactory<AppDbContext> ctxFactory)
{
public async Task<DashboardDto>
GetDashboardAsync(int id)
{
var orders = GetOrdersAsync(id);
var logs = GetSystemLogsAsync();
var stats = GetUserStatsAsync(id);

await Task.WhenAll(orders, logs, stats);

return new DashboardDto(
await orders,
await logs,
await stats
);
}

private async Task<List<Order>>
GetOrdersAsync(int id)
{
// Создаём контекст для операции
await using var ctx =
await ctxFactory.CreateDbContextAsync();

return await ctx.Orders
.AsNoTracking()
.Where(o => o.UserId == id)
.ToListAsync();
}
// остальные методы аналогичны
}


Ключевые особенности
1. Изоляция
Каждая задача получает свой собственный DbContext. Т.е. у каждой задачи есть собственное соединение с БД. Конфликтов нет.
2. Освобождение ресурсов
Использование await критически важно. Как только запрос завершится, мы хотим освободить этот контекст и вернуть соединение в пул.

При последовательном выполнении каждая операция ожидает завершения предыдущей. Использование параллельного подхода позволяет ускорить время ответа. Все три этапа обработки данных в БД начинаются и завершаются параллельно.

Компромиссы и заключение
IDbContextFactory устраняет разрыв между архитектурой EF Core, основанной на единице работы, и реальностью современных требований к параллельной обработке. Он позволяет выйти за рамки принципа «один запрос — один поток» без ущерба для безопасности.

Однако используйте этот шаблон с осторожностью:
1. Истощение пула соединений
Один HTTP-запрос теперь одновременно занимает 3 соединения с БД вместо 1. При высокой степени параллелизма вы можете легко исчерпать пул соединений.
2. Накладные расходы на контекст
Если ваши запросы выполняются очень быстро (например, простой поиск по ID), накладные расходы на создание нескольких контекстов и задач могут сделать параллельную версию медленнее, чем последовательную.

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

Источник: https://www.milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way
👍22
День 2510. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

12. Кроссплатформенные возможности
«Расскажите о кроссплатформенных возможностях современной платформы .NET. Объясните, как эти возможности приносят пользу разработке ПО, и приведите пример сценария, в котором кроссплатформенная поддержка имеет решающее значение».

Хороший ответ
«.NET Core и более поздние версии .NET — это значительный шаг вперёд в экосистеме .NET, прежде всего благодаря своим кроссплатформенным возможностям, которые позволяют разработчикам создавать и запускать приложения на Windows, Linux и macOS.

Преимущества кроссплатформенных возможностей современного .NET включают:
- Более широкий охват: Разработчики могут ориентироваться на несколько платформ с помощью одной кодовой базы, что упрощает охват более широкой аудитории или пользовательской базы без переписывания приложения для каждой платформы.

- Экономическую эффективность: Поддержка одной кодовой базы для нескольких платформ более экономична по сравнению с поддержкой нескольких кодовых баз. Это снижает затраты на разработку и обслуживание.

- Упрощение разработки: Инструменты и библиотеки современного .NET поддерживают среды разработки на всех платформах, что упрощает процесс разработки. Разработчики могут использовать свои предпочтительные инструменты и среды независимо от целевой платформы развертывания.

- Сообщество и поддержку: Современный .NET — платформа с открытым исходным кодом, которая выигрывает от вклада сообщества, расширяющего её возможности и повышающего надёжность. Это включает в себя улучшенную производительность, безопасность и функции, которые регулярно обновляются и расширяются.

Например, компания может разрабатывать пакет офисных приложений, который должен быть доступен на ПК, Mac и серверах под управлением Linux. Используя .NET и Avalonia, его можно создать 1 раз и развернуть на всех этих платформах с минимальными изменениями. Это не только ускоряет процесс разработки, но и гарантирует, что все пользователи имеют одинаковую функциональность и пользовательский опыт независимо от их операционной системы. Хотя, стоит отметить, что пока не все платформы для UI-разработки в .NET полностью кроссплатформенны. Например, .NET MAUI пока не поддерживает Linux.

По сути, кроссплатформенные возможности .NET обеспечивают единый подход к разработке приложений, что особенно полезно в современном разнообразном технологическом ландшафте.»

Часто встречающийся неверный ответ
«NET работает одинаково на всех платформах, поэтому не нужно делать ничего особенного, чтобы ваше приложение работало на Windows, Linux или macOS».

Этот ответ упрощает кроссплатформенную функциональность .NET и упускает из виду несколько ключевых моментов:
- Игнорирование особенностей платформ: Хотя .NET поддерживает множество платформ, разработчикам часто необходимо учитывать различия, специфичные для каждой платформы, такие как пути к файлам, API операционных систем или рекомендации по пользовательскому интерфейсу, что может потребовать условной компиляции или специальных настроек.

- Чрезмерное упрощение процесса разработки: Утверждение о том, что ничего особенного делать не нужно, не учитывает необходимость тестирования и потенциальных корректировок для различных сред, чтобы обеспечить согласованное поведение и производительность на разных платформах.

- Недооценка проблем конфигурации и развёртывания: Процесс развёртывания может значительно различаться между операционными системами, и такие вопросы, как управление зависимостями или конфигурация среды, могут влиять на то, как приложение настраивается на каждой платформе.

Эта ошибка обычно возникает из-за недостатка опыта развёртывания приложений в различных средах или непонимания сложностей, связанных с настоящей кроссплатформенной разработкой.

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍6
День 2511. #ЗаметкиНаПолях
Работа с Git в Visual Studio 2026. Начало

Вы — .NET разработчик, у вас и так напряжённое утро, а тут ещё прилетает задача в трекере: «Конечная точка авторизации выдает ошибку 500 при высокой нагрузке». Вам нужно исправить ошибку, проверить код в отдельной ветке у коллеги, при этом поддерживая чистоту репозитория. Инструменты Git в Visual Studio превращают эту повседневную рутину с Git — создание тематических веток, сохранение изменений, коммиты и обработка пул-реквестов — в плавный и быстрый процесс.

Создание тематической ветки для исправления ошибки
Ваш репозиторий открыт в VS: View -> Git Repository (Вид -> Репозиторий Git), и вы находитесь в ветке main. Чтобы изолировать исправление ошибки, щёлкните правой кнопкой мыши по ветке main в окне репозитория Git, выберите New Local Branch From (Создать локальную ветку из) и назовите её fix/auth-race-1, связав её с задачей в трекере. Вы мгновенно переключитесь на новую ветку, и строка состояния Git в Visual Studio 2026 покажет вашу ветку и статус синхронизации, так что вы никогда не потеряетесь. См. картинку 1.

Вы обновляете AuthService.cs исправлением состояния гонки… И тут прилетает сообщение от ПМ: «нужно срочно проверить код в ветке фичи для презентации». Время отложить вашу работу без риска её потерять.

Сохранение изменений для переключения между задачами
Вы находитесь в процессе исправления ошибки, у вас есть несохраненные изменения, и вы еще не готовы к коммиту. В окне Git Changes - View -> Git Changes (Вид -> Изменения Git) - выберите изменённые файлы, нажмите Stash All (Сохранить все) и добавьте примечание: «WIP: Auth fix before demo.» («В Процессе: исправление авторизации перед демо фичи»). Visual Studio сохранит все изменения, оставив вашу рабочую директорию чистой. См. картинку 2.

Переключение между ветками в Visual Studio теперь происходит намного быстрее благодаря оптимизированной загрузке решения и индексации. Вы можете доработать новую функцию и отправить её в удалённый репозиторий, в то время как ваше исправление ошибки остается безопасно сохранённым.

После завершения проверки функции вы готовы вернуться к исправлению ошибки и доработать его перед коммитом.

Восстановление изменений и проверка кода
Переключитесь обратно на ветку fix/auth-race-1. Быстрое переключение веток в Visual Studio позволяет вам продолжить работу без задержек. Чтобы восстановить свою работу, перейдите в окно Git Changes, откройте вкладку Stash (Сохранённые изменения), щелкните правой кнопкой мыши и выберите Pop Stash (Восстановить изменения). Ваши изменения будут восстановлены.

Перед коммитом хорошо бы проверить код и выявить любые недочеты. В окне Git Changes нажмите кнопку Copilot Code Review (Проверка кода с помощью Copilot). Copilot просканирует ваши изменения и оставит комментарии в редакторе. Предложения можно применить одним щелчком мыши. См. картинку 3.

Для большей уверенности откройте Copilot Chat - View -> Copilot Chat (Вид -> Чат Copilot), введите «#changes Check security» («#changes Проверить безопасность») и получите советы по безопасности, например: «Add rate limiting for brute-force protection.» («Добавьте ограничение запросов для защиты от перебора паролей»). Так вы выявляете больше проблем на ранних этапах, что делает ваш будущий пул-реквест более чистым.

После доработки кода вы готовы зафиксировать изменения.

Окончание следует…

Источник:
https://devblogs.microsoft.com/visualstudio/streamlining-your-git-workflow-with-visual-studio-2026/
👍12
День 2512. #ЗаметкиНаПолях
Работа с Git в Visual Studio 2026. Окончание
Начало

Коммит
Код готов, поэтому пришло время сделать коммит. В окне Git Changes нажмите кнопку Generate Commit Message (Сгенерировать сообщение коммита). Оно отформатируется в соответствии со стандартами вашей команды (подробнее об этом тут) и сэкономит вам несколько драгоценных минут. Можете отредактировать сообщение или принять его и отправить изменения с помощью кнопки Commit. См. картинку 1.

Создание пул-реквеста и просмотр изменений прямо в IDE
Ваша работа готова к проверке! После отправки изменений вы увидите сообщение в окне Git Changes, и можете щелкнуть по ссылке, чтобы создать пул-реквест. Либо вы можете перейти в окно Git Repository, щёлкнуть правой кнопкой мыши по ветке fix/auth-race-1 и выбрать Create Pull Request (Создать пул-реквест). В пользовательском интерфейсе создания пул-реквеста выберите коллег в качестве рецензентов, щелкнув в поле в блоке Reviewers (Рецензенты) и выбрав их имена из выпадающего списка. Поскольку есть только один коммит, VS автоматически подтягивает сообщение коммита Copilot в описание пул-реквеста. Вы также можете сгенерировать описание пул-реквеста, чтобы убедиться, что оно соответствует стандартам вашей команды. Нажмите Create (Создать), и запрос будет отправлен на проверку. См. картинку 2.

Рецензенты могут легко просмотреть пул-реквест прямо в Visual Studio, найдя его в окне Git Repository. См. картинку 3.

Рецензенты могут открыть пул-реквест и добавлять комментарии непосредственно в Visual Studio. Markdown-разметка отображается прямо в окне сравнения изменений, в виде цепочки комментариев, что делает их понятными. См. картинку 4.

Вы отвечаете на комментарии, вносите обновления и отправляете новые изменения. Visual Studio мгновенно синхронизирует изменения. Процесс проверки становится менее трудоёмким, рецензенты одобряют изменения. Вы сливаете ветку с основной и удаляете её, чтобы поддерживать репозиторий в чистоте.

Итого
Инструменты Git в Visual Studio — быстрое переключение веток, визуальное сравнение изменений, коммиты Copilot, проверка кода с помощью ИИ, комментарии прямо в коде — экономят ваше время в повседневной работе. Работа с ветками, сохранение изменений и пул-реквесты стали проще, чем когда-либо, позволяя вам сосредоточиться на коде. Проверка кода с помощью ИИ перед отправкой изменений позволяет выявлять ошибки на ранних этапах.

Источник: https://devblogs.microsoft.com/visualstudio/streamlining-your-git-workflow-with-visual-studio-2026/
1👍7
День 2513. #ЗаметкиНаПолях
Не Используйте JSON-Сериализатор Напрямую в Коде. Начало

Вероятно, в большинстве приложений вам придётся работать с JSON. Запросы/ответы или настройки приложения — вам всё равно придётся обрабатывать JSON.

И System.Text.Json, и Newtonsoft.Json предоставляют статические классы для сериализации и десериализации, поэтому очень заманчиво использовать их как есть:
using System.Text.Json; 

Person person = new();
var json = JsonSerializer.Serialize(person);

Однако использование этих статических классов приводит к ряду проблем, таких как:
- Жёсткая связь с реализацией. Если позже вы захотите перейти с System.Text.Json на Newtonsoft.Json или настроить поведение, это может привести к значительному рефакторингу;
- Отсутствие внедрения зависимостей. Гораздо удобнее, когда класс явно объявляет свои зависимости через конструктор. Со статическими сериализаторами это неочевидно или даже невозможно;
- Сложность модульного тестирования. Если класс имеет жёсткую зависимость от статического сериализатора, его сложно изолировать и использовать в качестве заглушки во время тестирования.

Поэтому существуют некоторые подходы, помогающие избежать этих проблем. Один из них — создать абстракцию над сериализатором и использовать её единообразно во всей системе:
interface ISerializer
{
Task<string> SerializeAsync(object obj, Type type);
Task<object> DeserializeAsync(string json, Type type);
}


Для более удобного использования можно создать обобщённый метод расширения:
static class SerializerExtensions
{
extension(ISerializer serializer)
{
public Task<string>
SerializeAsync<TValue>(TValue value)
{
return serializer
.SerializeAsync(value, typeof(TValue));
}

public async Task<TValue>
DeserializeAsync(string json)
{
return (TValue)await serializer
.DeserializeAsync(
json,
typeof(TValue));
}
}
}

Теперь регистрируем сериализатор в DI-контейнере и используем его там, где нам нужно, как в этом примере:
class Handler(ISerializer serializer)
{
public Task<Result> InvokeAsync(Command command)
{
return serializer.DeserializeAsync(command.Json);
}
}


Окончание следует…

Источник:
https://medium.com/@denmaklucky/dont-use-a-json-serializer-directly-in-your-code-instead-do-this-72f8eff3fdc5
1👎17👍12