C# Portal | Программирование
15.1K subscribers
929 photos
111 videos
24 files
771 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Связь: @devmangx

РКН: https://clck.ru/3FocB6
Download Telegram
На Хабре вышла статья о том, как с помощью EF Core и PostgreSQL избежать deadlock и oversell при работе со стоками. Автор разбирает подход с сортировкой обновлений по ключам, использованием ExecuteUpdateAsync, повторными попытками (retry), триггерами, очередями и батчами.

В статье есть бенчмарки, примеры кода и ссылка на репозиторий на GitHub.

Читать подробнее: habr.com/ru/articles/955714/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72
В превью C# 14 появились partial-конструкторы и partial-события.

Каждый из них должен состоять ровно из одного объявления-определения и одного объявления-реализации.

Реализация partial-события обязана содержать add и remove аксессоры.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👎8👍4🤔2
На GitHub появился классный репозиторий с чётко структурированным материалом по system design. В нём собраны:

- основы проектирования крупных систем,
- типичные вопросы и решения для собеседований,
- карточки Anki для запоминания ключевых концепций.

Сохрани, чтобы не потерять: https://github.com/donnemartin/system-design-primer

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83
Ты правильно называешь свои DTO?

Какую схему именования выбрать

Когда ты делаешь Web API, твои эндпоинты принимают и отдают данные.

Частая практика — добавлять к таким моделям суффикс Dto

Но вот в чём проблема:

DTO часто смешивают входные и выходные данные в одном классе.
Со временем такие классы разрастаются и становятся непонятными.

Мой вариант: использовать суффиксы Request и Response вместо Dto:

• CreateUserRequest —> для входных данных
• UserResponse —> для выходных

Почему так лучше:

Чёткое назначение —> сразу видно, модель для входа или для выхода.
Масштабируемость —> изменения в Response не ломают Request.
Поддерживаемость —> не надо гадать, что вообще делает UserDto.

Совет: выбери один подход и используй его последовательно во всём проекте.

А ты как называешь свои модели Request/Response или Dto? Делись опытом

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍8🤣3🔥2
Деревья выражений в C# помогают создавать быстрые и эффективные методы преобразования данных, ускоряя работу с объектами и запросами в Entity Framework Core. В статье раскрываются механизмы мэппинга и построения сложных фильтров через IQueryable.

Читать подробнее: https://habr.com/ru/articles/932110/

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43
Экономим память как профи: 5 продвинутых техник

Если вы работаете с .NET, то знаете, под капотом куча мощных инструментов, про которые редко говорят. Вот 5 API, которые реально экономят память и ускоряют код.

1. CollectionsMarshal.AsSpan() — прямой доступ к списку

Обычно List<T> это удобная, но безопасная обёртка над массивом.
Если нужен Span без копирования:

using System.Runtime.InteropServices;

var numbers = new List<int> { 1, 2, 3, 4, 5 };
Span<int> span = CollectionsMarshal.AsSpan(numbers);


Работает напрямую с внутренним массивом списка, без ToArray().
Важно: при изменении размера списка Span станет недействительным.
Когда использовать: большие буферы, короткие циклы.
Выигрыш: до 2 раз меньше памяти.

2. CollectionsMarshal.GetValueRefOrNullRef() — доступ к словарю без двойного поиска

Обычный способ вызывает поиск ключа дважды:

if (dict.TryGetValue("foo", out var value)) {
value++;
dict["foo"] = value;
}


Лучше так:

using System.Runtime.InteropServices;

ref int valueRef = ref CollectionsMarshal.GetValueRefOrNullRef(dict, "foo");
if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;


Получаем ссылку прямо на значение без повторного поиска.
Когда использовать: большие словари, горячие циклы.
Выигрыш: примерно в 2 раза быстрее.

3. GC.AllocateUninitializedArray<T>() — массив без обнуления

.NET по умолчанию обнуляет массив:

int[] arr = new int[1000];


Если вы всё равно перезаписываете элементы - то это лишняя работа.
Быстрее:

int[] arr = GC.AllocateUninitializedArray<int>(1000);
for (int i = 0; i < arr.Length; i++) arr[i] = i;


Когда использовать: если массив заполняется сразу.
Выигрыш: ~15% быстрее на больших массивах.

4. ArrayPool<T>.Shared и IMemoryOwner — аренда массивов

Постоянные аллокации убивают GC:

for (int i = 0; i < 1000; i++) {
var buffer = new byte[1024];
DoSomething(buffer);
}


Используем пул:

using System.Buffers;

for (int i = 0; i < 1000; i++) {
using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(1024);
DoSomething(owner.Memory.Span);
}


Когда использовать: сетевые серверы, конвейеры, потоковые операции.
Выигрыш: до 1000 раз меньше аллокаций.

5. ObjectPool<T> — переиспользование дорогих объектов

Создание тяжёлых объектов вроде StringBuilder дорого:

for (int i = 0; i < 100; i++) {
var sb = new StringBuilder(1024);
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
}


Пулим:

using Microsoft.Extensions.ObjectPool;

var provider = new DefaultObjectPoolProvider();
var pool = provider.CreateStringBuilderPool();

for (int i = 0; i < 100; i++) {
var sb = pool.Get();
sb.Clear();
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
pool.Return(sb);
}


Когда использовать: логирование, сериализация, временные буферы.
Выигрыш: до 4 раз быстрее, в 50 раз меньше памяти.

Если вы когда-нибудь заглядывали в свой профилировщик и задавались вопросом, почему так много времени тратится на сборку мусора или копирование памяти, эти приёмы помогут вам это исправить. Они требуют аккуратности, но дают настоящую производительность.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
12👍9
Rider 2025.3 EAP 6 уже доступен!

В этом билде появилась поддержка ASP. NET и обнаружения проблем с базой данных прямо в окне Monitoring 🔥

Теперь, когда ты запускаешь или отлаживаешь приложение, мониторинг автоматически определяет:

Медленные или чрезмерно частые запросы к БД
Слишком большие результаты выборок
Медленные действия MVC или обработчики Razor-страниц
…и многое другое

Подробнее — здесь

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
14
This media is not supported in your browser
VIEW IN TELEGRAM
Задумывался, почему приложение ощущается «тормозным», хотя с пропускной способностью вроде всё ок? Дело в том, что latency и throughput описывают два совершенно разных аспекта производительности.

Latency — это задержка на один пакет. То, что ощущает пользователь, когда нажимает кнопку. Это отзывчивость системы. Это время, за которое один запрос проходит путь от сервера до конечного устройства. В это входит время обработки на сервере, ожидание в очереди, распространение по сети, задержка при передаче и последний участок соединения до устройства пользователя.

Throughput — это объём данных в секунду. Не скорость отдельного пакета, а то, сколько пакетов проходит через «трубу» за определённый промежуток времени. Throughput — это ёмкость системы. Высокий throughput означает, что система справляется с нагрузкой, не захлёбываясь.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥63👏2
C# и LINQ = функциональный коктейль для кодинга

Если объяснить максимально просто — функциональный стиль позволяет писать код так, как ты о нём говоришь.

«Отфильтровать пользователей старше 30»
«Сгруппировать пользователей по школе»
«Показать все имена пользователей»

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

В LINQ больше двух десятков операторов, но чтобы начать, достаточно пары базовых:

Select — выбрать нужные данные
Where — отфильтровать записи
Any — проверить, существует ли хотя бы один элемент
GroupBy — сгруппировать совпадающие элементы
ToList — собрать результат в List<T>
ToDictionary — собрать результат в Dictionary<K,V>

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥222
Одно небольшое изменение = запрос в 400 раз быстрее

Вот что было сделано, чтобы добиться такого прироста.

Реализовывалась пагинация с курсором через EF и Postgres.

Но нужно было сделать запрос быстрее.

Как ускорить SQL-запрос?

Добавить индекс, и всё должно полететь.

Так ведь?

Не совсем…

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

НО ЗАПРОС СТАЛ МЕДЛЕННЕЕ!!!

Пришлось разбираться, почему индекс не используется.

Оказалось, что дело в сравнении кортежей — это и решило проблему.

Но непонятно было, как перенести это в запрос EF Core.

К счастью, провайдер Postgres поддерживает это через кастомную функцию.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍3
Настройка batch size в Entity Framework

По умолчанию для провайдера SQL Server batch size равен 42.
Это значит, что при вставке 100 строк произойдёт 5 отдельных запросов к базе.

Я недавно гонял простой бенчмарк в .NET, и вот что получилось:
даже если уменьшить количество раунд-трипов, batch size 1000 оказался самым медленным, а вот 200 показал себя лучше всего — некий "sweet spot".

Вывод простой: тестируйте на своих сценариях.
Оптимальный размер batch может отличаться в зависимости от типа нагрузки и объёмов данных.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
1
В свежей статье «Обзор нововведений в C# 14» собрали ключевые фичи новых версий C#:
появилось ключевое слово field, добавили модификаторы в лямбдах, перегрузки присваиваний, extension-свойства, неявные преобразования к Span и даже partial-конструкторы.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍4
В Entity Framework 10 есть анализатор, который выдает предупреждение, если выполняется конкатенация внутри вызова «сырого» SQL-метода, как показано ниже

var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE [" + fieldName + "] IS NULL");


👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍93🥴1
Планирование в Visual Studio теперь доступно в публичной превью-версии

Это обновление добавляет новый способ, с помощью которого Copilot может разруливать сложные, многошаговые задачи. Благодаря Planning Copilot теперь может изучать твой код, разбивать большие задачи на подзадачи и выполнять их пошагово, в итоге ты получаешь более предсказуемые результаты и лучше понимаешь, что именно он делает.

Мы уже видим обнадёживающие результаты: улучшилась успешность и стабильность работы разных моделей. Но это только начало


Попробуй в Visual Studio 2022 версии 17.14:
зайди в Tools → Options → Copilot → Enable Planning и включи функцию.

После этого расскажи, что думаешь.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
1🥴1
6 Шагов для Правильной Настройки Нового .NET-проекта

1. Задай единый стиль кода

Первое, что я добавляю в проект — файл .editorconfig.

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

Создать его можно прямо в Visual Studio.

Стандартная конфигурация - хороший старт, но можно подстроить под вкусы команды.

Положи файл в корень решения, чтобы все проекты следовали тем же правилам. При желании можно переопределить настройки в отдельных подпапках, добавив туда свой .editorconfig.

Вот два примера .editorconfig, которые можно использовать:

- из репозитория .NET runtime
- вариант для обычных .NET-проектов

2. Централизуй настройки сборки

Дальше я добавляю файл Directory.Build.props в корень решения. Он позволяет задавать настройки сборки, общие для всех проектов.

Пример:

<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>


Так .csproj файлы остаются чистыми и единообразными, поэтому не нужно повторять эти свойства в каждом проекте.

Если потом захочешь подключить статические анализаторы или поменять параметры сборки, то делается это в одном месте.

Фишка в том, что .csproj файлы становятся почти пустыми, кроме ссылок на NuGet-пакеты.

3. Централизуй управление пакетами

Когда решение разрастается, версии NuGet-пакетов в разных проектах начинают расходиться и чёрт, это превращается в кошмар 😬

Тут помогает централизованное управление пакетами.

Создай в корне файл Directory.Packages.props:

<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.15.0.120848" />
</ItemGroup>
</Project>


Теперь, когда подключаешь пакет в проекте, указываешь только имя, без версии:

<PackageReference Include="Microsoft.AspNetCore.OpenApi" />


Все версии хранятся централизованно. Обновлять зависимости становится проще, а рассинхрон версий между проектами исчезает.

Если нужно, можно переопределить версию в конкретном проекте.

4. Подключи статический анализ кода

Статический анализ помогает ловить потенциальные баги и следить за качеством кода. В .NET уже есть встроенные анализаторы, но я обычно добавляю SonarAnalyzer.CSharp, ведь он делает проверку глубже.

Установи пакет: Install-Package SonarAnalyzer.CSharp

И добавь его как глобальную зависимость в Directory.Build.props:

<ItemGroup>
<PackageReference Include="SonarAnalyzer.CSharp" />
</ItemGroup>


Дополнительно настрой сборку:

<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>


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

Если какие-то правила не подходят, можно понизить их уровень до none в .editorconfig.

5. Настрой локальную оркестрацию

Чтобы у всех разработчиков среда была одинаковой, стоит настроить контейнерную оркестрацию.

Один из вариантов это .NET Aspire

.NET Aspire добавляет сервис-дискавери, телеметрию и упрощает конфигурацию. Все это встроено прямо в .NET-проекты.

Пример добавления проекта и Postgres-ресурса:

var postgres = builder.AddPostgres("demo-db");

builder.AddProject<WebApi>("webapi")
.WithReference(postgres)
.WaitFor(postgres);

builder.Build().Run();


Aspire под капотом использует Docker, но разработчику с ним работать приятнее.

В любом случае цель одна.

6. Автоматизируй сборку через CI

В конце я настраиваю простой workflow в GitHub Actions, чтобы проверять каждый коммит.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1710❤‍🔥1
Отличный плейлист для тех, кто только начинает изучать бэкенд

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
9
Многие приложения сильно завязаны на реляционные базы данных. Твое приложение, скорее всего, использует сложные запросы, ограничения на данные и прочие приятные фишки реляционных баз. А значит, большая часть поведения твоего приложения напрямую зависит от того, как именно ведет себя база данных.

Поэтому я стараюсь тестировать код на настоящей базе, а не на какой-нибудь подделке. Раньше это было проблемой — большинство СУБД сложно поднимать и автоматизировать. Сейчас всё проще. Используй TestContainers 🙄

TestContainers — это библиотека, которая поднимает Docker-контейнеры для тестов. Более того, в ней уже есть готовые конфигурации для популярных баз данных.

Сначала ставим библиотеку через NuGet. Потом настраиваем первый контейнер. Например, для Microsoft SQL Server:

// Конфигурируем базу, которую хотим поднять
var dbConfig = new MsSqlTestcontainerConfiguration
{
Password = "Test1234",
Database = "TestDB"
};

// Создаем контейнер с этой конфигурацией
var testContainer = new TestcontainersBuilder<MsSqlTestcontainer>()
.WithDatabase(dbConfig)
// Если не указать образ, MsSqlTestcontainerConfiguration выберет дефолтный
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.Build();

// Запускаем контейнер
testContainer.StartAsync().Wait();


Этот код скачает Docker-образ Microsoft SQL Server и запустит контейнер. Потом ты можешь запросить строку подключения и гонять тесты против реальной базы. В примере ниже создается таблица Dogs, вставляется запись и читается обратно. Тест, как ни странно, упадет. Почему? Подсказка в конце поста

class Dog
{
public DateTime BirthDate { get; set; }
public string Name { get; set; }
}

using (var db = new SqlConnection(testContainer.ConnectionString))
{
// Для примера я использую Dapper
db.Execute(@"CREATE TABLE Dogs(
BirthDate DATETIME NOT NULL,
Name VARCHAR(MAX) NOT NULL
)");

var bornAt = new DateTime(2022, 12, 22, 12, 59, 59, 999);
db.Execute(@"INSERT Dogs(BirthDate,Name) VALUES(@BirthDate, @Name)", new
{
BirthDate = bornAt,
Name = "Joe"
});

var dog = db.Query<Dog>(@"SELECT * FROM Dogs").First();
// Этот assert упадет. Почему? Именно поэтому нужно тестировать на реальной базе.
Assert.AreEqual(bornAt, dog.BirthDate);
}


Очистка контейнера:

// Чистим за собой
testContainer.DisposeAsync();


Контейнеры автоматически очищаются

Интересный момент: если ты убьешь процесс, который гоняет тесты, что станет с контейнерами?

Запусти тест, потом посмотри, какие контейнеры сейчас активны: docker ps

Результат будет примерно такой:

roman@gamlor /tmp> docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
86a1da367f12 mcr.microsoft.com/mssql/server:2017... "/opt/mssql/bi..." Up 6s 49180->1433/tcp busy_bohr
5cec99b15206 testcontainers/ryuk:0.3.4 "/app" Up 7s 49179->8080/tcp
testcontainers-ryuk...


Как видно, поднялось два контейнера. Потом можно убить тестовый процесс и наблюдать за docker ps — контейнеры вскоре исчезнут.
Это и есть сила TestContainers. Контейнер testcontainers/ryuk следит за всеми тестовыми контейнерами и убивает их (вместе с собой), если теряет связь с тестовым процессом.

У TestContainers уже есть готовая поддержка множества баз данных. Можно поискать наследников класса TestcontainerDatabase. Аналогично для систем обмена сообщениями — они наследуются от TestcontainerMessageBroker. Полный список есть в документации.

Если готовые конфиги тебе не подходят, можно поднять любой образ вручную:

var someContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("some-image")
.WithEnvironment("USER", "test-user")
.WithCommand("-flag-one -flag-two")
.Build();


Есть и стратегии ожидания, чтобы убедиться, что контейнер полностью запущен и готов к работе.

P.S. Почему упал Assert? Потому что тип datetime в SQL Server имеет странные правила округления. Он может незаметно подправить значение даты. Так что снова повторим, тестируй на настоящей базе!

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
11👏1
Каждое приложение, которое ты используешь, общается с сервером через API.

Много лет стандартом был REST:

- роуты, которые представляют ресурсы (/users, /posts)
- классические методы: GET, POST, PUT, DELETE

Работает отлично, но когда приложение разрастается, начинаются проблемы:

Overfetching — сервер отдает больше данных, чем нужно. (Ты просишь имя, а получаешь весь профиль).

Underfetching — чтобы отобразить один экран, приходится делать несколько запросов. (Ты запрашиваешь посты, а комментарии приходят отдельно).

Жесткость — если меняется UI, приходится менять и бэкенд.

Главная проблема в подходе:

фронтенд мыслит экранами, а бэкенд — ресурсами.

Именно поэтому появился GraphQL — язык, который описывает, как клиент может запрашивать и структурировать данные с сервера.

Он решает три ключевые задачи:

1. Один endpoint — меньше связности между фронтендом и бэкендом.
2 Гибкий запрос — клиент сам определяет, какие данные ему нужны.
3. Типизированная схема — валидация, автодополнение и автогенерация документации.

На практике REST отлично подходит для стабильных эндпоинтов, а GraphQL для динамических вьюх и составных данных.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍4