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
По сути, можно наблюдать, как репозиторий «пишет себя сам», что делает инструмент не только полезным, но и эффектным.
Проект написан на Rust
Тестим
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
.NET Framework в этот раз остался без апдейтов.
Microsoft рекомендует перейти на новую версию, чтобы получить актуальные фиксы и более стабильную работу стека.
Подробности — в официальном блоге Microsoft
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6
C# Portal | Программирование
Не смешивайте CQRS и MediatR. Экосистема .NET со временем практически приравняла CQRS к использованию MediatR. Разберём несколько распространённых заблуждений и выделим сильные стороны каждого подхода. CQRS это архитектурный паттерн, который разделяет операции…
CQRS можно реализовать без всякого MediatR. Команды и запросы спокойно описываются обычными интерфейсами:
Дальше пишем обработчики:
И регистрируем их в DI:
Используем обработчик прямо в контроллере:
В чем разница между этим подходом и MediatR?
Такой вариант дает то же разделение на команды и запросы, но без лишних прослоек. Все прозрачно и явно, и для множества проектов этого более чем хватает.
Но тут нет удобств, которые дает MediatR. Например, пайплайны, поведение-посредники, автоматическая регистрация обработчиков. Плюс вам придется вручную прокидывать обработчики в контроллеры — в больших системах может раздражать.
CQRS и MediatR — это разные инструменты, которые решают разные задачи. Их можно использовать вместе, но связывать их намертво неправильно. CQRS разделяет модель чтения и записи. MediatR разводит компоненты через посредника.
Важно понимать, что именно дает каждый из этих подходов, и выбирать их под свой контекст. Где-то нужны оба. Где-то достаточно одного. А иногда не нужен ни один. Смысл хорошей архитектуры именно в этом - подобрать под задачу те инструменты, которые реально облегчат жизнь, а не создадут лишние слои.
👉 @KodBlog
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 разводит компоненты через посредника.
Важно понимать, что именно дает каждый из этих подходов, и выбирать их под свой контекст. Где-то нужны оба. Где-то достаточно одного. А иногда не нужен ни один. Смысл хорошей архитектуры именно в этом - подобрать под задачу те инструменты, которые реально облегчат жизнь, а не создадут лишние слои.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤5🤯5🤔2🔥1
В .NET 10 появилась экспериментальная фича runtime-async, которая позволяет выполнять async-методы через сам рантайм, а не только через компиляторы C#, VB и F#. Это даёт ощутимый прирост производительности, но пока действует только на ваш собственный код ( базовые фреймворк-библиотеки под runtime-async ещё не пересобраны. )
Чтобы включить поддержку, нужно:
• таргетировать проект на net10.0
• добавить в csproj
• активировать фичу
• при запуске выставить
Хотели бы вы увидеть такую фичу?👀
👉 @KodBlog
Чтобы включить поддержку, нужно:
• таргетировать проект на net10.0
• добавить в csproj
<EnablePreviewFeatures>true</EnablePreviewFeatures>
• активировать фичу
<Features>$(Features);runtime-async=on</Features>
• при запуске выставить
DOTNET_RuntimeAsync=1
Хотели бы вы увидеть такую фичу?
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
.NET Runtime-Async Feature · Issue #109632 · dotnet/runtime
Intro This issue tracks progress on implementing so-called "runtime-async", meaning implementation for async methods directly inside the .NET runtime. This is in contrast to the current s...
👍10❤4
AI в dotnet — RAG-система на Postgres и Ollama
Для начала, что такое RAG?
RAG это архитектура, которая усиливает генеративные LLM, добавляя к ним классические механики поиска ( движки, базы данных и так далее. )
За счет этого модель может выдавать ответы точнее, актуальнее и привязанные к контексту, опираясь и на ваши данные, и на внешние знания.
Пример:
Допустим, компании нужен бот, который мгновенно выдает точные ответы на вопросы о продуктах и сервисах. Вместо того чтобы полагаться только на предобученную LLM, у которой может не быть свежей инфы по компании, бот строится как RAG-система.
Как это работает:
Запрос пользователя/ клиент спрашивает, например, какая у него политика возврата по последнему заказу.
Этап извлечения/ система сначала ищет данные во внутренней документации, базе знаний или БД (FAQ, регламенты, история заказов).
Генерация с контекстом/ найденные данные передаются в LLM, и та формирует структурированный, корректный ответ.
Ответ/ бот говорит что-то вроде:
Вот реализация RAG на .NET.
Для модели использовалась embedding-модель Mistral в Ollama. Установка занимает минут десять.
В качестве векторной базы PostgreSQL.
Гайд по внедрению здесь
👉 @KodBlog
Для начала, что такое RAG?
RAG это архитектура, которая усиливает генеративные LLM, добавляя к ним классические механики поиска ( движки, базы данных и так далее. )
За счет этого модель может выдавать ответы точнее, актуальнее и привязанные к контексту, опираясь и на ваши данные, и на внешние знания.
Пример:
Допустим, компании нужен бот, который мгновенно выдает точные ответы на вопросы о продуктах и сервисах. Вместо того чтобы полагаться только на предобученную LLM, у которой может не быть свежей инфы по компании, бот строится как RAG-система.
Как это работает:
Запрос пользователя/ клиент спрашивает, например, какая у него политика возврата по последнему заказу.
Этап извлечения/ система сначала ищет данные во внутренней документации, базе знаний или БД (FAQ, регламенты, история заказов).
Генерация с контекстом/ найденные данные передаются в LLM, и та формирует структурированный, корректный ответ.
Ответ/ бот говорит что-то вроде:
политика возврата — 30 дней; твой заказ сделан 15 дней назад, значит возврат доступен; нужно запустить процесс?
Вот реализация RAG на .NET.
Для модели использовалась embedding-модель Mistral в Ollama. Установка занимает минут десять.
В качестве векторной базы PostgreSQL.
Гайд по внедрению здесь
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤3
Нашли полезный GitHub-репозиторий с личным набором рабочих скриптов для LINQPad
Внутри .linq-скрипты, заточенные под .NET-проекты: быстрые запросы к базе через LINQPad вместо поднятия отдельных утилит, мелкие проверки, конвертеры данных и прочие «одноразовые» штуки, которые на практике всплывают постоянно.🍿
👉 @KodBlog
Внутри .linq-скрипты, заточенные под .NET-проекты: быстрые запросы к базе через LINQPad вместо поднятия отдельных утилит, мелкие проверки, конвертеры данных и прочие «одноразовые» штуки, которые на практике всплывают постоянно.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤7🥰3🌚1
Использование сортируемых UUID/GUID в Entity Framework
В .NET 9 появились методы Guid.CreateVersion7() и Guid.CreateVersion7(DateTimeOffset), которые генерируют UUID/GUID, упорядочиваемые по времени создания. Это особенно удобно, когда база должна хранить записи в хронологическом порядке, да и в плане производительности есть свои плюсы. Сейчас в Entity Framework нет встроенного механизма, позволяющего подменить генерацию новых GUID на эти методы. Но это легко сделать вручную.
Сначала нужно написать свой ValueGenerator, который будет выдавать GUID через Guid.CreateVersion7():
Подключение:
Готово. Теперь Id у MyEntity будет генерироваться как UUID версии 7. Если хотите, чтобы в EF появился более удобный встроенный способ для таких ключей, можно проголосовать за соответствующий тикет: https://github.com/dotnet/efcore/issues/34158
👉 @KodBlog
В .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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤5
Кто ещё любит точечно использовать именованные аргументы, чтобы вызовы методов в 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, где важна производительность на масштабе, например для журналов активности или потоков событий, где пользователи часто пролистывают большие объёмы данных. Разберём детали реализации и обсудим, где каждый подход уместнее.
Сделаем простое хранилище заметок пользователя на базе такого объекта:
Традиционный подход - офсетная пагинация
Офсетная пагинация использует Skip и Take: мы пропускаем заданное число строк и берём фиксированное количество. Обычно это транслируется в OFFSET и LIMIT в SQL:
Обратите внимание, мы сортируем результаты по Date и Id по убыванию. Это нужно, чтобы разбиение на страницы было стабильным. Вот сгенерированный SQL (в PostgreSql) для офсетной пагинации:
Ограничения офсетной пагинации:
Производительность падает по мере роста смещения, потому что базе приходится сканировать и отбрасывать все строки до OFFSET;
Есть риск потерять элементы или получить дубликаты, если данные меняются между запросами страниц;
Результаты могут быть несогласованными при параллельных обновлениях.
👉 @KodBlog
Пагинация (разбиение на страницы) критична, когда нужно эффективно отдавать большие наборы данных. Хотя офсетная пагинация (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;
Есть риск потерять элементы или получить дубликаты, если данные меняются между запросами страниц;
Результаты могут быть несогласованными при параллельных обновлениях.
Please open Telegram to view this post
VIEW IN TELEGRAM
👎9🔥6❤3
Это огромная база бесплатных курсов по IT/CS/дизайну/бизнесу, где после прохождения можно получить сертификат или цифровой бейдж.
Для бэкендеров тут есть подборка бесплатных курсов с сертификатами по API и REST (FreeCodeCamp, HackerRank), Postman, базам данных (Saylor) и MongoDB (MongoDB University), плюс SQL-база от Kaggle
👉 @KodBlog
Для бэкендеров тут есть подборка бесплатных курсов с сертификатами по API и REST (FreeCodeCamp, HackerRank), Postman, базам данных (Saylor) и MongoDB (MongoDB University), плюс SQL-база от Kaggle
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Ты знаешь, что ArgumentNullException.ThrowIfNull можно использовать даже при таргете на .NET Framework, если задействовать extension members и C# 14?
По сути, ты можешь собрать свою кастомную библиотеку-полифилл, которая будет прятать различия при таргетинге на разные target frameworks.
👉 @KodBlog
По сути, ты можешь собрать свою кастомную библиотеку-полифилл, которая будет прятать различия при таргетинге на разные target frameworks.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3
C# Portal | Программирование
Разбираем курсорную пагинацию. Пагинация (разбиение на страницы) критична, когда нужно эффективно отдавать большие наборы данных. Хотя офсетная пагинация (offset pagination) используется повсеместно, курсорная пагинация (cursor pagination) в некоторых сценариях…
Курсорная пагинация использует контрольную точку (курсор), чтобы получить следующий набор результатов. Эта контрольная точка обычно представляет собой уникальный идентификатор или комбинацию полей, задающих порядок сортировки.
Используем поля Date и Id, чтобы построить курсор для нашей таблицы UserNotes. Курсор это композиция этих двух полей, и она позволяет эффективно делать пагинацию:
Порядок сортировки тот же, что и в примере с офсетной пагинацией
Но при курсорной пагинации сортировка критична для консистентных результатов. Так как Date в нашей таблице не уникален, мы добавляем Id для корректной обработки границ страниц. Это гарантирует, что при пагинации мы не пропустим и не задублируем элементы.
Вот сгенерированный SQL (PostgreSQL) для курсорной пагинации:
Обрати внимание: в запросе нет OFFSET. Производительность курсорной пагинации остаётся постоянной независимо от номера страницы, потому что мы напрямую ищем строки по значениям курсора. Это заметно эффективнее, чем OFFSET. Это огромное преимущество по сравнению с офсетной пагинацией, особенно на больших наборах данных.
COUNT при курсорной пагинации тоже не нужен, потому что мы не считаем общее число элементов. Хотя он может понадобиться, если тебе нужно заранее показать общее количество страниц.
Ограничения курсорной пагинации:
- Если пользователям нужно динамически менять поля сортировки, курсорная пагинация становится сильно сложнее, потому что курсор должен включать все условия сортировки.
- Пользователи не могут перейти к конкретному номеру страницы, только последовательно листать страницы.
- Реализовать корректно сложнее, чем офсетную пагинацию, особенно на границах страниц и при обеспечении корректной сортировки.
👉 @KodBlog
Используем поля 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 при курсорной пагинации тоже не нужен, потому что мы не считаем общее число элементов. Хотя он может понадобиться, если тебе нужно заранее показать общее количество страниц.
Ограничения курсорной пагинации:
- Если пользователям нужно динамически менять поля сортировки, курсорная пагинация становится сильно сложнее, потому что курсор должен включать все условия сортировки.
- Пользователи не могут перейти к конкретному номеру страницы, только последовательно листать страницы.
- Реализовать корректно сложнее, чем офсетную пагинацию, особенно на границах страниц и при обеспечении корректной сортировки.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4❤3👍2
C#
🔹 Модификатор 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
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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🤯2😁1
Хочешь сделать встроенные покупки в приложении на .NET MAUI? Тогда эта статья для тебя.
Тут собрали пример, который показывает, как работать со встроенными покупками в MAUI для iOS, Android и Windows.
А если нужна более сложная обработка платежей, включая серверную часть, советую библиотеку RevenueCat. Вот для неё обёртка под MAUI.
👉 @KodBlog
Тут собрали пример, который показывает, как работать со встроенными покупками в MAUI для iOS, Android и Windows.
А если нужна более сложная обработка платежей, включая серверную часть, советую библиотеку RevenueCat. Вот для неё обёртка под MAUI.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Массовая выборка данных в EF Core
5 методов, которые стоит знать
При работе с Entity Framework Core часто нужно вытащить из базы несколько сущностей по списку ID или значений.
Обычно делают через Contains:
Для небольших списков это нормально, но на больших объёмах начинаются проблемы:
→ Просадка по скорости. Даже с индексами 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
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
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11
Media is too big
VIEW IN TELEGRAM
Запускайте GitHub Actions локально в VS Code!
Расширение GitHub Local Actions позволяет запускать рабочий процесс и отдельные job’ы без коммитов и пушей, используя CLI-инструмент nektos/act. Интерфейс сделан максимально похоже на официальный GitHub Actions, так что вливаться легко.
Можно триггерить стандартные GitHub-события, смотреть историю запусков и логи, а также настраивать secrets, переменные, inputs, runners и payload’ы для выполнения. Отличный способ ускорить разработку и отладку CI🧃
👉 @KodBlog
Расширение GitHub Local Actions позволяет запускать рабочий процесс и отдельные job’ы без коммитов и пушей, используя CLI-инструмент nektos/act. Интерфейс сделан максимально похоже на официальный GitHub Actions, так что вливаться легко.
Можно триггерить стандартные GitHub-события, смотреть историю запусков и логи, а также настраивать secrets, переменные, inputs, runners и payload’ы для выполнения. Отличный способ ускорить разработку и отладку CI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Хорошие новости для пользователей Postgres под конец года. Команда TimescaleDB выпустила и открыла исходники расширения pg_textsearch.
В Postgres уже есть встроенный полнотекстовый поиск, а это расширение делает его более современным и продвинутым за счёт добавления ранжирования BM25.
Если тебе важен быстрый полнотекстовый поиск с нормальной релевантностью без выхода из Postgres, или гибридный retrieval с комбинацией pg_textsearch и pgvector / pgvectorscale — это точно для тебя.
https://github.com/timescale/pg_textsearch
👉 @KodBlog
В Postgres уже есть встроенный полнотекстовый поиск, а это расширение делает его более современным и продвинутым за счёт добавления ранжирования BM25.
Если тебе важен быстрый полнотекстовый поиск с нормальной релевантностью без выхода из Postgres, или гибридный retrieval с комбинацией pg_textsearch и pgvector / pgvectorscale — это точно для тебя.
https://github.com/timescale/pg_textsearch
Please open Telegram to view this post
VIEW IN TELEGRAM
Tiger Data Blog
From ts_rank to BM25. Introducing pg_textsearch: True BM25 Ranking and Hybrid Retrieval Inside Postgres | Tiger Data
pg_textsearch brings BM25 ranking to enable hybrid search to Postgres. Build RAG systems with keyword precision and vector semantics in one database.
👍5❤3
This media is not supported in your browser
VIEW IN TELEGRAM
Помогите… мой VS Code захватили покемоны 😆
Это расширение для VS Code добавляет покемонов прямо внутрь редактора.
Пока ты пишешь код или смотришь, как AI генерит его за тебя, эти покемоны просто тусуются и бегают по экрану, создавая компанию и немного настроения
👉 @KodBlog
Это расширение для VS Code добавляет покемонов прямо внутрь редактора.
Пока ты пишешь код или смотришь, как AI генерит его за тебя, эти покемоны просто тусуются и бегают по экрану, создавая компанию и немного настроения
Please open Telegram to view this post
VIEW IN TELEGRAM
😁14🥰10❤4👎1
C# Portal | Программирование
Курсорная пагинация использует контрольную точку (курсор), чтобы получить следующий набор результатов. Эта контрольная точка обычно представляет собой уникальный идентификатор или комбинацию полей, задающих порядок сортировки. Используем поля Date и Id, чтобы…
Улучшаем курсорную пагинацию
Чтобы ускорить пагинацию, можно добавить индекс:
Индекс создаётся в обратном порядке, чтобы соответствовать ORDER BY в запросах. Однако если посмотреть план выполнения, окажется, что индекс используется, но сам запрос может работать даже медленнее, чем без него:
Возможно, объём данных слишком мал, чтобы индекс дал выигрыш. Но есть один трюк — сравнение кортежей:
В этом варианте индекс отрабатывает корректно и заметно ускоряет выполнение. Оптимизатор запросов не всегда может понять, что составной индекс можно использовать для построчного сравнения, но при сравнении кортежей индекс применяется эффективно.
У провайдера EF для Postgres есть метод EF.Functions.LessThanOrEqual, который принимает ValueTuple. Его можно использовать для построения такого сравнения:
» Кодирование курсора
Курсор, используемый для получения следующей страницы результатов, можно закодировать. Клиенту он отдаётся в виде строки Base64, без знания внутренней структуры:
Пример использования:
👉 @KodBlog
Чтобы ускорить пагинацию, можно добавить индекс:
CREATE INDEX idx_user_notes_date_id
ON user_notes (date DESC, id DESC);
Индекс создаётся в обратном порядке, чтобы соответствовать ORDER BY в запросах. Однако если посмотреть план выполнения, окажется, что индекс используется, но сам запрос может работать даже медленнее, чем без него:
EXPLAIN ANALYZE
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 1000;
Возможно, объём данных слишком мал, чтобы индекс дал выигрыш. Но есть один трюк — сравнение кортежей:
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE (u.date, u.id) <= (@date, @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT 1000;
В этом варианте индекс отрабатывает корректно и заметно ускоряет выполнение. Оптимизатор запросов не всегда может понять, что составной индекс можно использовать для построчного сравнения, но при сравнении кортежей индекс применяется эффективно.
У провайдера EF для Postgres есть метод EF.Functions.LessThanOrEqual, который принимает ValueTuple. Его можно использовать для построения такого сравнения:
query = query.Where(x => EF.Functions.LessThanOrEqual(
ValueTuple.Create(x.Date, x.Id),
ValueTuple.Create(date, lastId)));
» Кодирование курсора
Курсор, используемый для получения следующей страницы результатов, можно закодировать. Клиенту он отдаётся в виде строки Base64, без знания внутренней структуры:
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
public record Cursor(DateOnly Date, Guid LastId)
{
public string Encode() =>
Base64Url.EncodeToString(
Encoding.UTF8.GetBytes(
JsonSerializer.Serialize(this)));
public static Cursor? Decode(string? cursor)
{
if (string.IsNullOrWhiteSpace(cursor))
return null;
try
{
return JsonSerializer.Deserialize<Cursor>(
Encoding.UTF8.GetString(
Base64Url.DecodeFromChars(cursor)));
}
catch
{
return null;
}
}
}
Пример использования:
var cursor = new Cursor(
new DateOnly(2025, 4, 26),
Guid.Parse("019500f9-8b41-74cf-ab12-25a48d4d4ab4"));
var encoded = cursor.Encode();
// eyJEYXRlIjoiMjAyNS0wMi0xNSIsIkxhc3RJZCI6IjAxOTUwMGY5LThiNDEtNzRjZi1hYjEyLTI1YTQ4ZDRkNGFiNCJ9
var decoded = Cursor.Decode(encoded);
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍3❤2