C# Portal | Программирование – Telegram
C# Portal | Программирование
14.9K subscribers
965 photos
117 videos
24 files
807 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчика

Связь: @devmangx

РКН: https://clck.ru/3FocB6
Download Telegram
Вышла подробная статья о том, как быстро прикрутить LLM к существующему ASP.NET API через Semantic Kernel.

Показан полный путь от создания Azure OpenAI ресурса и деплоя модели до сборки чат-ассистента с автогенерацией вызовов функций через Swagger, хранением контекста в EF Core и оркестрацией через KernelFactory.

Автор отдельно разбирает, как корректная OpenAPI-документация напрямую влияет на точность вызовов функций агентом, и где остаются реальные риски по безопасности и интеграции.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍4❤‍🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Этот трюк с GitHub PR надо знать

Просто добавь “0” перед словом “github” в URL любого Pull Request, и у тебя откроется полноценный PR-вьювер, который подсвечивает каждую строку diff’а цветом в зависимости от того, сколько внимания от человека она, скорее всего, требует.

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

Очень полезный способ быстрее проводить code review и находить то, что обычно легко пропустить.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
16👍6🔥3
Для тех, кто только начинает разбираться в программировании и собирает себе рабочую среду, на GitHub есть полезная подборка Awesome Uses

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

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

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍43
Не смешивайте CQRS и MediatR.

Экосистема .NET со временем практически приравняла CQRS к использованию MediatR. Разберём несколько распространённых заблуждений и выделим сильные стороны каждого подхода.

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

Он появился из понимания того, что во многих приложениях, особенно со сложным доменом, требования к чтению и записи принципиально различаются. Чтение часто требует объединения данных из нескольких источников или представления их в удобном для UI виде. Запись должна обеспечивать соблюдение бизнес-правил, согласованность данных и управление состоянием домена.

Такое разделение даёт несколько преимуществ:

Оптимизированные модели чтения и записи под свои задачи.

Упрощённое сопровождение, так как чтение и запись развиваются независимо.

Более гибкое масштабирование операций чтения и записи.

Чёткая граница между доменной логикой и потребностями представления.

MediatR это реализация шаблона посредника (Mediator). Его основная цель - снизить прямые зависимости между компонентами, предоставив единую точку взаимодействия. Компоненты не знают друг о друге напрямую - они общаются через посредника.

MediatR предоставляет несколько возможностей:

Внутрипроцессный обмен сообщениями между компонентами.

Поведения конвейера (pipeline behaviors) для сквозных задач.

Обработка уведомлений по модели publish/subscribe.

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

Почему их часто используют вместе?

Связка CQRS и MediatR используется неслучайно. Модель запрос/ответ в MediatR хорошо ложится на разделение команд и запросов в CQRS. Команды и запросы можно оформлять как запросы MediatR, а их обработчики будут содержать бизнес-логику.

Пример команды в MediatR:

public record CreateHabit(string Name, string? Denoscription, int Priority) : IRequest<HabitDto>;

public sealed class CreateHabitHandler(
ApplicationDbContext dbContext,
IValidator<CreateHabit> validator)
: IRequestHandler<CreateHabit, HabitDto>
{
public async Task<HabitDto> Handle(
CreateHabit request, CancellationToken ct)
{
await validator
.ValidateAndThrowAsync(request);

var habit = request.ToEntity();
dbContext.Habits.Add(habit);
await dbContext.SaveChangesAsync(ct);

return habit.ToDto();
}
}


Использование CQRS вместе с MediatR даёт ряд преимуществ:

Единый подход к обработке команд и запросов.

Использование pipeline-поведений для логирования, валидации и обработки ошибок.

Чёткое разделение ответственности через классы обработчиков.

Упрощённое тестирование за счёт изоляции обработчиков.

Но за это удобство приходится платить дополнительной абстракцией и сложностью. Нужно описывать классы запросов и ответов, писать обработчики, код для отправки запросов и так далее. Для простых приложений это может быть избыточно.

Вопрос не в том, хороший это компромисс или плохой вообще, а в том, подходит ли он для конкретного проекта и контекста.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83
This media is not supported in your browser
VIEW IN TELEGRAM
Нашли красивый и полезный ИНТЕРАКТИВНЫЙ проект, который показывает, как эволюционировали устройства хранения информации. Там можно посмотреть на работу SSD, HDD и даже ленточных накопителей. 👍

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
9👍5🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Нашлась утилита, которая превращает историю Git-коммитов в полноценный мини-фильм прямо в терминале. Коммиты проигрываются как анимация набора текста, с подсветкой синтаксиса и динамически обновляемым деревом файлов.

По сути, можно наблюдать, как репозиторий «пишет себя сам», что делает инструмент не только полезным, но и эффектным.

Проект написан на Rust 😎

Тестим

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15🤣5🤔2
Вышел декабрьский сервис-релиз .NET 10.0.1. Microsoft опубликовала обновление 9 декабря — в нём набор исправлений по нескольким подсистемам, без каких-либо патчей безопасности. Обновили Runtime, ASP.NET Core, SDK, WinForms, EF Core, а также контейнерные образы и NuGet-пакеты.

.NET Framework в этот раз остался без апдейтов.

Microsoft рекомендует перейти на новую версию, чтобы получить актуальные фиксы и более стабильную работу стека.

Подробности — в официальном блоге Microsoft

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
6
C# Portal | Программирование
Не смешивайте CQRS и MediatR. Экосистема .NET со временем практически приравняла CQRS к использованию MediatR. Разберём несколько распространённых заблуждений и выделим сильные стороны каждого подхода. CQRS это архитектурный паттерн, который разделяет операции…
CQRS можно реализовать без всякого MediatR. Команды и запросы спокойно описываются обычными интерфейсами:

public interface ICommandHandler<in TCommand, TResult>
{
Task<TResult> Handle(
TCommand command, CancellationToken ct = default);
}

// аналогично для IQueryHandler


Дальше пишем обработчики:

public record CreateOrderCommand(
string CustomerId, List<OrderItem> Items)
: ICommand<CreateOrderResult>;

public class CreateOrderCommandHandler :
ICommandHandler<CreateOrderCommand, CreateOrderResult>
{
public async Task<CreateOrderResult> Handle(
CreateOrderCommand command,
CancellationToken ct = default)
{
// реализация
}
}


И регистрируем их в DI:

builder.Services
.AddScoped<ICommandHandler<CreateOrderCommand, CreateOrderResult>,
CreateOrderCommandHandler>();


Используем обработчик прямо в контроллере:

[ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<CreateOrderResult>>
CreateOrder(
CreateOrderCommand command,
ICommandHandler<CreateOrderCommand, CreateOrderResult> handler)
{
var result = await handler.Handle(command);
return Ok(result);
}
}


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

Но тут нет удобств, которые дает MediatR. Например, пайплайны, поведение-посредники, автоматическая регистрация обработчиков. Плюс вам придется вручную прокидывать обработчики в контроллеры — в больших системах может раздражать.

CQRS и MediatR — это разные инструменты, которые решают разные задачи. Их можно использовать вместе, но связывать их намертво неправильно. CQRS разделяет модель чтения и записи. MediatR разводит компоненты через посредника.

Важно понимать, что именно дает каждый из этих подходов, и выбирать их под свой контекст. Где-то нужны оба. Где-то достаточно одного. А иногда не нужен ни один. Смысл хорошей архитектуры именно в этом - подобрать под задачу те инструменты, которые реально облегчат жизнь, а не создадут лишние слои.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍85🤯5🤔2🔥1
В .NET 10 появилась экспериментальная фича runtime-async, которая позволяет выполнять async-методы через сам рантайм, а не только через компиляторы C#, VB и F#. Это даёт ощутимый прирост производительности, но пока действует только на ваш собственный код ( базовые фреймворк-библиотеки под runtime-async ещё не пересобраны. )

Чтобы включить поддержку, нужно:

• таргетировать проект на net10.0
• добавить в csproj
<EnablePreviewFeatures>true</EnablePreviewFeatures>

• активировать фичу
<Features>$(Features);runtime-async=on</Features>

• при запуске выставить
DOTNET_RuntimeAsync=1


Хотели бы вы увидеть такую фичу? 👀

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍104
AI в dotnet — RAG-система на Postgres и Ollama

Для начала, что такое RAG?

RAG это архитектура, которая усиливает генеративные LLM, добавляя к ним классические механики поиска ( движки, базы данных и так далее. )

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

Пример:

Допустим, компании нужен бот, который мгновенно выдает точные ответы на вопросы о продуктах и сервисах. Вместо того чтобы полагаться только на предобученную LLM, у которой может не быть свежей инфы по компании, бот строится как RAG-система.

Как это работает:

Запрос пользователя/ клиент спрашивает, например, какая у него политика возврата по последнему заказу.

Этап извлечения/ система сначала ищет данные во внутренней документации, базе знаний или БД (FAQ, регламенты, история заказов).

Генерация с контекстом/ найденные данные передаются в LLM, и та формирует структурированный, корректный ответ.

Ответ/ бот говорит что-то вроде:
политика возврата — 30 дней; твой заказ сделан 15 дней назад, значит возврат доступен; нужно запустить процесс?


Вот реализация RAG на .NET.

Для модели использовалась embedding-модель Mistral в Ollama. Установка занимает минут десять.

В качестве векторной базы PostgreSQL.

Гайд по внедрению здесь

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍103
Нашли полезный GitHub-репозиторий с личным набором рабочих скриптов для LINQPad

Внутри .linq-скрипты, заточенные под .NET-проекты: быстрые запросы к базе через LINQPad вместо поднятия отдельных утилит, мелкие проверки, конвертеры данных и прочие «одноразовые» штуки, которые на практике всплывают постоянно. 🍿

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥87🥰3🌚1
Использование сортируемых UUID/GUID в Entity Framework

В .NET 9 появились методы Guid.CreateVersion7() и Guid.CreateVersion7(DateTimeOffset), которые генерируют UUID/GUID, упорядочиваемые по времени создания. Это особенно удобно, когда база должна хранить записи в хронологическом порядке, да и в плане производительности есть свои плюсы. Сейчас в Entity Framework нет встроенного механизма, позволяющего подменить генерацию новых GUID на эти методы. Но это легко сделать вручную.

Сначала нужно написать свой ValueGenerator, который будет выдавать GUID через Guid.CreateVersion7():

public class UUIDv7Generator : ValueGenerator<Guid>
{
public override bool GeneratesTemporaryValues => false;

public override Guid Next(EntityEntry entry)
=> Guid.CreateVersion7();
}


Подключение:

public class MyDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<MyEntity>()
.Property(e => e.Id)
.HasValueGenerator<UUIDv7Generator>()
.ValueGeneratedOnAdd();
}
}


Готово. Теперь Id у MyEntity будет генерироваться как UUID версии 7. Если хотите, чтобы в EF появился более удобный встроенный способ для таких ключей, можно проголосовать за соответствующий тикет: https://github.com/dotnet/efcore/issues/34158

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍145
Кто ещё любит точечно использовать именованные аргументы, чтобы вызовы методов в C# читались понятнее?

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
32💯15👍9🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Бывало, что хочется принять автодополнение не целиком, а только кусок и сразу одним кликом? Теперь можно кликнуть прямо по подсказке и принять её только до позиции курсора.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍5
Разбираем курсорную пагинацию.

Пагинация (разбиение на страницы) критична, когда нужно эффективно отдавать большие наборы данных. Хотя офсетная пагинация (offset pagination) используется повсеместно, курсорная пагинация (cursor pagination) в некоторых сценариях даёт интересные преимущества. Она особенно полезна для real-time лент, интерфейсов с бесконечной прокруткой и API, где важна производительность на масштабе, например для журналов активности или потоков событий, где пользователи часто пролистывают большие объёмы данных. Разберём детали реализации и обсудим, где каждый подход уместнее.

Сделаем простое хранилище заметок пользователя на базе такого объекта:

public record UserNote (
Guid Id,
Guid UserId,
string? Note,
DateOnly Date
);


Традиционный подход - офсетная пагинация

Офсетная пагинация использует Skip и Take: мы пропускаем заданное число строк и берём фиксированное количество. Обычно это транслируется в OFFSET и LIMIT в SQL:

var query = dbContext.UserNotes
.OrderByDescending(x => x.Date)
.ThenByDescending(x => x.Id);

// При офсетной пагинации мы обычно сначала считаем общее количество элементов
var total = await query
.CountAsync(cancellationToken);
var pages = (int)Math.Ceiling(total / (double)pageSize);

var query = dbContext.UserNotes
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);


Обратите внимание, мы сортируем результаты по Date и Id по убыванию. Это нужно, чтобы разбиение на страницы было стабильным. Вот сгенерированный SQL (в PostgreSql) для офсетной пагинации:

-- запрос общего количества
SELECT count(*)::int FROM user_notes AS u;

-- запрос данных
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
ORDER BY u.date DESC, u.id DESC
LIMIT @pageSize OFFSET @offset;


Ограничения офсетной пагинации:

Производительность падает по мере роста смещения, потому что базе приходится сканировать и отбрасывать все строки до OFFSET;

Есть риск потерять элементы или получить дубликаты, если данные меняются между запросами страниц;

Результаты могут быть несогласованными при параллельных обновлениях.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👎9🔥63
Это огромная база бесплатных курсов по IT/CS/дизайну/бизнесу, где после прохождения можно получить сертификат или цифровой бейдж.

Для бэкендеров тут есть подборка бесплатных курсов с сертификатами по API и REST (FreeCodeCamp, HackerRank), Postman, базам данных (Saylor) и MongoDB (MongoDB University), плюс SQL-база от Kaggle

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
3
Ты знаешь, что ArgumentNullException.ThrowIfNull можно использовать даже при таргете на .NET Framework, если задействовать extension members и C# 14?

По сути, ты можешь собрать свою кастомную библиотеку-полифилл, которая будет прятать различия при таргетинге на разные target frameworks.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍3
C# Portal | Программирование
Разбираем курсорную пагинацию. Пагинация (разбиение на страницы) критична, когда нужно эффективно отдавать большие наборы данных. Хотя офсетная пагинация (offset pagination) используется повсеместно, курсорная пагинация (cursor pagination) в некоторых сценариях…
Курсорная пагинация использует контрольную точку (курсор), чтобы получить следующий набор результатов. Эта контрольная точка обычно представляет собой уникальный идентификатор или комбинацию полей, задающих порядок сортировки.

Используем поля Date и Id, чтобы построить курсор для нашей таблицы UserNotes. Курсор это композиция этих двух полей, и она позволяет эффективно делать пагинацию:

var query = dbContext.UserNotes.AsQueryable();

if (date != null && lastId != null)
{
// Используем курсор для выборки следующего чанка результатов
// При прямой сортировке нужно заменить > на <
query = query
.Where(x => x.Date < date ||
(x.Date == date && x.Id <= lastId));
}

// Забираем на 1 элемент больше
var items = await query
.OrderByDescending(x => x.Date)
.ThenByDescending(x => x.Id)
.Take(limit + 1)
.ToListAsync(cancellationToken);

// Определяем параметры для следующей страницы
var hasMore = items.Count > limit;
DateOnly? nextDate =
hasMore ? items[^1].Date : null;
Guid? nextId =
hasMore ? items[^1].Id : null;

// Удаляем "лишний" результат, если он есть
if (hasMore)
items.RemoveAt(items.Count - 1);


Порядок сортировки тот же, что и в примере с офсетной пагинацией

Но при курсорной пагинации сортировка критична для консистентных результатов. Так как Date в нашей таблице не уникален, мы добавляем Id для корректной обработки границ страниц. Это гарантирует, что при пагинации мы не пропустим и не задублируем элементы.

Вот сгенерированный SQL (PostgreSQL) для курсорной пагинации:

SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE u.date < @date OR (u.date = @date AND u.id <= @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT @limit;


Обрати внимание: в запросе нет OFFSET. Производительность курсорной пагинации остаётся постоянной независимо от номера страницы, потому что мы напрямую ищем строки по значениям курсора. Это заметно эффективнее, чем OFFSET. Это огромное преимущество по сравнению с офсетной пагинацией, особенно на больших наборах данных.

COUNT при курсорной пагинации тоже не нужен, потому что мы не считаем общее число элементов. Хотя он может понадобиться, если тебе нужно заранее показать общее количество страниц.

Ограничения курсорной пагинации:

- Если пользователям нужно динамически менять поля сортировки, курсорная пагинация становится сильно сложнее, потому что курсор должен включать все условия сортировки.

- Пользователи не могут перейти к конкретному номеру страницы, только последовательно листать страницы.

- Реализовать корректно сложнее, чем офсетную пагинацию, особенно на границах страниц и при обеспечении корректной сортировки.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥43👍2
C# in параметр

🔹Модификатор in позволяет компилятору создать временную переменную для аргумента и передать только чтение по ссылке.

🔹Методы с in параметрами потенциально получают оптимизацию производительности за счёт избежания копирования больших структур.

Подробнее у Microsoft: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters#in-parameter-modifier

Пример в репозитории: https://github.com/karenpayneoregon/learning-topics/tree/master/InParameterSampleApp

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🤯2😁1
Хочешь сделать встроенные покупки в приложении на .NET MAUI? Тогда эта статья для тебя.

Тут собрали пример, который показывает, как работать со встроенными покупками в MAUI для iOS, Android и Windows.

А если нужна более сложная обработка платежей, включая серверную часть, советую библиотеку RevenueCat. Вот для неё обёртка под MAUI.

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍4🔥3
Массовая выборка данных в EF Core

5 методов, которые стоит знать

При работе с Entity Framework Core часто нужно вытащить из базы несколько сущностей по списку ID или значений.

Обычно делают через Contains:

var products = await dbContext.Products
.Where(p => productIds.Contains(p.Id))
.ToListAsync();


Для небольших списков это нормально, но на больших объёмах начинаются проблемы:

→ Просадка по скорости. Даже с индексами SQL-условие WHERE IN тормозит всё сильнее по мере роста количества параметров.

→ Упираемся в лимит параметров. В SQL Server жёсткий предел: 2 100 параметров на запрос. Можно дробить на батчи на уровне SQL, но в EF Core это не всегда удобно, особенно когда есть доп. фильтры и join’ы.

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

Это реально всплывает в задачах вроде синхронизации каталога, обработки массовых заказов или обновления остатков по тысячам позиций.

В чистом SQL обычно обходят лимит через временную таблицу или параметр-таблицу.

Но такие варианты требуют писать сырой SQL и теряются плюсы EF Core -» строгая типизация запросов, отслеживание изменений и поддержка навигационных свойств.

Есть более удобное решение --» библиотека Entity Framework Extensions даёт специальные методы для массовой выборки, которые закрывают эти проблемы.

Внутри она использует временные таблицы, чтобы обойти лимит параметров и ускорить запросы. Вместо передачи тысяч значений в WHERE IN она делает так:

- создаёт временную таблицу в базе
- вставляет туда значения фильтра
- джойнится таблицей сущностей к временной таблице
- возвращает отфильтрованный результат
- автоматически удаляет временную таблицу

На больших наборах данных это заметно ускоряет выборку.

В Entity Framework Extensions есть пять основных методов для массовой выборки:
→ WhereBulkContains
→ WhereBulkNotContains
→ BulkRead
→ WhereBulkContainsFilterList
→ WhereBulkNotContainsFilterList

👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
11