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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2541. #AI
Добавляем ИИ в Существующие REST API с Помощью MCP. Начало
Сегодня многие организации стремятся интегрировать ИИ в существующую инфраструктуру, не переписывая всё заново. Рассмотрим процесс добавления возможностей MCP в существующие REST API на .NET, что позволит обеспечить бесперебойное взаимодействие между системами ИИ и вашими бэкэнд-данными и приложениями.

Что это?
MCP (Model Context Protocol - Протокол Контекста Модели) —стандартизированный протокол с открытым кодом, представленный компанией Anthropic. Он был разработан для обеспечения взаимодействия моделей ИИ (таких как чат-боты или большие языковые модели — LLM) с внешними системами. Одна из его основных особенностей — двусторонняя связь, позволяющая ИИ не только получать данные, но и выполнять действия, такие как внесение обновлений в эти системы.

Главное преимущество MCP в том, что он связывает системы ИИ (такие как ChatGPT или Claude) и бэкэнд-системы, такие как базы данных или сторонние сервисы, создавая гибкий двусторонний поток данных. Если вы хотите добавить функциональность ИИ в приложение без полной переработки, MCP предлагает простое и эффективное решение.

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

Как MCP работает с API .NET?
В то время как REST — это широко используемая архитектура для межсервисного взаимодействия, MCP оптимизирован для взаимодействия ИИ с системами.

Сравнение
1. Целевая аудитория:
REST: В основном используется разработчиками для интеграции сервисов.
MCP: Разработан для взаимодействия моделей и агентов ИИ с бэкэнд-системами. Создан для упрощения интеграции с нетехническими пользователями и системами.

2. Передача информации:
REST: Связь обычно осуществляется по протоколу HTTP с использованием методов GET, POST, PUT, DELETE.
MCP: Может использовать стандартный ввод-вывод для локальной связи или потоковый HTTP для удалённой связи, что поддерживает обмен данными в реальном времени.

3. Доступность:
REST: Требует таких инструментов, как OpenAPI (Swagger), для документирования.
MCP: Автоматически обнаруживает доступные инструменты, ресурсы и подсказки во время выполнения, что делает его более динамичным.

4. Варианты использования:
REST: Отлично подходит для связи между серверами или между сервером и клиентом.
MCP: Лучше всего подходит для интеграции агентов ИИ с бэкэнд-системами, такими как CRM, базы данных или даже сторонние сервисы.

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

Источник:
https://trailheadtechnology.com/reusing-your-existing-net-rest-apis-for-ai-with-mcp/
👎14👍6
День 2542. #AI
Добавляем ИИ в Существующие REST API с Помощью MCP. Окончание

Начало

Добавление MCP в.NET API
Рассмотрим необходимые шаги. Мы будем использовать .NET 10 и MCP C# SDK настройки базового MCP-сервера.

Шаг 1: Установка MCP SDK и настройка сервера
Начните с установки MCP SDK в .NET-проект. SDK предоставляет все необходимые инструменты для создания сервера, который может взаимодействовать с моделями ИИ через MCP.
dotnet add package ModelContextProtocol

Примечание: на данный момент MCP C# SDK находится в режиме предварительного просмотра, поэтому вам также потребуется флаг --version 0.5.0-preview.1.

Добавим в Program.cs код для настройки сервера MCP:
builder.Services
.AddMcpServer()
// Для удалённой коммуникации
.WithHttpTransport()
// Автоматическое обнаружение утилит в проекте
.WithToolsFromAssembly();


Шаг 2: Создание инструментов MCP для конечных точек API
Далее мы определяем инструменты MCP, которые сопоставляются с нашими существующими конечными точками. Это делается путём добавления определённых атрибутов к методам, которые будут предоставлять функциональность системе ИИ.

Например, так конечная точка GetToDos (Список дел) может быть сопоставлена с инструментом MCP:
[McpServerTool]
[Description("Get todos from the todo list.")]
public async Task<IEnumerable<ToDo>> GetToDos()
{
// Логика получения списка дел
}


Шаг 3: Интеграция с моделями ИИ
Теперь, когда MCP-сервер настроен, мы можем интегрировать его с моделями ИИ, такими как Claude или ChatGPT. Когда агент ИИ запрашивает задачу (например, проверку списка дел), он будет использовать протокол MCP для запроса к вашему серверу.

Например, используя ИИ, такой как Claude, вы можете программно получать данные из своего списка дел:
var request = new AIModelRequest("Get To-Dos");
var response = await aiModel.ExecuteRequest(request);

Этот запрос получит данные через сервер MCP, что позволит модели ИИ действовать на их основе, например, отмечать задачи как выполненные.

Полный код примера API тут.

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

Итого
Интеграция MCP с вашими REST API предоставляет мощные возможности ИИ для ваших существующих систем. Независимо от того, создаёте ли вы чат-бота, автоматизируете поддержку клиентов или заблаговременно уведомляете пользователей о ключевых показателях эффективности, MCP обеспечивает необходимую связь для бесперебойной работы.

Источник:
https://trailheadtechnology.com/reusing-your-existing-net-rest-apis-for-ai-with-mcp/
👎7👍4
День 2543. #Карьера
Топ Советов по Повышению Продуктивности. Часть 4

Части 1, 2, 3

4. Разработка, основанная на документации: пишите документацию до написания кода
Звучит нелогично, пока вы не попробуете.

Традиционный рабочий процесс разработчика: написать код, заставить его работать, отполировать его, а затем (возможно, если кто-то напомнит вам или есть контрольный список для PR) написать документацию, объясняющую, что вы только что создали. Документация — это результат разработки, овощи, которые вы едите, потому что они полезны, а не потому что хотите.

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

Преимущества
1. Мгновенная ясность
Написание документации заставляет продумывать детали так, как это никогда не происходит при написании кода. Вы обнаружите проблемы проектирования до того, как их будет дорого исправлять. «А что произойдет, если клиент передаст null?» — вопрос, на который вы ответите на этапе проектирования, а не ошибка, обнаруженная кем-то в продакшене.

2. Улучшение API
Когда вы сначала пишете документацию с точки зрения пользователя, вы естественным образом проектируете более интуитивно понятные интерфейсы. Вы заметите непонятные имена, отсутствующие параметры или неудобные шаблоны использования, потому что придётся их пояснять в документации.

3. Упрощение реализации
Вы уже продумали логику. Документация — это ваше техзадание. Теперь просто переведите это в код. Больше не нужно смотреть на пустой файл, не зная, с чего начать.

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

Новый уровень
Для сложных функций пишите три типа документации перед написанием кода:
1. Документация для пользователя - как кто-то будет использовать эту функцию.
2. Документация API - сигнатуры функций, параметры, возвращаемые значения.
3. Журнал решений - почему вы приняли те или иные проектные решения (это бесценно для вас в будущем). См. также ADR.

Инструменты, поддерживающие этот рабочий процесс:
- Пишите документацию в формате Markdown прямо рядом с кодом.
- Храните файл decisions.md (журнал решений) в корневой директории проекта.
- Для API используйте спецификации OpenAPI/Swagger в качестве инструмента для создания документации.

Изменение мышления
Вы не разработчик, который пишет документацию. Вы дизайнер, который выражает свои замыслы как через документацию, так и через код. Документация — это не налог, который вы платите после создания чего-либо, — это план, который делает создание продукта возможным.

Попробуйте это, реализуя свою следующую задачу. Сначала README. Затем напишите описания и интерфейсы функций перед реализацией тел функций. Напишите документацию API перед реализацией конечных точек. Это кажется странным примерно 30 минут, а потом проявляется путь до самого конца, где на каждом этапе очевидно, что делать.

Источник: https://dev.to/thebitforge/top-10-productivity-hacks-every-developer-should-know-151h
👍9
День 2544. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

16. Настройки и паттерн Options
«Опишите, как реализовать паттерн Options в .NET для управления настройками приложения? Приведите пример настройки и доступа к параметрам из файла конфигурации».

Хороший ответ
В .NET паттерн Options используется для доступа к группам связанных параметров из файлов конфигурации (таких как appsettings.json), переменных среды или других источников конфигурации. Этот паттерн обеспечивает строгую типизацию данных конфигурации с поддержкой проверки данных, что улучшает удобство сопровождения и уменьшает количество ошибок.

Для реализации паттерна Options обычно необходимо:
- Создать класс, представляющий параметры конфигурации.
- Зарегистрировать класс в контейнере внедрения зависимостей (DI).
- Получить доступ к параметрам конфигурации через внедрение зависимостей классах-потребителях.

Определим класс, соответствующий структуре параметров в appsettings.json:
public class MySettings
{
public string ConnectionString { get; set; }
public int RetryCount { get; set; }
}

В appsettings.json:
{
"MySettings": {
"ConnectionString": "…",
"RetryCount": 3
}
}

Затем в Program.cs настроим привязку класса настроек к конфигурации и их использование:
var builder =
WebApplication.CreateBuilder(args);

// привязка
builder.Services.Configure<MySettings>(
builder.Configuration.GetSection("MySettings"));

var app = builder.Build();

// использование
app.MapGet("/", (IOptions<MySettings> options) =>
{
MySettings settings = options.Value;
// …
});

app.Run();

В этой конфигурации MySettings настраивается в контейнере внедрения зависимостей и может быть внедрён в любое место приложения. Для доступа к настройкам используется интерфейс IOptions<T>.

Преимущества
- Строгая типизация: Настройки конфигурации сопоставляются с классами C#, что помогает выявлять ошибки на этапе компиляции.
- Централизованное управление: Все настройки управляются в одном месте, что упрощает обслуживание.
- Гибкость и повторное использование: Настройки могут быть повторно использованы в приложении без жёсткого кодирования.
Этот подход не только упрощает управление конфигурацией, но и повышает масштабируемость и удобство сопровождения приложения.

Часто встречающийся плохой ответ
«Настройки конфигурации определяются в файле appsettings.json. А для доступа к ним используется объект Configuration. Например, Configuration["MySettings:ConnectionString"].»

Почему это неверно
- Отсутствие строгой типизации: Прямой доступ к настройкам с помощью строковых ключей (Configuration["key"]) подвержен ошибкам во время выполнения и не имеет проверки на этапе компиляции. Этот метод увеличивает риск опечаток и не использует преимущества строгой типизации.

- Нарушение принципа единственной ответственности: Распределение доступа к конфигурации по всему приложению приводит к дублированию кода и нарушает принцип единственной ответственности. Это затрудняет поддержку и тестирование кода.

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

См. также:
- Подробнее про паттерн Options
- Проверка Конфигурации .NET
- Не внедряйте IOptions

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍15
День 2545. #ЗаметкиНаПолях
Некоторые Недостатки Частых Советов по Оптимизации Производительности
Давайте будем откровенны: в большинстве случаев сосредоточение внимания на времени задержки не очень полезно. Даже если задержка является серьёзной проблемой, простое изучение времени редко приводит к полезным выводам. Например, если время ответа увеличивается с 5 секунд до 15, эта информация сама по себе не раскрывает первопричину. Нужно понять, что вызывает эти задержки. Ответ обычно заключается в каком-либо потреблении ресурсов — ЦП, память, диск, БД, сеть или что-то ещё. Ключевым моментом является изучение, что вы потребляете и как вы это потребляете. Именно это покажет вам, что нужно изменить и откуда возьмутся самые значительные улучшения.

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

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

Анализ ресурсов и предельное сокращение
Это справедливо независимо от того, сократите ли вы количество серверов, потоков или уменьшите загрузку ЦП. Потенциальная экономия средств может быть существенной. У вас редко бывает эксклюзивный доступ ко всем ресурсам ЦП, другие пользователи или процессы могут извлечь выгоду из любого снижения нагрузки. Даже если вы «владеете машиной», у вас, вероятно, есть другие задачи, которые вы хотите выполнить.

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

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

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

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

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

Источник: https://ricomariani.medium.com/common-performance-tuning-advice-some-flaws-b2c427fad7ca
👍2
День 2546. #ЧтоНовенького #NET11
Аргументы Выражений Коллекций в C #15

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

Замечание: пока это только предложение, и оно может появиться в C# 15 (или, вероятно, позже), а может и никогда.

Что такое выражения коллекций?
Вкратце: выражения коллекций — это альтернативный синтаксис для создания коллекций в C#:
List<int> evens = [2, 4, 6, 8, 10];
List<int> odds = [1, 3, 5, 7, 9];

// Сливаем обе коллекции и добавляем 0 в начало
List<int> all = [0, ..evens, ..odds];

Естественно, это работает не только с List<T>, но и с любым типом, который реализует определённый шаблон. См. подробнее.

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

Но и это можно улучшить!
Здесь в случае с all компилятор создаст новый List с нужной ёмкостью:
num2 = 1 + (list4.Count + list5.Count);
List<int> list6 = new List<int>(num2);


Но бывают случаи, когда компилятор не знает, сколько элементов будет в коллекции, а вы знаете (например, при обращении к базе данных). Сейчас у нас нет возможности указать ёмкость списка в выражении коллекции. Но предлагаемый вариант позволит это изменить. Как уже говорилось ранее, это всего лишь предложение, и синтаксис, используемый в следующем примере, не окончательный (и, откровенно говоря, выглядит он пока странно):
List<int> all = 
[args(capacity: 32), 0, ..evens, ..odds];

Это сразу создаст список вместимостью 32 (хотя это и не очень полезно, но суть понятна). В итоге получится new List<int>(32);. Это может пригодиться и в других случаях. Например, HashSet<T> позволяет передавать IEqualityComparer<T> для сравнения строк:
HashSet<string> names = 
[args(comparer: StringComparer.OrdinalIgnoreCase), "Alice", "Bob", "alice"];


Источник: https://steven-giesel.com/blogPost/352fe495-9cc4-4df8-8ad1-a3e26a64185c/collection-expression-arguments-in-c-15
👍7👎1
День 2547. #ЗаметкиНаПолях
Передавайте Состояние. Начало
Представьте, что вы создали простую функцию фильтрации:
public IEnumerable<Item> 
GetOversized(IEnumerable<Item> items)
=> items.Where(item => item.Size > 45);

А затем решили, что нужно добавить параметр. Интуиция предлагает простое решение:
public IEnumerable<Item> 
GetOversized(IEnumerable<Item> items, int minSize)
=> items.Where(item => item.Size > minSize);


Для фрагментов кода, которые выполняются редко, это будет работать. А на горячем пути? Или в параллельном коде с отменой или продолжением? Давайте разберёмся!

Лямбда-функции
C# позволяет нам определить анонимную функцию, которую можно передать как Func или Action. Сначала определяем параметры, а потом тело функции после стрелки:
item => item.Size > minSize;

Есть одна проблема: эти функции захватывают контекст, т.е. переменные или параметры из окружения. В реальности компилятор создаёт класс, поля которого содержат необходимые данные (в примере выше – переменную minSize).

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

В C# 9.0 добавлены статические анонимные функции, которые запрещают захват контекста. Однако, в примере выше это приведёт к ошибке компиляции:
// Нельзя использовать static и minSize вместе
items.Where(static (item) => item.Size > minSize);

Не всегда возможно избежать захвата контекста. Для использования статических анонимных функций необходимо выполнение одного из двух условий:
1. Функция требует только предоставленных параметров.
2. Существует перегрузка, которая принимает состояние, передаваемое в качестве дополнительного параметра.

Рассмотрим второе условие подробнее:
public IEnumerable<T> Query(Func<T, bool> predicate);

Очевидно, что, если для предиката требуется какое-либо внешнее состояние, потребуется захватить его. А в следующей перегрузке:
public IEnumerable<T> Query<TContext>(
Func<T, TContext, bool> predicate,
TContext ctx);

…добавлен дополнительный параметр, называемый контекстом, который можно передать функции. Суть в том, что он также передается предикату. Если у вас есть чётко определённый контекст, вы можете просто передать его в качестве дополнительного параметра:
repo.Query(
// ctxMinSize передаст параметр minSize в лямбду
static (item, ctxMinSize) =>
(item) => item.Size > ctxMinSize,
minSize);

Теперь мы знаем, что, объединив статические лямбда-функции и параметр контекста, мы можем многого добиться, не захватывая состояние (не выделяя память). Далее рассмотрим примеры.

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

Источник:
https://blog.scooletz.com/2025/10/08/pass-the-state
👍17
День 2548. #ЗаметкиНаПолях
Передавайте Состояние. Продолжение

Начало

Начнём с нескольких простых случаев. Первый максимально прост - агрегирующая функция:
public static TAccumulate 
Aggregate<TSource,TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate,TSource,TAccumulate> func);

Это метод расширения, который перебирает перечислимый объект. Он принимает контекст в виде параметра seed. У него есть функция, которая принимает контекст и элемент перечисляемого объекта. Тот факт, что он возвращает значение типа контекста, является особенностью агрегирующей функции. Это не имеет ничего общего с идеей передачи контекста. Использование:
var sumOfEven = numbers
.Aggregate(
0,
static (a, item) =>
a + (item % 2 == 0 ? 1 : 0));

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

Следующий пример основан на методе, который принимает делегат и некоторое состояние. В данном случае мы рассматриваем одну из перегрузок AddOrUpdate из ConcurrentDictionary:
TValue AddOrUpdate<TArg>(TKey key, 
Func<TKey,TArg,TValue> addValueFactory,
Func<TKey,TValue,TArg,TValue> updateValueFactory,
TArg factoryArgument);

ConcurrentDictionary — словарь, который может обрабатывать одновременный доступ нескольких потоков. Один из распространённых сценариев — добавление или обновление значения. Вот не самый удачный пример его использования (просто для демонстрации):
var counters = 
new ConcurrentDictionary<string, long>();

counters.AddOrUpdate("tag",
static (_, v) => v, // задаём значение
static (_, prev, update) => prev + update, // обновляем
1); // начальное значение

Состояния такого счетчика:
1. Счётчик для конкретного ключа не существует и требуется его создать.
Нужен ключ и делегат для создания значения, который принимает ключ и контекст.
2. Счётчик для конкретного ключа существует и требуется его обновить.
Аналогично, но здесь потребуется ещё один параметр – предыдущее значение.

Синхронизация их выполнения остаётся на усмотрение реализации словаря. Вы можете быть уверены, что после завершения работы этого метода возвращаемое значение будет тем, которое было установлено одним из методов. Обратите внимание: благодаря использованию дополнительного параметра контекста можно использовать статические лямбда-функции! Захват контекста не требуется.

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

Источник:
https://blog.scooletz.com/2025/10/08/pass-the-state
День 2549. #ЗаметкиНаПолях
Передавайте Состояние. Окончание

Начало
Продолжение

CancellationToken.Register
Вы можете зарегистрировать функцию обратного вызова, которая будет вызвана при отмене токена:
CancellationToken ct;
TaskCompletionSource tcs = new ();
ct.Register(() => tcs.TrySetCanceled());

Та же проблема. Попытка добавить модификатор static приведёт к ошибке:
ct.Register(static () => tcs.TrySetCanceled());

Однако, если создатель функции подумал о производительности, он предоставит перегрузку, принимающую состояние.

ct.Register(
static (state) =>
((TaskCompletionSource)state).TrySetCanceled(),
tcs); // Состояние передаётся в последнем параметре

Да, здесь требуется привести состояние к нужному типу и правильно его передать. В обычном бизнес-приложении это может быть необязательно. Но, например, в RavenDB, распределённой БД, созданной на .NET и использующей CancellationToken из BCL, PR #21205 предлагает такие улучшения, используя перегрузку с передачей состояния.

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

Task.ContinueWith
Аналогичный подход, как и с CancellationToken, можно использовать с продолжениями, которые можно прикреплять к задачам вручную. Иногда необходимо прикрепить продолжение к задаче без использования сложного механизма async-await. В таком случае можно использовать метод ContinueWith:
Task connection;
TaskCompletionSource tcs;

connection.ContinueWith ( (t) => {
if (t.IsFaulted)
tcs.TrySetException(t.Exception);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(null);
});

Мы можем следовать тому же подходу, сначала сделав метод статическим. Затем, обратив внимание на ошибку компиляции, исправим её с помощью перегрузки метода, передающей состояние, и приведения типов:
Task connection;
TaskCompletionSource tcs;

// лямбда теперь статическая
connection.ContinueWith ( static (t, state) => {
// приводим к типу продолжения
var c = (TaskCompletionSource) state;
if (t.IsFaulted)
c.TrySetException(t.Exception);
else if (t.IsCanceled)
c.TrySetCanceled();
else
c.TrySetResult(null);
}, tcs); // передаём состояние


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

Источник: https://blog.scooletz.com/2025/10/08/pass-the-state
👍4
День 2550. #Карьера
Топ Советов по Повышению Продуктивности. Часть 5

Части 1, 2, 3, 4

5. Правило 2 минут для проверок кода
Проверки кода — место, где умирают благие намерения. Как обычно бывает: кто-то открывает пул-реквест с 47 измененными файлами и 2300 строками. Вы видите уведомление и думаете: «Я проверю это позже, когда будет время». «Позже» никогда не наступает. PR лежит три дня. Автор пишет вам. Вы наконец выделяете час, открываете PR, сразу чувствуете себя перегруженным, бегло просматриваете его, одобряете с комментарием «Выглядит неплохо», и возвращаетесь к своим делами. Так вы сами способствуете той же проблеме, которая вас раздражает, когда вы ждёте проверки ваших PR.

Правило 2 минут
Если проверка кода займет меньше 2 минут, сделайте её немедленно.
Не «когда закончите текущую задачу». Не «после этой встречи». Не «в назначенное для проверок время». Немедленно.

Небольшие проверки выполняются быстро. PR на 30 строк с чётким контекстом можно проверить за 90 секунд. Прочитать код, убедиться, что он понятен, оставить комментарий или одобрение — готово.

Когда люди знают, что небольшие PR проверяются мгновенно, а большие остаются в очереди бесконечно, поведение меняется органично. Команда естественным образом начинает разбивать работу на более мелкие, более удобные для проверки части.
Это снижает и вашу когнитивную нагрузку. Вместо того чтобы 12 ожидающих проверок преследовали вас, как технический долг, вы сразу же выполняете небольшие. Чувство вины за то, что вы являетесь узким местом, исчезает.
Это улучшает качество кода. Быстрая обратная связь означает, что разработчики работают над проектом, пока контекст свеж. Они все ещё думают о проблеме. Они не переключились на три другие задачи. Раннее выявление проблем делает их исправление дешевле.

Но вот в чем загвоздка: небольшие PR должны быть легко идентифицируемыми.

Работайте с командой над установлением правил:
- PR менее 200 строк получают метку «small».
- Используйте префиксы заголовков PR: [SMALL] или [QUICK].
- Настройте уведомления в Slack или по email, которые будут отправляться по-разному для небольших и больших PR.
- Составьте командное соглашение: небольшие PR проходят проверку в тот же день, большие — когда будет время.

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

Когда команда коллективно внедряет это, скорость проверки резко возрастает. Эта тесная обратная связь ускоряет всё. Функции выпускаются быстрее. Знания распространяются более равномерно. Ошибки обнаруживаются раньше. Сотрудничество ощущается не как бюрократия, а как настоящая командная работа.

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

Советы
- Сделайте уведомления о PR видимыми (Slack, email, расширение для браузера);
- Используйте мобильное приложение GitHub или GitLab для просмотра небольших PR во время перерывов на кофе;
- Создайте сочетание клавиш для быстрого перехода к очереди проверок;
- Отслеживайте время ответа на проверки PR в течение недели — осведомлённость приводит к улучшению;
- Создайте шаблоны PR, которые поощряют просмотр небольших PR. Включите пункт в контрольный список: «Этот PR содержит менее 200 строк или разбит на несколько». Сделайте осознание размера частью культуры команды.

Накопительный эффект быстрой обратной связи — одно из самых эффективных улучшений производительности, которых может добиться команда. Правило двух минут — это не просто проверка кода, это целая философия. Уменьшите препятствия для быстрых действий, и вы будете совершать их чаще. Сделайте отзывчивость простой, и вы сами станете отзывчивыми.

Источник: https://dev.to/thebitforge/top-10-productivity-hacks-every-developer-should-know-151h
👍6👎2
Что выведет код с картинки в первом комментарии? Выберите наиболее подходящий на ваш взгляд ответ.
#Quiz #CSharp
Anonymous Quiz
21%
ничего
8%
0;
11%
526;1=>460,2=>269,3=>230,4=>192
10%
517;1=>496,2=>499,3=>500,4=>492
7%
1351;1=>500,2=>499,3=>496,4=>492
14%
1429;1=>500,2=>500,3=>500,4=>500
6%
2032;1=>500,2=>499,3=>496,4=>492
19%
2054;1=>500,2=>500,3=>500,4=>500
4%
2071;1=>0,2=>500,3=>0,4=>0
👎32
День 2551. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

17. Хостинг и сервер Kestrel
«Расскажите, что такое Kestrel в контексте ASP.NET Core? Как бы вы настроили приложение ASP.NET Core для совместного использования Kestrel и IIS в производственной среде, и почему такая конфигурация может быть полезной?»

Хороший ответ
Kestrel — это кроссплатформенный веб-сервер для ASP.NET Core, созданный для запуска приложений ASP.NET Core напрямую, без использования внешнего сервера. Он легковесный и способен обрабатывать тысячи одновременных подключений, что делает его подходящим для микросервисов и приложений, требующих высокой пропускной способности и низкой задержки.

В производственной среде часто используют Kestrel в паре с обратным прокси-сервером, таким как IIS, Nginx или Apache. Такая конфигурация использует скорость и эффективность Kestrel, а обратный прокси-сервер используется для управления более сложными функциями, такими как SSL-терминация, балансировка нагрузки и обслуживание статических файлов.

Kestrel можно настроить непосредственно в файле Program.cs проекта ASP.NET Core, указав различные параметры, такие как порт и IP-адрес, если это необходимо. Однако для основных операций часто достаточно настроек по умолчанию:
var builder =
WebApplication.CreateBuilder(args);

// Дополнительные настройки Kestrel, если нужно
builder.WebHost.ConfigureKestrel(serverOptions =>
{
// IP, порты, лимиты и т.п.
});

var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();


Для настройки IIS в качестве обратного прокси необходимо убедиться, что на сервере установлен пакет ASP.NET Core Hosting Bundle, который включает в себя среду выполнения .NET и поддержку IIS.

Хотя проекты ASP.NET Core в большинстве случаев не используют файлы web.config для настройки, при размещении за IIS файл web.config используется для настройки модуля ASP.NET Core, который управляет запуском и жизненным циклом процесса:
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*"
modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\YourApp.dll"
forwardWindowsAuthToken="false" stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>


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

Часто встречающийся плохой ответ
«Начиная с ASP.NET Core, можно просто использовать чистый Kestrel даже в производственной среде, потому что это быстрее и эффективнее, чем использовать его с обратным прокси».

Почему это неверно
- Риски безопасности: Запуск чистого Kestrel в производственной среде напрямую подвергает его воздействию извне, что может быть рискованно, поскольку Kestrel не предназначен для такой же надёжной защиты от атак, как веб-серверы типа IIS или Nginx.
- Отсутствие расширенных функций: Kestrel не обрабатывает изначально такие задачи, как SSL-терминация, балансировка нагрузки или расширенная подача статического контента, которые имеют решающее значение для производственной среды.

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍12
День 2552. #BestPractices
Чему 9 Лет Работы в Разработке Научили Меня о «Лучших Практиках»


Автор оригинала: Luthfi Noviandi

Долгое время я считал, что, если достаточно строго следовать лучшим практикам, всё само собой заработает. Поэтому я делал всё «правильно». Чистая архитектура. Микросервисы. Повторные попытки. Паттерны поверх паттернов. Код отлично выглядел на ревью. Диаграммы были чистыми. Это ощущалось как качественная разработка. А потом всё запускалось в продакшен…

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


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

Лучшие практики, которым я больше не следую слепо
1. «Всегда используйте микросервисы»
Микросервисы решают не столько технические проблемы, сколько организационные. Без команды и масштаба, оправдывающих их использование, они в основном увеличивают операционные издержки. Теперь я по умолчанию использую модульный монолит и разделяю сервисы только тогда, когда есть реальная необходимость в этом.

2. «Проектируйте с учётом масштабируемости с самого начала»
Большинство систем выходят из строя не потому, что не могут масштабироваться. Они выходят из строя потому, что их сложно изменять. Теперь я изначально проектирую с учетом ясности поддержки. Масштабирование позже обычно проще, чем распутывание избыточного проектирования на ранних этапах.

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

4. «Гарантии выполнения ровно-один-раз спасут нас»
Гарантии инфраструктуры не защищают от бизнес-ошибок. Если ваша логика не идемпотентна, доставка ровно один раз не спасёт вас от двойной обработки или двойной оплаты.

5. «Чистая архитектура повсюду»
Чистая архитектура полезна до тех пор, пока не превращается в лабиринт. Если абстракции мешают пониманию больше, чем помогают, они вредны.

Практики, которым стоит следовать всегда
Некоторые уроки слишком дорого переучивать:
1. Идемпотентность для всего критически важного.
2. Чёткое определение владельца данных.
3. Сначала наблюдаемость потом оптимизации.
4. Явные сбои вместо скрытых повторных попыток.
Эти практики не делают системы элегантными. Они делают их надёжными.

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

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

Источник: https://blog.stackademic.com/what-9-years-of-backend-engineering-taught-me-about-best-practices-53e8a6e2d21d
👍29
День 2553. #ЗаметкиНаПолях
Миграция Процесса Хеширования Паролей без Простоев. Начало

Требования к безопасности постоянно меняются. То, что считалось «достаточно безопасным» пять лет назад, сегодня может не пройти проверку безопасности. Необходимо перейти на современный алгоритм, такой как Argon2 или Bcrypt. Но вот в чем проблема: хеширование — односторонняя операция. Нельзя переделать существующий хэши под другой алгоритм. А если просто заменить реализацию IPasswordHasher, вы сломаете приложение. Каждый существующий пользователь, пытающийся войти в систему, не пройдёт аутентификацию, потому что новый хэш не совпадёт со старым. Рассмотрим миграцию процесса хеширования без простоев на практике.

Реальные системы имеют больше ограничений (и не следует создавать аутентификацию с нуля). Но это наглядный пример шаблона, который можно повторно использовать для миграции БД:
- Переход от старого формата к новому;
- Сохранение работоспособности существующего поведения;
- Поэтапная миграция данных;
- Удаление устаревших данных только после завершения миграции.

Наивный подход и проблемы с ним
Представим, что у вас есть простая система аутентификации. Вы хотите заменить устаревший хэш PBKDF2 стандартной реализацией Argon2. Можно попытаться просто зарегистрировать новую реализацию в DI-контейнере:
// Меняем LegacyHasher на NewHasher
builder.Services.AddSingleton<IPasswordHasher, NewHasher>();


Cценарий сбоя
Новые пользователи: регистрируются и входят в систему без проблем. Их пароли сразу хэшируются с помощью Argon2.
Существующие пользователи: Пользователь вводит свой правильный пароль. Система извлекает старый хэш PBKDF2 из БД.
Сбой: NewHasher пытается проверить хэш PBKDF2 и терпит неудачу, возвращая ошибку 401 Unauthorized.
Нам нужен способ одновременной поддержки обоих алгоритмов без усложнения кода авторизации.

Решение: Миграция при входе в систему
Стратегия проста: мигрируем пользователей постепенно, по мере подтверждения ими своей личности.

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

Далее рассмотрим реализацию этого алгоритма.

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

Источник:
https://www.milanjovanovic.tech/blog/a-practical-demo-of-zero-downtime-migrations-using-password-hashing
👍6
День 2554. #ЗаметкиНаПолях
Миграция Процесса Хеширования Паролей без Простоев. Окончание

Начало

В .NET 8 представлена регистрация сервисов по ключу, идеально подходящая для этого сценария. Можно зарегистрировать несколько реализаций одного и того же интерфейса и получать к ним доступ по имени.

1. Регистрация сервисов
Мы регистрируем оба сервиса хэширования, присваивая им уникальные ключи:
builder.Services.AddKeyedSingleton<IPasswordHasher, Pbdkf2PasswordHasher>("legacy");
builder.Services.AddKeyedSingleton<IPasswordHasher, Argon2PasswordHasher>("modern");

// (Опционально) Регистрируем новый сервис как вариант по умолчанию
builder.Services.AddSingleton<IPasswordHasher, Argon2PasswordHasher>();


2. Обработка входа в систему
Теперь реализуем логику миграции. Внедряем оба сервиса, используя атрибут FromKeyedServices:
public class LoginCommandHandler(
IUserRepository userRepo,
[FromKeyedServices("modern")]
IPasswordHasher newHasher,
[FromKeyedServices("legacy")]
IPasswordHasher legacyHasher)
{
public async Task<AuthenticationResult>
Handle(LoginCommand command)
{
var user = await userRepo.GetAsync(command);
if (user is null)
return AuthenticationResult.Fail();

// 1. Пробуем новый алгоритм
if (newHasher.Verify(user.PasswordHash, command.Password))
return AuthenticationResult.Success(user);

// 2. При неудаче пробуем старый
if (legacyHasher.Verify(user.PasswordHash, command.Password))
{
// 3. МИГРАЦИЯ: вычисляем новый хэш и обновляем
var newHash = newHasher.Hash(command.Password);

user.UpdatePasswordHash(newHash);
await userRepo.SaveChangesAsync();

return AuthenticationResult.Success(user);
}

return AuthenticationResult.Fail();
}
}

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

Префиксы алгоритмов
Стандартные алгоритмы часто включают префикс (например, Bcrypt начинается с $2a$ или $2b$). Можно использовать это для выбора алгоритма вместо попыток валидации вслепую:
bool IsLegacyHash(string hash)
=> hash.StartsWith("pbkdf2$");


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

На этом этапе вы можете удалить:
- Регистрацию устаревшего сервиса хэширования;
- Код проверки старого хэша.
И миграция завершена.

Источник: https://www.milanjovanovic.tech/blog/a-practical-demo-of-zero-downtime-migrations-using-password-hashing
👍7
День 2555. #ЧтоНовенького
EF Core 10 Превращает PostgreSQL в Реляционно-Документную БД. Начало

Современным .NET-приложениям всё чаще требуется хранить данные, которые не помещаются в реляционные таблицы: кастомные поля для каждого клиента, изменяющиеся атрибуты продукта или данные из внешних API. До EF Core 10 обработка таких гибких данных означала неудобные костыли, столбцы с необработанным текстом JSON или разрозненные NoSQL-хранилища.

Посмотрим, как EF Core 10 представляет новый мощный способ сопоставления столбцов JSONB в PostgreSQL с помощью сложных типов. В сочетании с высокооптимизированным механизмом хранения JSONB в PostgreSQL это позволяет создавать чёткие модели гибких данных с изменяющейся схемой без ущерба для производительности или удобства запросов.

Важность JSONB
Традиционные реляционные модели перестают работать, когда вашему приложению необходимо хранить:
- Динамические или специфичные для клиента поля;
- Иерархические или вложенные атрибуты;
- Развивающиеся структуры, которые часто меняются;
- Данные внешних API или метаданные.
Здесь JSONB проявляет свои лучшие качества: вы получаете гибкость схемы, нативное индексирование, быструю обработку запросов и полные гарантии ACID — и всё это внутри реляционного механизма.

JSON или JSONB в PostgreSQL
- Хранение: JSON – текст, JSONB – двоичное дерево;
- Производительность запросов: JSON – медленно, JSONB – быстро;
- Индексы: JSON – нет, JSONB – GIN/GiST;
- Дублирующиеся ключи: JSON – сохраняются, JSONB – выигрывает последний;
- Плата за парсинг: JSON – в каждом запросе, JSONB – единожды при вставке.
Итого: всегда используйте JSONB, если только вам не нужно сохранять форматирование, тогда используйте JSON.

EF Core 10 изменяет JSON-сопоставление
До .NET 10 сопоставление JSONB требовало наличия принадлежащих сущностей, что приводило к:
- Запутанной семантике принадлежащих типов,
- Теневым первичным ключам,
- Многословной конфигурации,
- Отсутствию поддержки ExecuteUpdate.

Решение для .NET 10: Сложные типы
EF Core 10 представляет сложные типы, предоставляющие:
- Семантику типов-значений,
- Более чистую конфигурацию,
- Автоматические вложенные коллекции,
- Полноценную трансляцию LINQ в JSONB,
- Массовое обновление JSON с помощью ExecuteUpdate,
- Опциональные сложные типы (Address?).

Пример конфигурации:
modelBuilder.Entity<Product>()
.ComplexProperty(p => p.Specs, b => b.ToJson());

Вот и всё - EF Core автоматически обрабатывает вложенные структуры.

Запросы к JSONB в EF Core 10
EF Core теперь транслирует сложные LINQ-запросы напрямую в операторы JSONB, такие как ->, ->>, @>.

Фильтр по атрибуту JSON:
var items = await context.Products
.Where(p => p.Specs.Brand == "Apple")
.ToListAsync();


Фильтр по вложенному числовому полю:
var results = await context.Products
.Where(p => p.Specs.RAM >= 16)
.ToListAsync();


Запрос JSON-массивов:
var items = await context.Products
.Where(p => p.Specs.Features.Contains("Waterproof"))
.ToListAsync();


Массовое обновление JSON с помощью ExecuteUpdate. EF Core 10 обеспечивает реальную поддержку массового обновления JSONB:
await context.Products
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.Metadata.Views,
p => p.Metadata.Views + 1));


Сравнение производительности (10000 записей)
Загрузка+SaveChanges - 5–10с, 10000 запросов
ExecuteUpdate - 100–200мс, 1 запрос

Это значительное улучшение для счётчиков аналитики, обновлений статуса, изменений метаданных и т.д.

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

Источник:
https://trailheadtechnology.com/ef-core-10-turns-postgresql-into-a-hybrid-relational-document-db/
👍24
День 2556. #ЧтоНовенького
EF Core 10 Превращает PostgreSQL в Реляционно-Документную БД. Окончание

Начало

Когда использовать JSONB
- Гибкая схема данных
Метаданные, настройки, определения рабочих процессов, конфигурация.
- Иерархические данные
Вложенные объекты или списки, которые плохо отображаются на реляционные таблицы.
- Часто изменяющиеся данные
Динамические поля, которые изменяются без необходимости миграции.
- Полуструктурированные или внешние данные
Полезные нагрузки веб-хуков, ответы API, интеграции.
- Снепшоты
Журналы аудита, история версий, журналы изменений.

Когда НЕ использовать JSONB
- Стабильные данные основного домена
Реляционные столбцы работают быстрее и обеспечивают соблюдение ограничений.
- Связи по внешним ключам
JSONB не может обеспечить ссылочную целостность.
- Данные с множественными объединениями (JOIN)
Объединения по реляционным полям производительнее извлечений JSON.
- Нагрузки OLTP с высокой частотой записи
Обновление JSONB перезаписывает весь документ.

Правильная индексация JSONB
Без индексации запросы к JSONB быстро деградируют.
Индекс GIN (наиболее частоиспользуемый):
CREATE INDEX idx_specs_gin ON products USING gin (specs);

Индекс-выражение для определённого поля:
CREATE INDEX idx_brand ON products ((specs ->> 'Brand'));

Используйте GIN-индексы для запросов на вхождение и индексы-выражения для фильтрации по определённым ключам.

Разработка гибридной схемы (рекомендуемый подход)
Наиболее надёжные архитектуры сочетают реляционный и JSONB стили.

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

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

Пример модели
public class Product
{
public int Id { get; set; }
public string Category { get; set; } = null!;
public decimal Price { get; set; }

public Specifications Specs { get; set; } = new();
}

Это даёт вашей схеме безопасность и гибкость.

Сводка производительности
JSONB быстрее для:
- Запросов на вхождение (@>);
- Чтения целых документов;
- Запросов, избегающих нескольких объединений таблиц.

JSONB медленнее для:
- Агрегации больших наборов данных;
- Частных обновлений больших документов;
- Сложной логики объединения (JOIN);
- Запросов, требующих строгих реляционных ограничений.

Практический пример
Конфигурация EF Core:
modelBuilder.Entity<Order>(entity =>
{
entity.ComplexProperty(o => o.Metadata, b => b.ToJson());
entity.ComplexCollection(o => o.Items, b => b.ToJson());
});

Запрос:
var orders = await context.Orders
.Where(o => o.Items.Any(i => i.UnitPrice > 100))
.ToListAsync();

Массовое обновление:
await context.Orders
.Where(o => o.Metadata.Status == "Pending")
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.Metadata.Status, "Processing"));


Итого
EF Core 10 наконец-то предоставляет чистый, мощный и первоклассный способ использования JSONB в PostgreSQL через сложные типы:
- Сложные типы — новый стандарт для сопоставления JSON в .NET 10;
- JSONB идеально подходит для гибких, развивающихся иерархических данных;
- ExecuteUpdate повышает производительность при обновлениях JSON;
- Используйте гибридную реляционную модель + JSONB для оптимальной архитектуры;
- JSONB — мощный инструмент, но не замена реляционному проектированию.

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

Источник: https://trailheadtechnology.com/ef-core-10-turns-postgresql-into-a-hybrid-relational-document-db/
👍9👎2
День 2557. #Карьера
Топ Советов по Повышению Продуктивности. Часть 6

Части 1, 2, 3, 4, 5

6. Алиасы команд и скрипты: автоматизируйте свою мышечную память
Быстрый тест: сколько команд вы набираете каждый день?
Для большинства разработчиков это одни и те же действия: запуск сервера разработки, запуск тестов, проверка статуса Git, и т.п. Вы набираете эти команды так часто, что они прочно засели в вашей мышечной памяти.

Секрет продуктивности: каждая повторяющаяся команда — это потраченное впустую время. Не потому, что набор текста медленный (хотя это так), а потому, что каждая команда — это точка принятия решения. «Какой тут флаг? На каком порту работает приложение?» Эти микрорешения накапливаются, превращаясь в сплошную рутину. Автоматизируйте всё, что вы делаете, более двух раз.

1. Алиасы терминала
Добавьте это в ваши .zshrc или .bashrc:
# Git
alias gs='git status'
alias gp='git pull'
alias gpo='git push origin'
alias gc='git commit -m'
alias gco='git checkout'
alias gb='git branch'

# Project
alias proj='cd ~/projects'
alias work='cd ~/projects/work'
alias ..='cd ..'
alias ...='cd ../..'

# Testing
alias run='dotnet run'
alias test='dotnet test'


2. Функции для сложных команд
Когда алиасов недостаточно, пишите функции:
# Новая ветка и пуш 
gnb() {
git checkout -b "$1"
git push -u origin "$1"
}

# Быстрый коммит
qc() {
git add .
git commit -m "$1"
git push
}

# Создать папку и перейти в неё
mkcd() {
mkdir -p "$1"
cd "$1"
}

Теперь gnb feature/new-auth создаст и запушит новую ветку одной командой.

3. Скрипты для проектов
Создайте папку scripts/ в проекте и добавьте частые команды:
#!/bin/bash
# scripts/dev-rebuild.sh

echo "Rebuilding environment…"
dotnet clean
dotnet build
dotnet test
dotnet watch run

Теперь скрипт ./scripts/dev-setup.sh пересобирает, тестирует и запускает ваше приложение.

4. Автозагрузка
Добавьте скрипт в автозагрузку ОС. В скрипт добавьте обновление ваших репозиториев, открытие браузера с багтрекером, пересборку проектов и т.п.

Дело не столько в скорости. Дело в снижении усталости от принятия решений. Когда вы исключаете микрорешения типа «Какая там была команда?», вы сохраняете умственную энергию для решения реальных проблем. Вы дольше остаётесь в состоянии потока и реже переключаетесь между задачами.

Что автоматизировать:
- Настройка и очистка среды;
- Операции с БД (сброс, заполнение, резервное копирование, восстановление);
- Рабочие процессы развёртывания;
- Генерация кода (новые компоненты, конечные точки API, тесты);
- Задачи очистки (удаление веток, очистка кэша);
- Распространённые команды отладки.

Каждый раз, когда вы вводите сложную команду во второй раз, остановитесь. Создайте алиас или скрипт. Это займет 30 секунд и принесёт дивиденды навсегда. Храните файл .aliases в репозитории и синхронизируйте его между компьютерами. Лучшие разработчики не просто печатают быстрее — они автоматизируют всё, что не требует размышлений.

Источник:
https://dev.to/thebitforge/top-10-productivity-hacks-every-developer-should-know-151h
👍11👎1
Что произойдёт при попытке запуска кода с картинки в первом комментарии?
#Quiz #CSharp
Anonymous Quiz
3%
Ошибка компиляции
9%
Ошибка времени выполнения
38%
Вывод "Hello World!"
50%
"Hello World!", затем ошибка времени выполнения
👍7👎1