.NET Разработчик
6.54K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2425. #ЗаметкиНаПолях
Тестирование Текущего Времени с Помощью TimeProvider и FakeTimeProvider
В тестировании сложно использовать то, что зависит от конкретных данных. Представьте себе файловую систему: для корректной работы тестов необходимо убедиться, что файловая система структурирована именно так, как вы ожидаете. Похожая проблема возникает и с датами: если вы создаёте тесты, основанные на текущей дате, при следующем запуске они не пройдут. Нужно найти способ абстрагировать эти функции, чтобы сделать их пригодными для использования в тестах. Сегодня рассмотрим класс TimeProvider, как его использовать и как его имитировать.

Раньше: интерфейс вручную
Раньше самым простым способом абстрагирования управления датами было ручное создание интерфейса или абстрактного класса для доступа к текущей дате:
public interface IDateTimeWrapper
{
DateTime GetCurrentDate();
}

И стандартная его реализация, использующая дату и время в UTC:
public class DateTimeWrapper : IDateTimeWrapper
{
public DateTime GetCurrentDate()
=> DateTime.UtcNow;
}

Или аналогичный подход с абстрактным классом:
public abstract class DateTimeWrapper
{
public virtual DateTime GetCurrentDate() => DateTime.UctNow;
}

Затем нужно просто добавить его экземпляр в движок DI, и всё готово. Единственная проблема - придётся делать это для каждого проекта, над которым вы работаете.

Сейчас: класс TimeProvider
Вместе с .NET 8 команда .NET выпустила абстрактный класс TimeProvider. Помимо предоставления абстракции для локального времени, он предоставляет методы для работы с высокоточными временными метками и часовыми поясами. Важно отметить, что даты возвращаются как DateTimeOffset, а не как экземпляры DateTime. TimeProvider поставляется «из коробки» с консольным приложением .NET:
DateTimeOffset utc = TimeProvider.System.GetUtcNow();
Console.WriteLine(utc);

DateTimeOffset local = TimeProvider.System.GetLocalNow();
Console.WriteLine(local);

А если вам нужно использовать внедрение зависимостей, нужно внедрить его как синглтон:
builder.Services.AddSingleton(TimeProvider.System);

// Использование
public class Vacation(TimeProvider _time)
{
public bool IsVacation
=> _time.GetLocalNow().Month == 8;
}


Тестируем TimeProvider
Мы можем использовать NuGet-пакет Microsoft.Extensions.TimeProvider.Testing, который предоставляет класс FakeTimeProvider, выступающий в качестве заглушки для абстрактного класса TimeProvider. Используя класс FakeTimeProvider, вы можете установить текущее время UTC и местное время, а также настроить другие параметры, предоставляемые TimeProvider:
[Fact]
public void WhenItsAugust_ShouldReturnTrue()
{
// Arrange
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(
new DateTimeOffset(2025, 8, 14,
22, 24, 12, TimeSpan.Zero));
var sut = new Vacation(fakeTime);

Assert.True(sut.IsVacation);
}

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

Источник: https://www.code4it.dev/csharptips/timeprovider-faketimeprovider/
🔥23👍1
День 2426. #TipsAndTricks
Перемещение Файлов и Папок в Корзину в .NET
При работе с файлами и папками в приложениях .NET в Windows может потребоваться перемещать элементы в корзину вместо их безвозвратного удаления (File.Delete, Directory.Delete). Это позволит пользователям восстановить случайно удалённые элементы. Вот как это можно сделать с помощью API Windows Shell:
static void MoveToRecycleBin(string path)
{
if (!OperatingSystem.IsWindows()) return;

var shellType = Type.GetTypeFromProgID(
"Shell.Application", throwOnError: true)!;
dynamic shellApp =
Activator.CreateInstance(shellType)!;

// https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?WT.mc_id=DT-MVP-5003978
var recycleBin = shellApp.Namespace(0xa);

// https://learn.microsoft.com/en-us/windows/win32/shell/folder-movehere?WT.mc_id=DT-MVP-5003978
recycleBin.MoveHere(path);
}


Теперь можно использовать этот метод для перемещения файлов и папок в Корзину:
MoveToRecycleBin(@"C:\path\to\file.txt");
MoveToRecycleBin(@"C:\path\to\directory");


Источник: https://www.meziantou.net/moving-files-and-folders-to-recycle-bin-in-dotnet.htm
👍26
День 2427. #ЗаметкиНаПолях
Особенность ToDictionaryAsync в Entity Framework

В Entity Framework Core ToDictionaryAsync (и, конечно же, его синхронный аналог ToDictionary) извлекает весь объект из БД.

Вот определение ToDictionaryAsync:
public static async Task<Dictionary<TKey, TElement>> ToDictionaryAsync<TSource, TKey, TElement>(
this IQueryable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey>? comparer,
CancellationToken ct = default)
where TKey : notnull
{
Check.NotNull(keySelector);
Check.NotNull(elementSelector);

var d = new Dictionary<TKey, TElement>(comparer);
await foreach (var element in
source.AsAsyncEnumerable()
.WithCancellation(ct)
.ConfigureAwait(false))
{
d.Add(keySelector(element), elementSelector(element));
}

return d;
}


Мы видим, что он принимает лямбды для выбора ключей и значений, а также видим, что этот выбор выполняется на клиенте после получения всего объекта! Поэтому, если у вас есть сущность вроде такой:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Content { get; set; }
public string Author { get; set; }
}

И вы выполняете какой-то такой запрос:
return await dbContext.BlogPosts
.Where(...)
.ToDictionaryAsync(k => k.Author, v => v.Title);

Тогда вы извлекаете из базы данных не только автора и заголовок, но и, описание (Description), и содержание (Content). Решение проблемы описано в этом тикете: используем Select для выбора только нужных полей, так как он выполняется на стороне сервера:
return await dbContext.BlogPosts
.Where(...)
.Select(s => new { Author = s.Author, Title = s.Title })
.ToDictionaryAsync(k => k.Author, v => v.Title);


Источник: https://steven-giesel.com/blogPost/1af57355-7978-40e6-a0f1-3d0ba2c6e1bc/todictionaryasync-retrieves-the-whole-object-from-the-database-in-entity-framework
👍41
День 2428. #Оффтоп
Странная Ошибка Компиляции
Пишу экспорт отчёта в CSV. До этого использовали библиотеку EPPlus (её же используем для экспорта в Excel), но из-за большого размера отчёта оно периодически падает с Out of memory.

Свойства объектов экспортируемой коллекции помечены атрибутами EPPlus для форматирования. Типа [EpplusIgnore] для игнорирования свойства, [EpplusTableColumn(Header="Заголовок", Order = 1)] - для имени заголовка колонки и порядка. См. первую картинку.

Понятно, что EPPlus при экспорте сам эти атрибуты обрабатывает, а мне в самодельном экспорте надо их ручками вытягивать через рефлексию. См. вторую картинку. Обращаюсь через
Attribute.GetCustomAttribute(prop, typeof(…))

или
prop.CustomAttributes.Any(a => a.AttributeType == typeof(…))

И вот когда в typeof(…) кидаю EpplusIgnore, всё работает, а когда кидаю EpplusTableColumn - пишет
The type or namespace name 'EpplusTableColumn' could not be found (are you missing a using directive or an assembly reference?)


В обоих файлах ссылка на нугет есть, юзинг неймспейса есть. И EpplusIgnore, и EpplusTableColumn находятся в одной сборке, в одном неймспейсе, и как атрибуты модели работают прекрасно.

Предложения по исправлению ничем не помогают. Там только что-то в духе «Создать новый класс 'EpplusTableColumn'…».

Есть предположения, в чём проблема?

Чуть позже выложу ответ.

UPD: довольно быстро догадались, ответ в комментариях.
👍2
День 2429. #ЗаметкиНаПолях
Управляем Временем Жизни DbContext. Начало
DbContext — это сердце EF Core, но его легко использовать неправильно.

Основные правила:
- DbContext представляет собой единицу работы и должен существовать недолго.
- Он не является потокобезопасным; никогда не используйте один экземпляр совместно для параллельных операций.
- В ASP.NET Core стандартным и, как правило, правильным выбором является DbContext с ограниченной (scoped) областью действия для каждого запроса. Для задач вне области действия запроса (фоновые сервисы, одиночные объекты, UI-приложения) используйте фабрику для создания новых контекстов по требованию.
Рассмотрим, как правильно подключать DbContext, когда выбирать каждый вид регистрации и каких ловушек следует избегать.

Виды регистрации
1. AddDbContext (Scoped – по умолчанию, для каждого запроса)
// Program.cs
builder.Services
.AddDbContext<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));

Когда использовать: контроллеры MVC/минимальные API, Razor Pages, хабы SignalR — всё, что находится внутри веб-запроса.
Почему: Вы автоматически получаете отдельный контекст на каждый запрос; EF Core эффективно обрабатывает использование подключений.

2. AddDbContextFactory (Transient - фабрика для контекстов по требованию)
builder.Services
.AddDbContextFactory<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));

Использование:
public sealed class ReportService(
IDbContextFactory<AppDbContext> factory)
{
public async Task<IReadOnlyList<OrderDto>>
GetAsync(CancellationToken ct)
{
await using var db = await
factory.CreateDbContextAsync(ct);
return await db.Orders
.Where(…)
.Select(…)
.ToListAsync(ct);
}
}

Когда использовать:
- Фоновые сервисы (IHostedService, BackgroundService),
- Любые синглтон-сервисы, которым требуется DbContext,
- Десктопные/Blazor-приложения, где требуется свежий контекст для каждой операции.
Почему: Фабрики создают чистые, кратковременные контексты, не полагаясь на внешние области видимости.

3. AddDbContextPool (Scoped с пулингом)
builder.Services
.AddDbContextPool<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));

Когда использовать: Высокопроизводительные API, когда конфигурация контекста стабильна и не сохраняет состояние.
Зачем: Повторное использование экземпляров DbContext из пула для снижения затрат на выделение ресурсов.
Внимание: не сохраняйте состояние запросов в контексте; экземпляры в пуле сбрасываются и используются повторно.

Замечания по производительности
- Пулинг уменьшает выделение памяти при высокой нагрузке; оцените свою рабочую нагрузку.
- Проверки потокобезопасности: EF Core может обнаруживать некоторые злоупотребления многопоточностью; вы можете отключить проверки для повышения производительности, но только если вы абсолютно уверены, что в одном контексте нет параллельных запросов. Обычно отключать проверки не рекомендуется.

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

Источник:
https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
1👍36
День 2430. #ЗаметкиНаПолях
Управляем Временем Жизни DbContext. Окончание

Начало

Примеры из реального мира
1. Контроллер / Минимальные API (Scoped)
DI даёт вам scoped-контекст, привязанный к запросу. Одна единица работы, никаких утечек:
app.MapGet("/orders/{id:int}", 
async (int id,
AppDbContext db,
CancellationToken ct) =>
{
var order = await db
.Orders.FindAsync([id], ct);

return order is null
? Results.NotFound()
: Results.Ok(order);
});


2. Фоновые сервисы
Фоновые сервисы не имеют области видимости запроса, вы должны создать область видимости:
public sealed class CleanupService(
IServiceScopeFactory scopes,
ILogger<CleanupService> log)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken stop)
{
while (!stop.IsCancellationRequested)
{
using var scope = scopes.CreateScope();
var db = scope
.ServiceProvider
.GetRequiredService<AppDbContext>();

// Одна короткоживущая единица работы
// … используем db …
await db.SaveChangesAsync(stop);

await Task.Delay(TimeSpan.FromHours(6), stop);
}
}
}


Либо использовать фабрику:
public sealed class CleanupService(
IDbContextFactory<AppDbContext> factory,
ILogger<CleanupService> log)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await using var db = await
factory.CreateDbContextAsync(ct);
// … та же логика, что и выше …
}
}
}


3. Синглтон-сервис, которому нужна БД
Синглтоны не должны захватывать DbContext. Создание нового при каждом вызове помогает избежать проблем:
csharp 
public sealed class PricingCache(
IDbContextFactory<AppDbContext> factory,
IMemoryCache cache)
{
public async Task<decimal>
GetPriceAsync(int id, CancellationToken ct)
{
if (cache.TryGetValue(id, out var price))
return price;

await using var db = await
factory.CreateDbContextAsync(ct);
price = await db.Products
.Where(p => p.Id == id)
.Select(p => p.Price)
.FirstAsync(ct);

cache.Set(id, price, TimeSpan.FromMinutes(10));
return price;
}
}


4. Высокопроизводительные APIs (пулинг)
builder.Services
.AddDbContextPool<AppDbContext>(o =>
{
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default"));
// Не используйте состояние в опциях
// Избегайте изменяемого состояния
});

Совет: Пулинг улучшает пропускную способность. Он не делает DbContext потокобезопасным. По-прежнему один контекст на запрос.

Распространённые ошибки
1. Совместное использование одного DbContext между потоками
Симптом: "A second operation started on this context before a previous operation was completed." (Вторая операция началась в этом контексте до завершения предыдущей операции).
Решение: один контекст на единицу работы; не выполняйте параллельные запросы в одном и том же контексте.

2. Внедрение DbContext в синглтоны
Решение: внедрение IDbContextFactory или IServiceScopeFactory и создание контекстов по требованию.

3. Долгоживущие контексты
Решение: делайте контексты короткоживущими, иначе раздуваются трекеры изменений и удерживаются соединения.

4. Использование пула с состоянием запросов
Решение: не добавляйте пользовательское состояние к контексту (например, поле CurrentUserId). Контексты в пуле переиспользуются.

5. Попытка «ускорения» путём одновременного выполнения нескольких запросов в одном контексте.
Решение: либо сериализуйте работу, либо создайте несколько контекстов. DbContext не является потокобезопасным.

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

Источник: https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
👍26
День 2431. #ЗаметкиНаПолях
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Начало

Если вы уже какое-то время работаете в мире .NET, то знаете, что эта среда выполнения полна скрытых жемчужин. Некоторые из них отточены и дружелюбны, разработаны для того, чтобы новички чувствовали себя в безопасности. Другие — сырые, но мощные инструменты, которым «не обучают в школе», но они дают вам невероятную мощь, если вы знаете, когда и как их использовать. Разберём 5 малоизвестных API, которые могут помочь вам выжать гораздо больше производительности из кода, интенсивно использующего память. Эти функции имеют реальное значение в высокопроизводительных сервисах, конвейерах с малой задержкой и любых приложениях, которые используют много памяти.

1. CollectionsMarshal.AsSpan() для доступа к списку
Обычно, работая с List<T>, вы представляете его себе как удобную и безопасную абстракцию над массивом. Добавляете данные, проходите по списку. Но что, если вам нужен прямой доступ к базовому массиву без копирования?
Стандартный способ:
var numbers = new List<int> { 1, 2, 3, 4, 5 };

// Нужен Span? Придётся делать копию
Span<int> span = numbers.ToArray().AsSpan();
// вариант 2, но всё равно копия
Span<int> span1 = [.. numbers];

// … работа со span’ом

ToArray() создаёт полную копию содержимого списка, тем самым удваивая размер используемой памяти.

Способ лучше:
using System.Runtime.InteropServices;

// Span напрямую над массивом
Span<int> span = CollectionsMarshal.AsSpan(numbers);

Почему: здесь мы работаем непосредственно с внутренним хранилищем списка. Никакого копирования, никаких лишних выделений памяти. Есть одна проблема: если список изменит размер (например, при добавлении нового элемента), span станет недействительным. Именно поэтому этот API находится в System.Runtime.InteropServices.
Когда использовать: при обработке больших буферов или выполнении коротких циклов. Это избавляет от ненужных копий, снижает нагрузку на сборщик мусора и повышает производительность.
Выигрыш: в 2 раза по памяти.

2. CollectionsMarshal.GetValueRefOrNullRef для доступа к словарю
Словари повсюду в приложениях .NET. И чаще всего мы просто делаем что-то вроде этого:
if (dict.TryGetValue("foo", out var value))
{
value++;
dict["foo"] = value;
}

Выглядит безобидно, но мы ищем ключ дважды: в TryGetValue и в индексаторе.

Способ лучше:
using System.Runtime.InteropServices;

ref int valueRef =
ref CollectionsMarshal
.GetValueRefOrNullRef(dict, "foo");

if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;

Почему: С помощью GetValueRefOrNullRef вы получаете ссылку прямо на хранилище словаря. Без повторного поиска, без избыточного вычисления хэша и без лишних записей.
Когда использовать: при работе с большими словарями или циклами, критически важными для производительности (например, с таблицей символов компилятора или кэшем), эти ненужные поиски накапливаются. Этот подход быстрее, но, опять же, слово Unsafe разработчиками языка используется не просто так. Обращайтесь с ref’ами осторожно.
Выигрыш: примерно в 2 раза.

См. также Альтернативный Доступ к Коллекциям в C#13

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

Источник:
https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
1👍33
День 2432. #ЗаметкиНаПолях
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Окончание

Начало

3. GC.AllocateUninitializedArray<T> для более быстрой работы с массивами
При создании нового массива в .NET среда выполнения обнуляет его. Обычно это хорошо, предсказуемое поведение, никаких мусорных данных. Но что, если вам не нужно изначальное содержимое, ведь вы всё равно перезапишете каждый элемент? Обнуление — пустая трата времени.
Стандартный способ:
// Автоматически обнуляется
int[] arr = new int[1000];
for (int i = 0; i < arr.Length; i++)
arr[i] = i; // Перезаписываем всё


Способ лучше:
int[] arr = GC.AllocateUninitializedArray<int>(1000);
for (int i = 0; i < arr.Length; i++)
arr[i] = i;

Почему: здесь среда выполнения пропускает очистку памяти. Это ускоряет выделение памяти, особенно для больших массивов.
Когда использовать: требуется дисциплина. Если вы читаете массив перед записью, вы получите мусорные значения. Но в таких сценариях, как сериализация, повторное использование буфера или краткие циклы с вычислениями, это может сэкономить время.
Выигрыш: ~15% для 1000 элементов.

4. ArrayPool<T>.Shared и IMemoryOwner
Если вы многократно выделяете большие массивы в цикле, сборщик мусора вас возненавидит. Именно здесь на помощь приходит ArrayPool<T> — общий пул массивов, который можно арендовать и вернуть.
Стандартный способ:
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);
var memory = owner.Memory;
DoSomething(memory.Span);
}

Почему: мы арендуем буфер из пула, используем его и возвращаем при уничтожении IMemoryOwner<T>. Никакие выделения памяти не перегружают сборщик мусора.
Когда использовать: особенно актуально в сценариях с высокой пропускной способностью, таких как сетевые серверы, конвейеры или обработка видео. Кроме того, это безопаснее, чем просто арендовать и возвращать массивы, поскольку IMemoryOwner<T> гарантирует корректную очистку.
Выигрыш: более чем в 2 раза быстрее и 1 аллокация вместо 1000.

5. ObjectPool<T> для дорогостоящих объектов
Создание некоторых объектов требует больших затрат. Если вы постоянно создаёте и уничтожаете их, вы тратите не только память, но и ресурсы процессора. Поможет ObjectPool<T>.
Без пулинга:
for (int i = 0; i < 100; i++)
{
var sb = new StringBuilder(1024);
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
}

Это создаст 100 экземпляров StringBuilder. Они будут собраны GC, но зачем создавать столько?

Пулинг:
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);
}

Почему: мы переиспользуем небольшой пул объектов StringBuilder. Выделение памяти значительно сокращается, и мы избегаем постоянного обращения к GC.
Когда использовать: фреймворки логирования, библиотеки сериализации или любые другие рабочие нагрузки, требующие создания множества временных объектов.
Выигрыш: примерно в 4 раза быстрее и в 50 раз меньше по памяти.

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

Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
👍47
День 2433. #SystemDesign101 #Microservices
Как Выглядит Типичная Микросервисная Архитектура?


Балансировщик нагрузки: устройство или приложение, которое распределяет сетевой или прикладной трафик между несколькими серверами.

CDN (Сеть Доставки Контента): группа географически распределённых серверов, которые обеспечивают быструю доставку статического и динамического контента. С CDN пользователям не нужно загружать контент (музыку, видео, файлы, изображения и т. д.) с исходного сервера. Вместо этого контент кэшируется на узлах CDN по всему миру, и пользователи могут загружать его с ближайших узлов CDN.

API-шлюз: обрабатывает входящие запросы и направляет их соответствующим сервисам. Он взаимодействует с провайдером идентификации и выполняет обнаружение сервисов. См. подробнее.

Провайдер идентификации: отвечает за аутентификацию и авторизацию пользователей.

Регистрация и обнаружение сервисов (Service Registry и Service Discovery): Service Registry — это база данных, которая хранит информацию о сервисах и их экземплярах, а Service Discovery — это механизм, использующий этот реестр для автоматического обнаружения, регистрации и отслеживания доступности сервисов в распределенной системе, что особенно важно для микросервисных архитектур, где сервисы могут динамически масштабироваться. API-шлюз ищет соответствующие сервисы в этом компоненте для взаимодействия с ними.

Менеджмент: этот компонент отвечает за мониторинг сервисов.

Микросервисы: микросервисы разрабатываются и развёртываются в разных доменах. Каждый домен имеет свою собственную базу данных. API-шлюз взаимодействует с микросервисами через REST API или другие протоколы, а микросервисы в пределах одного домена взаимодействуют друг с другом с помощью RPC (удалённого вызова процедур).

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

Источник: https://blog.bytebytego.com
👍8
День 2434. #ЧтоНовенького
Срок Поддержки STS Релизов Продлён до 24 месяцев
Microsoft объявили о продлении срока поддержки релизов .NET Standard Term Support с 18 до 24 месяцев. Изменение политики, вступающее в силу с .NET 9, поддержка которого продлена до 10 ноября 2026 года, совпадает с датой окончания поддержки .NET 8, версии с долгосрочной поддержкой (Long Term Support, LTS).

Как заявили в Microsoft, компания будет придерживаться установленного ежегодного графика выпуска релизов каждый ноябрь. Чётные релизы продолжат получать статус долгосрочной поддержки на 3 года, в то время как нечётные релизы обозначены как версии со стандартной поддержкой (STS). Ранее релизы STS получали обновления в течение 18 месяцев, и прекращали поддерживаться через 6 месяцев после выпуска следующей версии.

Основной причиной этого изменения названы проблемы управления зависимостями. Для таких релизов, как .NET Aspire, Microsoft.Extensions.AI и C# Dev Kit, иногда требуются обновлённые версии пакетов из более новых ежегодных выпусков. Эта ситуация создавала сложности для компаний, придерживающихся политики «только-LTS», поскольку они могли непреднамеренно включить компоненты STS при установке этих релизов, что потенциально сокращало сроки их поддержки. Т.е., когда организации, использующие LTS-релизы, устанавливали компоненты, требующие более новых версий пакетов, они непреднамеренно переводили части своей среды выполнения из статуса LTS в статус STS. Продлённый период поддержки решает эту проблему, обеспечивая поддержку пакетов из .NET 9 до той же даты, что и компонентов из .NET 8.

Что касается отзывов сообщества разработчиков, они выявили различные точки зрения на объявление. Некоторые отметили, что продлённый период поддержки повышает жизнеспособность релизов STS в производственных средах. Например, это изменение затрудняет обоснование отказа от запуска новых проектов на последней версии, независимо от её статуса STS или LTS.

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

Изменение политики особенно выгодно компаниям со строгими требованиями к развертыванию «только-LTS», одновременно способствуя внедрению новых возможностей .NET. Как заявили в Microsoft, организациям, планирующим миграцию с .NET 9 на .NET 10, следует продолжить придерживаться текущего графика обновлений, поскольку новые релизы обеспечивают повышение производительности и дополнительные функции.

Создатели библиотек продолжат поддерживать несколько версий одновременно в рамках новой политики. Расширенный жизненный цикл STS может упростить планирование совместимости версий для таких разработчиков. Как сообщалось, это изменение также помогает изменить мнение некоторых разработчиков, которые ошибочно воспринимали выпуски STS как предварительные версии или бета-версии, несмотря на их статус готовности к производству.

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

Источник: https://www.infoq.com/news/2025/09/microsoft-extends-dotnet-sts/
👍11
День 2435. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Начало
LLM, такие как GhatGPT и Claude, изменили наше взаимодействие с компьютерами. Однако они сталкиваются с фундаментальными ограничениями, которые не позволяют им быть немедленно полезными во многих бизнес-контекстах.

Некоторые ограничения:
- Знания LLM часто заморожены на момент окончания обучения. Если мы спросим GPT-4 о событиях, произошедших после сбора обучающих данных, он не узнает об этом, если не подключится к Интернету для сбора информации.
- LLM не имеют доступа к закрытым данным компании. Когда сотрудники задают вопросы о политике компании, а клиенты интересуются конкретными продуктами, стандартный LLM может давать только общие ответы, основанные на общих закономерностях, которые он, возможно, изучил из общедоступных интернет-данных.
- LLM страдают от галлюцинаций (генерируют правдоподобно звучащую, но неверную информацию). Они могут уверенно приводить выдуманные факты, придумывать цитаты или создавать вымышленные ситуации.
- Даже если у LLM есть соответствующие знания, их ответы могут быть скорее общими, чем конкретными для требуемого контекста.

RAG решает эти проблемы, предоставляя ИИ доступ к конкретным документам и данным.

Что это?
По сути, RAG (Retrieval Augmented Generation) — это метод, объединяющий два различных процесса в одну систему:
- Извлечение релевантной информации из коллекции документов.
- Создание точного ответа на основе этой информации.
Представьте, что вы заходите в библиотеку и задаёте библиотекарю конкретный вопрос о местном налоговом кодексе. Обычный библиотекарь может поделиться общими знаниями о налогах, но библиотекарь, имеющий доступ к конкретным налоговым документам города, может подойти к нужной полке, вытащить соответствующее руководство, прочитать нужный раздел и дать точный ответ, основанный на этих официальных документах. Так работает RAG.

Когда мы спрашиваем стандартную LLM, например, о политике компании в отношении отпусков, она может ответить общей информацией о типичных правилах, с которыми она ознакомилась во время обучения, вроде: «Многие компании предлагают 2 раза по 2 недели оплачиваемого отпуска», потому что это распространённая практика. При использовании RAG система сначала извлекает справочник, находит раздел о политике отпусков, а затем генерирует ответ на основе этого документа. Ответ будет следующим: «Согласно справочнику «…», штатные сотрудники могут планировать отпуска по своему усмотрению, но не более 28 оплачиваемых дней отпуска в год».

Упрощённый принцип работы RAG показан на рисунке ниже.

RAG более полезен, чем стандартный LLM в следующих случаях:
1. Когда сценарий использования включает в себя часто меняющуюся информацию, например, данные о товарных запасах, ценах или новостях.
2. При работе с личной или конфиденциальной информацией, которая не была частью обучающих данных модели, например, внутренней документацией, записями клиентов или конфиденциальными исследованиями.
3. Когда точность критически важна, а галлюцинации недопустимы, например, в юридических, медицинских или финансовых приложениях.
4. Когда важно предоставить ссылки или доказать источник информации. Система может указать на конкретные документы и отрывки, обеспечивая прозрачность и контролируемость, которые невозможны при использовании стандартных ответов LLM.
5. Когда приложению необходимо обрабатывать большие коллекции документов, которые было бы непрактично включать в каждое сообщение.

С другой стороны, RAG не нужен для вопросов общего знания, с которыми LLM и так может справиться достаточно хорошо, например, объяснение общих концепций, выполнение базовых рассуждений или задания по творческому письму.

Продолжение следует…

Источник:
https://substack.com/home/post/p-174052561
👍8
День 2436. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение

Начало

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

1. Подготовка (индексация)
Эта основополагающая работа выполняется до поступления пользовательских запросов и включает несколько важных этапов. См. схему 1 ниже.

1) Сбор и обработка документов. Каждый документ (PDF, Word, веб-страница или запись БД) должен быть преобразован в обычный текст. Процесс обрабатывает различные форматы и гарантирует чёткое отделение фактического содержимого от форматирования и метаданных.

2) Разделение текста на мелкие фрагменты. Документы обычно слишком длинные для обработки как единое целое. Размер фрагментов имеет значение: слишком маленький — они теряют контекст, слишком большой — они становятся менее точными. Большинство систем используют фрагменты по 500–1000 слов, часто с некоторым перекрытием между последовательными фрагментами для сохранения контекста.

3) Фрагменты преобразуются в числовые представления – эмбеддинги - список чисел, отражающих его семантическое значение, например, [0,23, -0,45, 0,67, …] с сотнями или тысячами измерений. Числа кодируют смысл текста так, что это позволяет проводить математические сравнения. Схожие понятия порождают схожие числовые шаблоны, что позволяет системе находить связанный контент даже при использовании разных слов.

4) Эмбеддинги вместе с исходными текстовыми фрагментами и их метаданными сохраняются в векторной БД. Она оптимизирована для поиска похожих векторов и индексирует вложения так, что обеспечивает быстрый поиск сходства среди миллионов фрагментов. Метаданные, хранящиеся вместе с каждым фрагментом, обычно включают исходный документ, номера страниц, временные метки и любую другую релевантную информацию, которая может быть полезна для фильтрации или атрибуции.

2. Обработка запросов
Этот этап должен быть быстрым и эффективным, поскольку пользователи ожидают быстрых ответов. См. схему 2 ниже.

1) Вопрос пользователя попадает в систему и проходит тот же с использованием той же модели эмбеддинга, которая использовалась для обработки документов.

2) Система ищет в векторной БД наиболее похожие фрагменты документов. Этот поиск быстр, т.к. использует математические операции, а не сравнение текстов. Обычно система извлекает от 3 до 10 наиболее релевантных фрагментов.

3) Извлечённые фрагменты подготавливаются для языковой модели. Система объединяет их в контекст, часто ранжируя по релевантности и фильтруя на основе метаданных или бизнес-правил.

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

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

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

Продолжение следует…

Источник:
https://substack.com/home/post/p-174052561
👍11
День 2437. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение

Что такое RAG
Как работает RAG

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

Представьте себе разнообразие способов задать вопрос о проблеме с компьютером: «ноутбук не запускается», «компьютер не загружается», «система не включается» или «ПК не работает». В этих фразах практически нет общих слов, но все они описывают одну и ту же проблему. Система, основанная на ключевых словах, воспримет их как совершенно разные запросы и пропустит руководства по устранению неполадок, использующие другую терминологию.

Эмбеддинги решают эту проблему, фиксируя семантическое значение (что текст фактически означает), а не простые совпадения слов. При преобразовании текста в эмбеддинги полученные числа представляют концепции и идеи, содержащиеся в этом тексте. Предложения о проблемах с компьютером в итоге имеют схожие числовые шаблоны, независимо от того, используется ли в них слово «ноутбук», «компьютер» или «ПК».

Процесс преобразования текста в числа:
- Модель эмбеддинга считывает текст и выводит список чисел, обычно сотни или тысячи.
- Эти числа позиционируют текст как точку в многомерном пространстве.
- Точка подобна координатам на карте, но вместо двух измерений (широта и долгота) эмбеддинги используют сотни измерений для передачи множества нюансов смысла.
См. схему ниже.

Это числовое представление позволяет выполнять математические операции. Мы можем вычислить расстояние между двумя эмбеддингами, чтобы оценить степень схожести их значений. Например, тексты о «ремонте ноутбуков» и «починке компьютеров» будут иметь эмбеддинги, расположенные близко друг к другу в этом пространстве, в то время как «ремонт ноутбуков» и «кулинарные рецепты» будут далеко друг от друга. Этот расчёт расстояния выполняется с помощью простых математических вычислений, что делает его чрезвычайно быстрым даже для миллионов документов.

Причина, по которой схожие значения создают схожие числовые закономерности, кроется в том, как обучаются модели эмбеддинга. В процессе обучения модель просматривает миллионы примеров текста и распознаёт, что определённые слова и фразы встречаются в схожих контекстах. Например, «врач» и «терапевт», встречаются в похожих предложениях, используются взаимозаменяемо и относятся к одним и тем же понятиям. Модель учится присваивать им схожие числовые закономерности. Это обучение происходит автоматически благодаря разбору огромных объемов текста, без явного программирования этих взаимосвязей кем-то.

Интересно, что мы не до конца понимаем, что представляет собой каждое измерение. Когда модель встраивания выдаёт 768 чисел для фрагмента текста, мы не можем просто сказать, что измерение 1 представляет «формальность», а измерение 547 — «техническую сложность». Эти измерения возникают естественным образом в процессе обучения, когда модель определяет закономерности, которые ей необходимо отслеживать для эффективного понимания языка. Некоторые измерения могут слабо коррелировать с человеческими понятиями, такими как тональность или тема, а многие отражают абстрактные закономерности, которые не соответствуют ни одному понятию, для которого у нас есть слова.

Модели эмбеддинга и большие языковые модели (LLM) служат разным целям в системе RAG. Модель эмбеддинга небольшая, специализирована для одной задачи: преобразования текста в числа. Модели LLM массивные, предназначены для понимания и генерации текста, похожего на человеческий. В то же время они дорогие. Поэтому системы RAG используют две отдельные модели. Модель эмбеддинга эффективно преобразует все документы и запросы в векторы, обеспечивая быстрый поиск по семантическому сходству. Затем LLM берёт найденные релевантные документы и генерирует интеллектуальные, контекстные ответы.

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

Источник:
https://substack.com/home/post/p-174052561
👍13
День 2438. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Окончание

Что такое RAG
Как работает RAG
Эмбеддинги

Создание RAG-системы

1. Понимание требований.
Разрабатывается ли система для внутренних сотрудников, для которых важна точность и надежность? Или для внешних клиентов, для которых важнее скорость и взаимодействие с UI?

2. Структура документов.
Масштаб имеет значение. Различные объёмы требуют разных стратегий хранения и поиска. Возможные типы контента определяют конвейеры загрузки и предварительной обработки.

3. Шаблоны запросов.
Являются ли большинство запросов простыми поисковыми запросами, например, «Какова наша политика в отношении отпусков?», или требуют сложных рассуждений, например, «Сравните стратегии продаж в III и IV кварталах»? Ожидают ли пользователи точных цитат или просто ответов в разговорном формате? Это определяет, насколько сложной должна быть система.

4. Технологический стек.
Вот некоторые популярные инструменты и технологии.
- LLM. С закрытым исходным кодом (GPT-4 от OpenAI, Claude от Anthropic, Gemini от Google), которые легко внедряются и обеспечивают высокую производительность, но привязаны к поставщику и имеют проблемы конфиденциальности данных. Модели с открытым исходным кодом (Llama 3, Mistral, или специализированные BioBERT для медицины и FinBERT для финансов), обеспечивают больший контроль и гибкость, но требуют инфраструктуры графических процессоров и наличия собственных специалистов для масштабирования.

- Модель эмбеддинга. Распространенные варианты: text-embedding-3 от OpenAI или embed-v3 от Cohere или бесплатные альтернативы с открытым исходным кодом. Специализированные модели, такие как E5 или Instructor, могут дополнительно повысить точность, специфичную для предметной области. Модели LLM и эмбеддинга не обязательно должны предоставляться одним и тем же поставщиком.

- Векторная БД, в которой хранятся и ищутся эмбеддинги: Pinecone, Weaviate Cloud или Qdrant Cloud, отлично подходят для быстрого начала работы и плавного масштабирования, хотя и стоят дороже. Решения с собственным хостингом, такие как ChromaDB, Milvus, Elasticsearch с поиском векторов или расширение PostgreSQL pgvector, обеспечивают больший контроль и могут быть дешевле в долгосрочной перспективе, но требуют инвестиций в DevOps. Правильный выбор зависит от объёма данных (сотни тысяч против миллиардов векторов), нагрузки запросов (десятки против десятков тысяч запросов в секунду) и бюджета.

- Фреймворк для оркестровки. Немногие команды разрабатывают всё с нуля. LangChain — самый популярный фреймворк с широкой экосистемой и абстракциями практически для каждого компонента, хотя в простых случаях он может показаться слишком громоздким. LlamaIndex разработан специально для приложений RAG с большим объёмом документов и предлагает чистый приём данных и конвейеры запросов. Haystack ориентирован на производство и обладает мощной поддержкой сложных рабочих процессов.

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

Источник:
https://substack.com/home/post/p-174052561
👍4
День 2439. #ЗаметкиНаПолях
Используем insteadOf в Git для Замены HTTPS-адресов на SSH
При работе с Git-репозиториями вы часто сталкиваетесь с URL-адресами в формате HTTPS, особенно при клонировании из GitHub, GitLab или других хостинг-провайдеров. Однако, если вы предпочитаете использовать SSH для аутентификации (что часто удобнее с аутентификацией по ключам), ручное изменение URL-адресов может быть утомительным. Кроме того, это может быть ещё сложнее при работе с субмодулями.

Конфигурационная опция Git insteadOf предлагает элегантное решение, автоматически переписывая URL-адреса «на лету»:
git config --global url."git@github.com:".insteadOf "https://github.com/"
git config --global url."git@gitlab.com:".insteadOf "https://gitlab.com/"
git config --global url."git@bitbucket.org:".insteadOf "https://bitbucket.org/"


Вы также можете настроить более конкретную проверку и выбрать только определённые репозитории или сервисы. Например, если вы хотите заменить HTTPS-адреса некоторых репозиториев, вы можете сделать следующее:
git config --global url."git@github.com:username/".insteadOf "https://github.com/username/"


После настройки Git будет автоматически переписывать URL:
# Следующая команда
git clone https://github.com/user/repo.git

# Станет эквивалентной
git clone git@github.com:user/repo.git


Перезапись происходит прозрачно. Вы по-прежнему можете использовать HTTPS-адреса в командах, документации или при копировании из веб-интерфейсов.

Ещё один вариант использования insteadOf — добавление аутентификации к URL. Например, если вы хотите использовать определённого пользователя для всех запросов Git, вы можете сделать следующее:
git config --global url."https://<token>@github.com/".insteadOf "https://github.com/"


Источник: https://www.meziantou.net/using-git-insteadof-to-automatically-replace-https-urls-with-ssh.htm
👍16
День 2440. #Книги
«.NET 8: инструменты и навыки. Лучшие практики и паттерны проектирования, отладки и тестирования.» (Прайс М. — Астана: «Спринт Бук», 2025).

Ещё одну книгу издательство «Питер» прислали (точнее, передали на DotNext) мне на обзор.

В обзоре предыдущей книги я упоминал, что она вторая из серии книг Марка Прайса. Сегодня напишу про третью. В третей части 800 страниц, и, в отличие от второй, где сделан упор на обзор технологий, существующих в экосистеме .NET, в третей рассказывается о лучших практиках, которые можно применять как при создании приложений, так и для продвижения по карьерной лестнице. Вот некоторые темы:
- Эффективная работа с IDE;
- Работа с Git;
- Отладка;
- Ведение журналов, метрик и трассировок;
- Документирование кода и API;
- Рефлексия и динамическая загрузка сборок;
- Криптография;
- Создание ИИ-чатбота;
- Внедрение зависимостей;
- Тестирование: модульное, интеграционное, нагрузочное, функциональное;
- Контейнеризация;
- .NET Aspire;
- Паттерны и принципы проектирования;
- Основы архитектуры ПО;
- Командная работа и собеседования.

Как и в предыдущей книге, автор на практических примерах показывает наиболее интересные на его взгляд приёмы, которые используют многие .NET разработчики: горячие клавиши и функции IDE, команды Git, стратегии отладки, настройки телеметрии, тесты и т.п. Автор попытался охватить ВСЕ лучшие практики. Конечно, подробно в рамках одной книги это сделать нереально, поэтому ко всем перечисленным выше темам стоило бы добавить слово «Основы …». Но информации по каждой теме вполне достаточно, чтобы создать прочный базис, и автор не скупится на ссылки с дополнительной информацией. Кстати, плюсик в карму издательству: где это возможно, ссылки ведут на русскоязычные версии страниц документации.

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

Очень интересная 19я глава про работу в команде и карьеру. В ней описаны хард- и софт-скилы, которых ждут от кандидатов, специализации, которые есть в современных вакансиях, роли в командах разработки. Далее довольно подробно описан процесс поиска работы, составления резюме и подготовки к собеседованию (ещё один плюсик издательству – добавили информацию об особенностях российского рынка разработки). Причём, описаны пути не только для новичков, но и для претендентов на старшие и руководящие должности. Описаны различные сценарии проведения собеседований, рассматриваются частые вопросы, которые задают кандидатам, в том числе поведенческие вопросы.

Интересный факт, одним из научных редакторов книги был «широко известный в узких кругах» Милан Йованович.

Ещё раз спасибо за подарок издательству «Питер». Присылайте что-нибудь ещё 😊
👍25
День 2441. #ЗаметкиНаПолях
Используем Хранимые Процедуры в EF Core. Начало

Необходимость в хранимых процедурах в БД может возникнуть по разным причинам:
- сложный отчёт, объединяющий несколько таблиц с помощью агрегаций и оконных функций, когда LINQ-запрос генерирует неоптимальный SQL;
- нужно обновить таблицу, используя правильные блокировки, чтобы предотвратить состояние гонки.

В разных источниках об использовании хранимых процедур в EF Core можно встретить противоречивые советы: от «избегать использования чистого SQL любой ценой» до «полностью отказаться от EF и использовать ADO.NET». Ни то, ни другое не кажется правильным. EF Core отлично работает с функциями и процедурами БД.

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

Когда стоит использовать чистый SQL?
В большинстве случаев LINQ вполне подходит. EF Core преобразует ваш код C# в качественный SQL, а вы получаете типобезопасность и поддержку рефакторинга. Но есть исключения:
1. Нужна производительность, которую невозможно получить от LINQ. Сложные агрегации с несколькими соединениями, оконными функциями или запросы к отчётам часто выполняются быстрее, если написаны непосредственно на SQL. Вы можете протестировать и настроить запрос в инструменте работы с БД, прежде чем добавлять его в код.
2. Специфичные для БД функции. В PostgreSQL есть полнотекстовый поиск запросы к JSON и общие табличные выражения (CTE), которые не всегда имеют понятные эквиваленты в LINQ.
3. Есть существующая логика в БД (хранимые процедуры и функции, например, из унаследованной системы). Их прямой вызов лучше, чем переписывание всего на C#.
4. Нужны атомарные операции с корректной блокировкой. Хранимая процедура, которая координирует несколько обновлений с помощью блокировок FOR UPDATE, проще и безопаснее, чем пытаться управлять этим из кода приложения.
5. Нужно сократить количество запросов. Один вызов функции, агрегирующий данные из пяти таблиц, эффективнее пяти отдельных LINQ-запросов.

Рассмотрим, как это сделать.

1. Простая скалярная функция
Вот простая функция, которая показывает, сколько билетов осталось:
CREATE OR REPLACE FUNCTION 
tt.tickets_left(ticket_type_id uuid)
RETURNS numeric LANGUAGE sql
AS $$
SELECT tt.available_quantity
FROM ticketing.ticket_types tt
WHERE tt.id = ticket_type_id
$$;

Ничего необычного, просто запрос, обёрнутый в функцию. Вызвать её в EF Core просто:
var result = await dbContext.Database.SqlQuery<int>(
$"""
SELECT tt.tickets_left({ticketTypeId}) AS "Value"
""")
.FirstAsync();

Обратите внимание на алиас AS "Value". Когда EF Core сопоставляется с примитивным типом, он ожидает свойство с именем Value. Кавычки сохраняют точный регистр (PostgreSQL по умолчанию преобразует идентификаторы без кавычек в нижний регистр).

Синтаксис интерполированной строки ($"{ticketTypeId}") может показаться опасным и подверженным SQL-инъекциям, но EF Core автоматически преобразует его в параметризованный запрос. Это всего лишь удобный способ написания параметризованных запросов. Причина в том, что мы передаём методу SqlQuery не строку, а FormattableString. Это специальный тип, который сохраняет формат и аргументы отдельно, позволяя EF Core обрабатывать параметры.

Продолжение следует…

Источник:
https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
👍16
День 2442. #ЗаметкиНаПолях
Используем Хранимые Процедуры в EF Core. Продолжение

Начало

2. Функция, возвращающая таблицу
Функции могут возвращать целые наборы результатов:
CREATE OR REPLACE FUNCTION 
tt.customer_order_summary(customer_id uuid)
RETURNS TABLE (
order_id uuid,
created_at timestamptz,
total numeric,
currency text,
qty numeric
)
LANGUAGE sql
AS $$
SELECT
o.id, o.created_at, o.total, o.currency,
COALESCE(SUM(oi.qty), 0) AS qty
FROM tt.orders o
LEFT JOIN tt.order_items oi ON oi.order_id = o.id
WHERE o.customer_id = customer_id
GROUP BY o.id, o.created_at, o.total, o.currency
ORDER BY o.created_at DESC
$$;

Эта функция объединяет заказы с их товарами, суммирует количество и возвращает несколько строк. Вы можете написать это на LINQ, но SQL понятнее, и вы можете протестировать его непосредственно в инструменте для работы с БД. Чтобы использовать её из C#, создайте DTO, соответствующий выходным данным функции:
public class OrderSummaryDto
{
public Guid OrderId { get; set; }
public DateTime CreatedAt { get; set; }
public decimal Total { get; set; }
public string Currency { get; set; }
public int Quantity { get; set; }
}

Затем запросите функцию, как и любую другую таблицу:
var orders = 
await dbContext.Database
.SqlQuery<OrderSummaryDto>(
$"""
SELECT order_id AS OrderId, created_at AS CreatedAt, total AS Total, currency AS Currency, qty AS Quantity
FROM tt.customer_order_summary({customerId})
""")
.ToListAsync();

Ключевым моментом является сопоставление имён столбцов со свойствами DTO с помощью алиасов. EF Core обрабатывает всё остальное автоматически.

Это простой случай без объединений, но вы можете использовать этот шаблон и в более сложных запросах. Однако вам придётся вручную проецировать данные в DTO, поскольку EF Core не может преобразовать объединения в чистом SQL в графы сущностей. Обычно вы в любом случае возвращаете плоскую структуру из функций, а затем при необходимости преобразуете её в более сложные модели на C#.

Понимание функций и процедур PostgreSQL
PostgreSQL различает функции и процедуры:
- Функции предназначены для возврата значений. Они могут возвращать скалярные значения, таблицы или даже сложные JSON-объекты. Они вызываются внутри транзакции с помощью SELECT и могут использоваться в запросах, предложениях WHERE, объединениях и других контекстах запросов.
- Процедуры предназначены для побочных эффектов. Они не возвращают значения напрямую, но могут изменять данные и иметь OUT-параметры. Вызываются с помощью CALL и идеально подходят для сложных операций, требующих явного управления транзакциями или выполнения нескольких связанных обновлений.

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

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

Источник:
https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
👍8