Веб-версияОткрыть в Telegram
..NET Разработчик

.NET Разработчик

✅ Высокое доверие
@NetDeveloperDiary · канал · Технологии · в индексе с 2026-05-29
6 750подписчиков+1 за неделю
1 355средний охват поста
20.1%ER — охват к подписчикам
29постов за 30 дней
.
.NET Разработчик
День 2781. #ЗаметкиНаПолях Паттерн «Производитель-потребитель» c System.Threading.Channels. Начало Представим эндпоинт, принимающий файлы, изменяющий их размер и возвращающий ответ. В демо-версии всё работает отлично. Но в проде нагрузка растёт, и эндпойнт получает сотни запросов в секунду. Каждый запрос запускает задачу по изменению размера прямо в потоке обработки; CPU загружается до 100%, а остальные функции приложения начинают завершаться по таймауту, т.к. пул потоков перегружен. Не обязательно обрабатывать запросы сразу. Достаточно принять данные и обработать их в удобном для системы темпе. Это классическая задача типа «производитель-потребитель»: одна сторона передает задачу, а другая выполняет её со скоростью, которую реально может поддерживать. Для простейшей её реализации не нужны брокеры сообщений, достаточно System.Threading.Channels. Далее рассмотрим реализацию. Каналы в .NET позволяют передавать данные между производителями и потребителями, работающими параллельно в одном процессе. Производители записывают данные в ChannelWriter<T>, потребители считывают их из ChannelReader<T>, а канал обеспечивает потокобезопасную асинхронную передачу между ними. Важный момент — канал с ограниченным размером (bounded) автоматически обеспечивает механизм «обратного давления» (backpressure). Почему «наивное» решение только всё усугубляет Первый порыв в решении проблемы – сделать всё асинхронным: запустить задачу через Task.Run и сразу вернуть ответ: [HttpPost("process")] public IActionResult Process(UploadRequest request) { // Запустил и забыл. Выглядит асинхронно _ = Task.Run(() => _imgService.Resize(request)); return Accepted(); } Такой подход обеспечивает быстрый отклик, поэтому кажется удачным решением. Но это не так. Количество запускаемых задач ничем не ограничено, поэтому всплеск нагрузки, который раньше «вешал» CPU, делает это снова — при этом всем отправляется ответ 202, сигнализирующий об успешном принятии запроса. Если происходит перезапуск процесса, э
23 · 1.6K ·
.
.NET Разработчик
День 2782. #ЗаметкиНаПолях Паттерн «Производитель-потребитель» c System.Threading.Channels. Продолжение Начало Производитель: конечная точка, передающая задачи Производитель просто выполняет запись. Единственный важный нюанс — как поступить, если канал переполнен; метод WriteAsync берёт это на себя: он завершается немедленно, если есть свободное место, или ожидает, пока потребитель освободит слот. [ApiController] [Route("api/[controller]")] public class ProcessController : ControllerBase { private readonly ChannelWriter<WorkItem> _writer; public ProcessController(Channel<WorkItem> ch) => _writer = channel.Writer; [HttpPost] public async Task<IActionResult> Enqueue( WorkItem item, CancellationToken ct) { // Ждёт, если канал полный await _writer.WriteAsync(item, ct); return Accepted(); } } Если при перегрузке системы вы предпочитаете отклонять запросы (что правильнее публичного API, для предотвращения атак), используйте TryWrite и возвращайте код 429: if (!_writer.TryWrite(item)) return StatusCode( StatusCodes.Status429TooManyRequests, "Сервис занят, попробуйте позже."); return Accepted(); TryWrite никогда не блокирует выполнение. Он возвращает false сразу же, как только канал оказывается заполненным, что позволяет преобразовать эту ситуацию в чёткий ответ 429 вместо ожидания. Выбор стратегии зависит от того, кто инициирует вызов: если это внутренняя пакетная задача, можно позволить ей подождать, а если публичная конечная точка — лучше сразу отклонить запрос. Потребитель: сервис, считывающий данные из канала Потребитель реализован в виде BackgroundService, т.е. запускается и останавливается вместе с приложением. Весь цикл обработки умещается в одну строку благодаря методу ReadAllAsync, который выдаёт элементы до тех пор, пока канал не будет закрыт: public class WorkConsumer : BackgroundService { private ChannelReader<WorkItem> _reader; private ILogger<WorkConsumer> _logger; public WorkConsumer( Channel<WorkItem
18 · 1.4K ·
.
.NET Разработчик
День 2783. #ЗаметкиНаПолях Паттерн «Производитель-потребитель» c System.Threading.Channels. Окончание Начало Продолжение Настройка и запуск нескольких потребителей Зарегистрируйте канал как синглтон, чтобы производитель и потребитель использовали один и тот же экземпляр, а затем добавьте столько экземпляров потребителя, сколько требуется для обеспечения нужной пропускной способности: builder.Services.AddSingleton(_ => Channel.CreateBounded<WorkItem>( new BoundedChannelOptions(100) { FullMode = BoundedChannelFullMode.Wait })); // 3 потребителя одного канала builder.Services.AddHostedService<WorkConsumer>(); builder.Services.AddHostedService<WorkConsumer>(); builder.Services.AddHostedService<WorkConsumer>(); Количество потребителей — регулятор уровня параллелизма. Один потребитель обрабатывает задачи строго последовательно, а 3 — до трёх одновременно. Настраивайте их количество с учётом возможностей последующего этапа обработки: если ProcessAsync обращается к БД, поддерживающей не более 10 одновременных операций записи, не стоит запускать 50 потребителей. Корректное завершение работы При остановке приложения в канале могут оставаться необработанные элементы. Если просто завершить процесс, они будут потеряны. Решение – закрыть канал записи при остановке и позволить потребителям обработать оставшиеся данные. За обработку сигнала отвечает небольшой фоновый сервис: public class ChannelCompleter : IHostedService { private readonly ChannelWriter<WorkItem> _writer; public ChannelCompleter(Channel<WorkItem> ch) => _writer = ch.Writer; public Task StartAsync(CancellationToken ct) => Task.CompletedTask; public Task StopAsync(CancellationToken ct) { _writer.Complete(); return Task.CompletedTask; } } После вызова Complete() метод WriteAsync будет генерировать исключение при попытке записи новым производителем, а метод ReadAllAsync каждого потребителя продолжит выдавать элементы до тех пор, пока буфер не опустеет, после чего цикл
14 · 1.3K ·
.
.NET Разработчик
День 2784. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков Если это ходит как утка и крякает как утка — значит, это утка. Применим утиную типизацию и заставим это работать в C# с помощью перехватчиков! Примечание: этот пост написан в образовательных целях, но вы смело можете использовать описанный подход в проде 😉 В TypeScript можно сделать вот так: class A { Do(): void { console.log("A.Do"); } } class B { Do(): void { console.log("B.Do"); } } function foo(a: { Do(): void }) { a.Do(); } Попробуем сделать что-то подобное в C#: public class A { public void Do() => Console.WriteLine("A.Do"); } public class B { public void Do() => Console.WriteLine("B.Do"); } public void Foo(??? a) => a.Do(); У классов A и B нет ничего общего: ни базового класса, ни общего интерфейса. Нужно придумать некую «форму» (обозначим её как ???) — нечто такое, что позволит успешно скомпилировать вызовы Foo(new A()) и Foo(new B()) и обеспечит вызов нужного метода Do() в каждом случае, при этом вообще не затрагивая сами классы A и B. Кроме того, запрещено использовать следующие подходы: - dynamic (хотя это и невероятно крутая штука); - аргумент типа object с последующим приведением типов через is или as; - модификацию классов A или B (например, добавление общего базового класса или интерфейса). Перехватчики Начиная с .NET 8, в C# появилась экспериментальная функция компилятора — перехватчики. Генератор исходного кода может создать метод, пометить его атрибутом [InterceptsLocation], указав конкретное место вызова в вашем коде, и компилятор незаметно перенаправит вызов на этот метод. Это происходит без каких-либо затрат ресурсов во время выполнения, так как всё разрешается на этапе компиляции. Обычно это реализуется через сочетание пользовательских атрибутов (например, DuckType и DuckShape), создаваемых генератором кода, и логики перехватчика: система находит все места вызовов и выполняет необходимые действия. Таким образом, с точки зрения использования нам всё равно потре
10 · 1.4K ·
.NET Разработчик
День 2785. #ЗаметкиНаПолях Обеспечиваем Изоляцию Тенантов с Помощью PostgreSQL Механизм защиты на уровне строк (Row-Level Security, RLS) в PostgreSQL добавляет проверку на стороне БД поверх фильтров запросов EF Core. Он контролирует операции чтения и записи при условии, что приложение подключается под ролью, не являющейся владельцем таблицы, и устанавливает идентификатор тенанта при каждом открытии соединения. Популярна рекомендация использовать глобальные фильтры запросов для реализации мультитенантности. EF Core добавляет условие WHERE tenant_id = @tenant к каждому генерируемому запросу, поэтому разработчикам не нужно помнить об этом предикате. Однако действие фильтра ограничено: SQL-команды, отправляемые через ExecuteSql, присоединённые сущности, а также запросы с вызовом IgnoreQueryFilters() остаются вне зоны его влияния. Требуется дополнительный уровень проверки, не зависящий от того, помнит ли каждый разработчик о первом механизме. В PostgreSQL такая возможность уже встроена благодаря RLS. Настройка правила в PostgreSql Защита на уровне строк (RLS) представляет собой предикат, который PostgreSQL автоматически добавляет к каждой команде, выполняемой для таблицы (за исключением ролей, имеющих привилегию обхода RLS). Приложение не может «забыть» об этом условии, т.к. оно даже не видит его. Суперпользователи, роли с атрибутом BYPASSRLS и владельцы таблиц обходят это ограничение. Во многих приложениях один и тот же пользователь БД выполняет миграции, обрабатывает запросы и является владельцем таблиц. Поэтому в этом примере приложение подключается под ролью app_user, которая не владеет объектами и имеет лишь необходимые права DML, тогда как миграции выполняются от имени отдельной роли-владельца. Такое разделение также предотвращает возможность выполнения команды DROP POLICY со стороны приложения. Вот пример политики для таблицы счетов (invoices): ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; ALTER TABLE invoices FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_
15 · 1.6K ·
.
.NET Разработчик
День 2786. #AI Пусть Copilot Поспорит с Вами Как разработчик или архитектор, вы ежедневно принимаете множество проектных решений. Вы выбираете Redis для кэширования, отдаете предпочтение REST вместо GraphQL и так далее. Приняв решение, вы движетесь дальше, но где-то на заднем плане звучит внутренний голос: «Стоит ли беспокоить коллегу и спрашивать его мнение?», «А вдруг я что-то упустил?», «Действительно ли это правильный выбор?» GitHub Copilot может помочь вам в этом с помощью новой команды: /spar. Что это? Команда /spar переключает Copilot из режима «помоги мне это создать» в режим «убеди меня, что это плохая идея». Вместо того чтобы просто принять ваш план и сгенерировать код, Copilot начинает ставить под сомнение ваши допущения, спрашивает о граничных случаях и указывает на компромиссы, которые вы могли не заметить. Примечание: это отличается от команды /plan, которая помогает разбить задачу на части. /spar исходит из того, что план уже есть, и стремится подвергнуть его стресс-тестированию, прежде чем вы приступите к реализации. Как пользоваться? Введите /spar в поле чата, а затем укажите, какое решение хотите подвергнуть критике. Вот несколько примеров из документации: Проверка архитектурного решения: /spar Я планирую использовать Redis в качестве уровня кэширования для API продукта. Проверь мой подход и укажи на возможные проблемы с масштабируемостью или согласованностью данных, которые я мог упустить. Сравнение вариантов реализации: /spar Помоги мне выбрать между REST и GraphQL для клиентского API. Задавай вопросы, ставь под сомнение мои допущения и порекомендуй подход, который лучше всего подойдет для приложения с мобильными клиентами. Анализ плана миграции: /spar Я переношу нашу базу данных на новый управляемый сервис с минимальным временем простоя. Найди слабые места в моём плане миграции и укажи на риски или граничные случаи, которые стоит учесть. Или проверка оптимизации производительности перед выпуском: /spar Я планирую использовать ленивую загр
11 · 1.6K ·
.
.NET Разработчик
День 2787. #ЗаметкиНаПолях #Middleware Что Такое Промежуточное ПО и Его Подводные Камни. Начало В этой серии постов разберём, что представляет собой промежуточное ПО, как написать собственный компонент и с какими неожиданными проблемами могут столкнуться разработчики. Создание промежуточного ПО Промежуточное ПО добавляется в файле Program.cs с помощью метода app.Use(): app.Use(async (ctx, next) => { Console.WriteLine($"Запрос: {ctx.Request.Path}"); await next(ctx); Console.WriteLine($"Ответ: {ctx.Response.StatusCode}"); }); Промежуточное ПО выполняется при каждом HTTP-запросе. Каждый запрос проходит через него на входе, а ответ — на выходе. Вызов next() играет ключевую роль: он обеспечивает переход к следующему компоненту в конвейере обработки. Если забыть его добавить, выполнение конвейера остановится, и все последующие компоненты промежуточного ПО не будут запущены. Недостаток подхода с app.Use() в использовании лямбда-выражения, что затрудняет тестирование. Более удачное решение — вынести логику в отдельный класс: public class RequestLoggingMiddleware { private readonly RequestDelegate _next; public RequestLoggingMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext ctx) { Console.WriteLine($"Запрос: {ctx.Request.Path}"); await _next(ctx); Console.WriteLine($"Ответ: {ctx.Response.StatusCode}"); } } Не забывайте внедрять next в конструктор, чтобы использовать его для вызова следующего компонента промежуточного ПО в конвейере. Затем зарегистрируйте этот компонент в файле Program.cs; вызов app.Use(…) можно удалить: app.UseMiddleware<RequestLoggingMiddleware>(); Если не зарегистрировать класс, промежуточное ПО никогда не выполнится. Порядок регистрации промежуточного ПО в Program.cs имеет значение. Если мы зарегистрируем RequestLoggingMiddleware первым, этот компонент первым обработает запрос, а ответ – последним: app.UseMiddleware<RequestLoggingMiddleware>(); app.UseSwaggerUI(opts => { opts
17 · 1.4K ·
.
.NET Разработчик
День 2788. #ЗаметкиНаПолях #Middleware Что Такое Промежуточное ПО и Его Подводные Камни. Окончание Начало Изменение ответа после начала его отправки В данном случае промежуточное ПО обновляет код ответа после возврата управления из вызова next(): public async Task InvokeAsync(HttpContext ctx) { Console.WriteLine($"Запрос: {ctx.Request.Path}"); await _next(ctx); // Изменение статуса и ответа ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await ctx.Response.WriteAsync( "<p>Эта страница недоступна</p>"); Console.WriteLine($"Ответ: {ctx.Response.StatusCode}"); } Это приводит к возникновению исключения: System.InvalidOperationException: StatusCode cannot be set because the response has already started. (StatusCode нельзя установить, так как отправка ответа уже началась) После того как отправка ответа началась, изменить код состояния или снова записать данные в ответ уже невозможно. Однако можно выполнить необходимые действия непосредственно перед началом отправки, воспользовавшись событием context.Response.OnStarting: public async Task InvokeAsync(HttpContext ctx) { Console.WriteLine($"Request: {ctx.Request.Path}"); ctx.Response.OnStarting(async() => { if (ctx.Response.HasStarted) return; // Изменение статуса и ответа ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await ctx.Response.WriteAsync( "<p>Эта страница недоступна</p>"); }); await _next(context); Console.WriteLine($"Ответ: {ctx.Response.StatusCode}"); } В методе OnStarting сначала проверяется свойство HasStarted, и, если оно уже имеет значение true, выполнение метода досрочно завершается. В противном случае в ответ выводится сообщение о том, что страница в данный момент недоступна. Удержание scoped-сервисов после завершения обработки запроса public class MyScopedService : IMyScopedService, IDisposable { public bool Disposed { get; private set; } public void Test() { _logger.LogInformation(
16 · 1.3K ·
.
.NET Разработчик
День 2789. #Оффтоп #AI Важно ли По-прежнему Качество Кода? Автор оригинала: Марк Симан Пока мы используем LLM как инструмент для генерации кода, за который в итоге отвечают люди, качество кода остается важным. Людям приходится проверять этот код, работать с ним, исправлять ошибки и т.п. Но что произойдёт, если люди перестанут участвовать в этом процессе? Если в будущем весь код будут писать LLM, будет ли иметь значение качество кода? Почему качество кода имеет значение Выражение «писать код в стиле YOLO (You Only Look Once)» означает создание кода без оглядки на его качество — практика, которая, откровенно говоря, преобладала десятилетиями. И всё же компании-разработчики, позволяющие своим сотрудникам так работать, в итоге сталкиваются с проблемами. Они накапливают столько технического долга, что больше не могут своевременно реагировать на запросы бизнеса. Важно уточнить: качество кода — это не то же самое, что качество ПО. Качество кода — это внутреннее свойство кодовой базы: то, как код структурирован, насколько он читаем и как легко его изменять. До сих пор все это сводилось к вопросам человеческого восприятия и мышления. Именно этому посвящена книга «Код, который умещается в голове». Код писали люди. Людям приходилось его редактировать. А что, если ситуация изменится? Языки программирования для LLM Представим будущее, в котором люди больше не пишут и не читают код. Важно ли в таком случае качество кода? Очевидно, что если люди перестанут работать с кодом, то все ограничения, связанные с особенностями человеческого мышления, потеряют актуальность. Полученный код может отличаться чрезмерно длинными методами, высокой цикломатической сложностью, невнятными именами переменных и сильной связностью компонентов. Более того, некоторые из этих понятий могут вообще утратить смысл. Само понятие «длинный метод» предполагает, что ПО вообще разбито на методы. Спойлер: в машинном коде их нет. В реальности я не ожидаю, что LLM будут генерировать машинный код напрямую — хот
10 · 1.4K ·
.NET Разработчик
День 2790. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании. 46. AutoMapper, метод расширения или операторы неявного приведения «Сравните использование AutoMapper, методов расширения и операторов неявного приведения для преобразования (маппинга) объектов в приложениях .NET. Обсудите преимущества и сценарии, наиболее подходящие для каждого из этих подходов. Приведите примеры реализации для каждого метода в минимальных API». Хороший ответ В приложениях .NET, особенно при использовании многоуровневой архитектуры, часто возникает необходимость преобразования объектов одного типа в другой. Для решения этой задачи популярны три способа: использование AutoMapper, методов расширения и операторов неявного приведения. У каждого из них есть свои преимущества и оптимальные сценарии применения. AutoMapper — библиотека, которая автоматически преобразует объекты одного типа в другой на основе заданных конфигураций маппинга. Её лучше всего использовать в проектах, где часто требуется выполнять сложные преобразования между типами. AutoMapper значительно сокращает объём шаблонного кода. Библиотека способна автоматически обрабатывать вложенные объекты, коллекции и сложные структуры без необходимости явно описывать правила для каждого поля, как показано в следующем примере: var builder = WebApplication.CreateBuilder(args); // Предположим, маппинги определены в Program.cs builder.Services.AddAutoMapper(typeof(Program)); var app = builder.Build(); app.MapGet("/customer/{id}", async (int id, IMapper mapper, DataContext db) => { Customer customer = await db.Customers.FindAsync(id); CustomerDto customerDto = mapper.Map<CustomerDto>(customer); return Results.Ok(customerDto); }); app.Run(); Методы расширения особенно полезны в сценариях сопоставления данных, когда требуется применить специфическую пользовательскую логику. Такие методы обеспечивают чёткий контроль над логикой пр
10 · 1.2K ·
.NET Разработчик
День 2791. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Часть 1 Большинство разработчиков используют лишь 20% возможностей SQL. SELECT, JOIN, GROUP BY — и на этом останавливаются. Однако у SQL есть и «второй уровень» — функции, позволяющие превратить страницу кода приложения или три отдельных запроса в одну лаконичную и понятную инструкцию. При этом они не являются новыми или экзотическими: они уже доступны в используемой вами БД. Замечание: запросы были протестированы в БД PostgreSQL. Большинство описанных функций поддерживаются и другими СУБД, хотя синтаксис может различаться. 1. Обобщённые табличные выражения (CTE) Сложный запрос, оформленный как единая инструкция, труден для чтения, а вносить в него изменения ещё сложнее. CTE позволяет разбить его на последовательные именованные этапы с помощью ключевого слова WITH. Каждый этап представляет собой временный именованный набор данных, который можно использовать в дальнейшем ходе запроса: WITH recent_shipments AS ( SELECT id, number, carrier, status, created_at FROM shipments WHERE created_at >= CURRENT_DATE - INTERVAL '30 days' ), shipment_details AS ( SELECT rs.number, rs.carrier, rs.status, COUNT(si.id) AS total_items, SUM(si.quantity) AS total_quantity FROM recent_shipments rs LEFT JOIN shipment_items si ON rs.id = si.shipment_id GROUP BY rs.number, rs.carrier, rs.status ) SELECT number, carrier, status, total_items, total_quantity FROM shipment_details ORDER BY total_quantity DESC; Здесь 2 именованные части: - recent_shipments выбирает данные об поставках за последние 30 дней; - shipment_details использует этот результат, присоединяя к нему данные о товарных позициях и выполняя агрегацию (подсчёт количества и объёмов). В итоговом операторе SELECT обращение ко второму CTE происходит так же, как к обычной таблице. В результате получается запрос, который читается сверху вниз, в отличие от подхода с использованием вложенных подзапросов, которые приходится разби
28 · 1.2K ·
.
Фотография
нажмите — покажем
🔍Тестовое собеседование с Senior C# разработчиком уже завтра 22 сентября(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle C# разработчика. Как это будет: 📂 Александр Моргунов, старший C# разработчик с опытом 7+ лет в Европейских высоконагруженных сервисах, будет задавать реальные вопросы и задачи разработчику-добровольцу 📂 Александр будет комментировать каждый ответ респондента, чтобы дать понять, чего от вас ожидает собеседующий на интервью 📂 В конце можно будет задать любой вопрос Александру Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для C# разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы. Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_csharp_bot Реклама. О рекламодателе.
9 · 1.5K ·
.
.NET Разработчик
День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Часть 2 1-3 4. GROUPING SETS, ROLLUP и CUBE Для отчёта часто требуется получить сразу несколько уровней агрегации: итоговые значения по перевозчику и статусу, промежуточные итоги по перевозчику и общий итог. Наивный подход предполагает объединение нескольких запросов с помощью оператора UNION ALL. Конструкции GROUPING SETS, ROLLUP и CUBE позволяют получить все эти уровни в рамках одного запроса: SELECT carrier, status, COUNT(*) AS shipment_count, SUM(si.quantity) AS total_quantity FROM shipments s LEFT JOIN shipment_items si ON s.id = si.shipment_id GROUP BY GROUPING SETS ( (carrier, status), -- по поставщику и статусу (carrier), -- подытог по поставщику (status), -- подытог по статусу () -- общий итог ); SELECT carrier, status, DATE_TRUNC('month', created_at) AS month, COUNT(*) AS shipment_count FROM shipments GROUP BY ROLLUP (carrier, status, DATE_TRUNC('month', created_at) ); Первый запрос формирует группировки, которые вам нужны: по перевозчику и статусу, только по перевозчику, только по статусу, а также пустую группу () для получения общего итога. Оператор ROLLUP во втором запросе — это сокращённая запись для иерархических промежуточных итогов: сначала по перевозчику, затем по перевозчику и статусу, далее по перевозчику, статусу и месяцу — и наконец общий итог. Оператор CUBE создает все возможные комбинации столбцов. Один запрос заменяет 4, а БД вычисляет уровни за один проход, вместо того чтобы многократно сканировать таблицу. Эти средства являются частью стандарта SQL и поддерживаются в PostgreSQL, SQL Server и Oracle. 5. Предложение FILTER в агрегатных функциях Часто возникает необходимость подсчитать количество или сумму только для тех строк, которые удовлетворяют определённому условию, и вывести эти результаты рядом друг с другом. Предложение FILTER применяет условие к конкретной агрегатной функции, благодаря чему каждая и
24 · 1.4K ·
.
.NET Разработчик
День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Часть 3 1-3 4-7 8. Вычисляемые (генерируемые) столбцы Если значение столбца всегда формируется на основе данных из других столбцов, его вычисление в коде приложения чревато ошибками, поскольку формулу приходится учитывать в каждом месте, где он используется. Использование генерируемого столбца позволяет перенести эту формулу в определение таблицы, благодаря чему БД вычисляет и сохраняет значение автоматически. CREATE TABLE shipments.shipping_costs ( id SERIAL PRIMARY KEY, shipment_id UUID NOT NULL, base_rate DECIMAL(10,2) NOT NULL, weight_kg DECIMAL(8,2) NOT NULL, distance_km DECIMAL(10,2) NOT NULL, fuel_surcharge_rate DECIMAL(5,4) NOT NULL DEFAULT 0.15, -- Вычисляемые столбцы weight_cost DECIMAL(10,2) GENERATED ALWAYS AS (weight_kg * 2.50) STORED, distance_cost DECIMAL(10,2) GENERATED ALWAYS AS (distance_km * 0.85) STORED, fuel_surcharge DECIMAL(10,2) GENERATED ALWAYS AS (base_rate * fuel_surcharge_rate) STORED, total_cost DECIMAL(10,2) GENERATED ALWAYS AS ( base_rate + (weight_kg * 2.50) + (distance_km * 0.85) + (base_rate * fuel_surcharge_rate) ) STORED, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), FOREIGN KEY (shipment_id) REFERENCES shipments(id) ); Значение каждого столбца, определённого как GENERATED ALWAYS AS (…) STORED, вычисляется на основе других столбцов при каждой вставке или обновлении строки. Столбец total_cost суммирует базовый тариф, стоимость с учётом веса, стоимость с учётом расстояния и топливный сбор; при этом невозможно забыть пересчитать его значение, так как в этот столбец нельзя записать данные напрямую. Ключевое слово STORED означает, что значение сохраняется физически (и может быть проиндексировано), а не вычисляется заново при каждом чтении. Примечание: в SQL Server такие столбцы называются вычисляемыми (computed) и описываются как total_cost AS (…), а для сохранения значения используется ключевое с
17 · 1.4K ·
.
.NET Разработчик
Фотография
нажмите — покажем
День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конференция DotNext 2026. И от ребят из RadioDotNet поступило предложение перед открытием конференции совершить пробежку. 5 км. в прогулочном темпе. Приглашаются все желающие, даже те, кто не участвует в конференции: - необычная движуха; - популяризация бега среди сидячих программистов; - посмотрим город (многие будут из других городов); - пообщаемся. Конференция стартует в 10 утра, так что собираемся в 7:30 у гостиницы «Холидей Инн Москва Сокольники», улица Русаковская, 24. Побежим по парку «Сокольники», примерный маршрут ниже. До встречи!
6 · 1.3K ·
.
.NET Разработчик
День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиционированием Copilot как «ИИ-напарника», которое использует GitHub в маркетинговых целях, в том, что это определение верно, но неполно. Программист, впервые видящий вашу кодовую базу, — это всё ещё человек. Он может спросить: «Погоди, а вы здесь используете Service Bus именно так?» — перед тем, как уверенно сгенерировать три файла с неверными привязками. Copilot же будет уверенно генерировать код, создавая как безупречный идиоматичный код для .NET, так и галлюцинированный метод, которого не существует ни в одной версии SDK. Лучше воспринимать Copilot как джуна, который идеально помнит публичный код с GitHub, но совершенно не знает вашего конкретного стека, стандартов кодирования, или что предложенный им метод был объявлен устаревшим ещё в 2022 году. Обеспечьте его контекстом. Задайте ограничения. Проверяйте всё, что взаимодействует с внешними системами. Именно такой подход избавит вас от лишних затрат времени на отладку. Режимы работы Copilot 1. Автодополнение. Вы печатаете, а он предлагает подсказку, которую можно принять нажатием клавиши Tab. Это отличное решение для шаблонного кода: конфигурации сущностей EF Core, запросов LINQ, добавления атрибутов к контроллерам ASP.NET Core и т.п. При работе с повторяющимися паттернами это действительно ускоряет процесс. Недостаток: задачи, требующие специфических знаний SDK; асинхронный код, где важна правильная передача CancellationToken; и всё, что касается безопасности. В таких случаях действовать нужно более осознанно. 2. Встроенный чат (Ctrl+I в VS Code, Alt+/ в Visual Studio) — возможность отправить точечный промпт непосредственно в редакторе. Это целевой инструмент с ограниченной областью действия. Чаще всего используется для рефакторинга конкретного метода, преобразования синхронного кода в асинхронный, генерации XML-документации или чтобы выяснить, что именно делает код и зачем он это делает. 3. Окно чата (Ctrl+Alt+I в VS Cod
7 · 1.1K ·
.
.NET Разработчик
День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три «S» в составлении промптов Лучшие промпты обладают тремя характеристиками: - Simple (простые) - одна задача за раз; - Specific (конкретные) - явно указаны фреймворк, версия, паттерн и ограничения; - Short (краткие) - достаточно лаконичные, чтобы полезная информация не терялась в «шуме». Антипаттерн — «мега-промпты», пытающиеся решить всё за один раз. Например: «Создай сервис управления заказами с интеграцией Service Bus, EF Core, механизмом повторных попыток Polly, структурированным логированием и полным набором тестов». Такой промпт выдаст результат, который технически охватывает все эти области, но в большинстве из них будет содержать ошибки. Разбейте задачу на части: «Создай интерфейс IOrderService с методами для создания, получения и отмены заказов. Используй асинхронность повсеместно. Возвращай Result<T> — не выбрасывай исключения при передаче данных между компонентами». Это даст результат, который действительно можно использовать. Ошибка, которая сводит всё на нет, — расплывчатый контекст. Если Copilot не знает, что вы используете .NET 10, он может предложить паттерны для .NET 6. Если он не в курсе, что у вас Polly v8, то предложит синтаксис версии 7. Конкретика в промпте ничего не стоит, а вот исправление неверных версий SDK — да. Контекстные переменные Здесь кроется реальный разрыв в продуктивности между разработчиками, которые уделили время правильному изучению Copilot, и всеми остальными. - #file: позволяет указать Copilot конкретные файлы, вместо того чтобы надеяться, что он сам догадается о контексте на основе открытого в редакторе файла. Добавление в промпт ссылки на copilot-instructions.md (о нём позже) сильно меняет дело. Copilot генерирует код, соответствующий вашим текущим паттернам, а не придумывает новые. - #terminal используется редко, хотя очень полезна. Если сборка завершается ошибкой, передайте вывод терминала прямо в запрос, вместо того чтобы пер
14 · 1.1K ·
.
.NET Разработчик
День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продолжение Подводные камни, которые только отнимают время 1. Использование комментариев для вызова подсказок Привычка писать комментарии, чтобы спровоцировать генерацию кода, осталась у многих разработчиков ещё с ранних времен Copilot. Перестаньте так делать. Используйте встроенный чат. Код, сгенерированный по комментарию, часто оставляет в кодовой базе «мёртвые» комментарии-заготовки, если предложенный вариант оказывается неудачным. 2. Доверие к уверенным «галлюцинациям» Хуже всего при работе с Copilot принять код, который выглядит правильным и компилируется, но содержит скрытую ошибку. Выдуманные пакеты NuGet легко заметить, а вот неверные сигнатуры методов, которые компилируются благодаря совпадению типов параметров, — гораздо сложнее. Любой код, затрагивающий Azure SDK, логику безопасности или внешние интеграции, перед выпуском должен вручную сверяться с официальной документацией. 3. Секреты в чате Строкам подключения, API-ключам и данным клиентов не место в промпте. Если Copilot нужно понять структуру конфигурации, вставьте саму структуру, заменив реальные данные заглушками. Если вы решили начать в понедельник Не пытайтесь изменить всё и сразу. Самое эффективное действие с максимальной отдачей — это добавление файла copilot-instructions.md в ваш самый активный репозиторий. Это займет всего час, но сразу повысит продуктивность всех разработчиков, работающих с этим репозиторием, и инициирует полезное обсуждение ваших реальных стандартов разработки. Затем проведите сеанс парного программирования с коллегой, который до этого использовал только автодополнение кода, и покажите ему возможности чата и встроенного чата. Понаблюдайте, как меняется его стиль составления промптов, когда для конкретной задачи используется подходящий инструмент взаимодействия. После того как эти приёмы войдут в привычку, попробуйте режим Agent на задачах с низким уровнем риска. Например, при рефакторин
14 · 1.1K ·
.
.NET Разработчик
День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время назад я выложил пост от Steven Giesel о реализации утиной типизации в C# с помощью перехватчиков (interceptors). Тогда это был лишь прототип с множеством недоработок. Теперь же это полноценная библиотека: IfItQuacks. TL;DR Объявите интерфейс, пометьте метод атрибутом [DuckTyped] и передавайте в него любой подходящий объект — без базового класса, без явной реализации интерфейса и без каких-либо атрибутов на самих типах: using IfItQuacks; public interface INamed { string Name { get; } } public class Person { public string Name => "Steven"; } public class Mallard { public string Name => "Donald"; } public partial class Greeter { [DuckTyped] public string Greet( INamed first, INamed second, string greeting = "Hello") => $"{greeting}, {first.Name} and {second.Name}!"; } Console.WriteLine(new Greeter() .Greet(new Person(), new Mallard())); // Hello, Steven and Donald! Ни Person, ни Mallard не знают об INamed. Генератор кода на этапе компиляции проверяет наличие у обоих типов свойства Name и перенаправляет вызов через небольшой сгенерированный адаптер. Никакой рефлексии, никакого dynamic. Если типы несовместимы, анализатор выдаст ошибку. Можно также явно привести объекты к интерфейсу, чтобы хранить их: List<INamed> names = [ Duck.As<INamed>(new Person()), Duck.As<INamed>(new Mallard()) ]; Подробности можно найти в документации. Далее кратко основные моменты. Анонимные типы — это «утки» Анонимные типы отлично «крякают» (по сути, то же самое, что предлагает TypeScript): new Greeter().Greet( new { Name = "Steven" }, new { Name = "Donald" }); По сути, можно писать свои тесты с «мгновенными моками»: public interface IPerson { string Name { get; } int Age { get; } } public partial class AgeChecker { [DuckTyped] public bool IsAdult(IPerson person) => person.Age >= 18; } [Test] public void Should_detect_adults() { var checker = new AgeChecker(
10 · 1.1K ·
.NET Разработчик
День 2799. Конференция DotNext 2026. Часть 1 25 и 26 сентября в Москве прошла очередная конференция DotNext. Вернулся и делюсь впечатлениями. Я уже писал, какие доклады хочу посетить, однако, по ходу несколько раз передумал. На этот раз решил посетить несколько воркшопов, т.к. они не записываются на видео, а доклады можно посмотреть в записи позже. Воркшоп отличается от доклада тем, что на нём разбирается конкретный практический пример, а аудитория активно участвует, задавая вопросы или предлагая решения. Раньше они меня пугали своей продолжительностью. Доклады длятся по часу, а воркшоп – это обычно от полутора часов и дольше. ИЧСХ, в начале каждого воркшопа любой докладчик считает своим долгом упомянуть, что времени у нас очень мало 😉. Хочу признать, что я много потерял, не посещая воркшопы раньше. Это отличный опыт практики и командной работы. Первым был воркшоп от Андрея Цветцих (они с братом Денисом завсегдатаи различных конференций) «Паттерны асинхронного взаимодействия в распределенных системах». Мы разбирали пример реализации сервиса уведомлений (например, по e-mail) о событиях. Как настроить передачу информации в сервис рассылки? Какие данные передавать? Как реализовать гарантии доставки и избежать отправки дублирующих сообщений? Возможно ли реализовать доставку «exactly-once»? Интересно, что здесь нет единственно верного решения. Каждое решение – компромисс со своими плюсами и минусами. После воркшопа я немного поговорил с Андреем. - Вы выступаете почти на каждой конференции DotNext и ещё на многих других. Что мотивирует постоянно готовить доклады или воркшопы? - Мне кажется мотивация комплексная. Когда ты что-то хорошо знаешь, хочется поделиться, двигать индустрию. Я не только разработчик и спикер, я ещё и пользователь систем других. Я хочу, чтобы мне не приходило по сто уведомлялок. Хочу, чтобы заказы, которые делаю на разных сайтах, доходили. И так далее. Это желание поделиться тем, что ты хорошо знаешь. Попутно есть мотивация в том, что приятно просто
6 · 1K ·
.
Фотография
нажмите — покажем
Фотография
нажмите — покажем
Фотография
нажмите — покажем
Фото 3 (с) Анатолий Кулаков
11 · 1.2K ·
Архив по месяцам
сентябрь 2026август 2026июль 2026июнь 2026май 2026апрель 2026март 2026февраль 2026январь 2026декабрь 2025ноябрь 2025октябрь 2025сентябрь 2025август 2025июль 2025июнь 2025май 2025апрель 2025март 2025февраль 2025январь 2025декабрь 2024ноябрь 2024октябрь 2024сентябрь 2024август 2024июль 2024июнь 2024май 2024апрель 2024март 2024февраль 2024январь 2024декабрь 2023ноябрь 2023октябрь 2023сентябрь 2023август 2023июль 2023июнь 2023май 2023апрель 2023март 2023февраль 2023январь 2023декабрь 2022ноябрь 2022октябрь 2022сентябрь 2022август 2022июль 2022июнь 2022май 2022апрель 2022март 2022февраль 2022январь 2022декабрь 2021ноябрь 2021октябрь 2021сентябрь 2021август 2021июль 2021июнь 2021май 2021апрель 2021март 2021февраль 2021январь 2021декабрь 2020ноябрь 2020октябрь 2020сентябрь 2020август 2020июль 2020июнь 2020май 2020апрель 2020март 2020февраль 2020январь 2020декабрь 2019ноябрь 2019октябрь 2019сентябрь 2019август 2019июль 2019июнь 2019май 2019апрель 2019март 2019февраль 2019январь 2019

Открытая публичная лента из поискового индекса ChatCrawler — «Google по публичному Telegram»; обновляется по мере обхода площадки. Время — UTC.

Только публичный контент, официальный API Telegram. О проекте · Вопросы · Чего мы не делаем · Убрать страницу из выдачи · Каталог · Поиск · Как мы считаем