.NET Разработчик
6.5K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1956. #Карьера
Ведите Дневник Разработчика. Продолжение

Начало. Зачем?

Как?

1. Настройка
Выберите место. Подойдёт любой текстовый редактор, даже редактор кода и файл markdown (только добавьте его в .gitignore). Чем проще этот этап, тем лучше. Лучше использовать рабочую машину, т.к. возможно понадобится вставлять фрагменты кода.
Дневник — ваш личный документ, в котором можно систематизировать и обработать мысли. Текст должен быть понятным и читабельным для вас. Стремитесь к формату списка дел, а не к сочинению. Не зацикливайтесь на форматировании, организации, формулировках, опечатках. Если вы можете ориентироваться в тексте – всё хорошо!
Для начала попробуйте разбивать текст по дням. Каждый день записывайте свою цель (можно разбить её на задачи) и краткое резюме. Кроме того, у вас могут быть разделы для заметок, полезные ссылки, просто мысли о будущем и т.п. Главное – дневник можно настраивать под себя.

2. Прежде чем писать код
В начале каждого рабочего сеанса (спринта, рабочего дня, сессии «помидора») определите цель сеанса, даже если она кажется очевидной. Чего достичь сегодня? Есть ли ясная и чётко определённая задача по написанию кода, которую необходимо выполнить? Нужно ли что-то изучить в кодовой базе? Нужно проверить гипотезу? Как уменьшить двусмысленность?
Иногда будет просто, иногда сложно определиться с целями, иногда будет жгучее желание побыстрей начать писать код. Если вы чувствуете дискомфорт при формулировании своих мыслей, возможно, вы недостаточно ясно представляете своё решение. Отлично! Именно поэтому вы и ведёте дневник.

3. Пока пишете код
- Застрял – запиши
Если обнаружите, что обдумываете проблему дольше минуты, запишите свои мысли в дневник. Если вы застряли в поиске бага, запишите всё, что пробовали до сих пор. Так будет легче организовать свои мысли и обратиться за помощью, если она вам понадобится.

- Разобрался - запиши
Запишите решение или логику, которая помогла решить проблему, или в чём была ошибка. Не судите себя, просто опишите как дела. Это будет полезно для определения того, что работает для вас в долгосрочной перспективе.

- Выбросьте идеи, вопросы и задачи из головы
Работая над кодом, вы естественным образом будете генерировать идеи и вопросы. В большинстве случаев не стоит прерывать работу ради них. Записывание этих задач поможет разгрузить мозг и сосредоточиться на коде. Если задача чётко определена, вы можете даже написать TODO прямо в коде. Но большинство идей не настолько детализированы, поэтому лучше записать их в дневник. Для них даже могут быть отдельные разделы «Вопросы» или «Идеи».

4. Когда закончили задачу
В конце сеанса кодирования запишите, как всё прошло. Помните, это только для вас. Будьте откровенны. Смогли ли выполнить поставленную задачу? Было ли что-то сложнее, чем вы ожидали? Вы неправильно оценили сложность задачи? Можете ли вы определить, что вас расстраивало? Хотели бы вы сделать что-нибудь по-другому, когда вернётесь к этому завтра? Где-то застряли? Сделали ли что-нибудь, чем гордитесь? Короче говоря, сделайте свою собственную ретроспективу своего дня.

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

Источник:
https://stackoverflow.blog/2024/05/22/you-should-keep-a-developer-s-journal/
👍18
День 1957. #Карьера
Ведите Дневник Разработчика. Окончание

Начало. Зачем?
Продолжение. Как?

Ключи к успеху

1. Создаём привычку
Возьмите за привычку писать в начале и в конце каждого сеанса кодирования. Держите дневник поблизости - всегда на расстоянии одной вкладки. У вас должна быть возможность проверить свои заметки или сразу сделать дополнительные заметки. Ваш дневник становится всё более ценным, чем дольше вы его ведёте, поскольку начинают проявляться закономерности.

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

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

4. Учимся на своём опыте
В конце спринта, месяца или квартала выделите немного времени для просмотра своего дневника. Не нужно читать мелкие детали, обратите внимание на то, что вызывало трудности и что помогало их решать, и чего вы достигали каждый день.
Это полезно для:
- Понимания, какой объём работы вы способны выполнить;
- Встреч 1 на 1 с начальником;
- Помощи коллегам, чтобы помочь повысить уровень вашей команды;
- Документирования ваших достижений для будущих разговоров о карьерном росте.
Запишите выводы, полученные в результате размышлений, в том же журнале, например, в разделе «Выводы из этого спринта/проекта/квартала». Опять же, это заставит вас задуматься о том, что вы делаете. Подумайте о том, чтобы поделиться своими знаниями с командой и руководителем. Если у вас возникли проблемы с концепцией/инструментом/частью кодовой базы, скорее всего, у ваших коллег (особенно новичков) тоже.

5. Бережём внимание
Написание текста параллельно с обычной работой по программированию может показаться совершенно другой работой, но, если вы будете придерживаться этого, это станет вашей второй натурой и сэкономит вам массу времени. Гораздо лучше запутаться, описывая мысли в начале проекта, чем когда вы в середине проекта написали кучу кода в десятках файлах. Потратьте пять минут, чтобы спланировать свой день сейчас вместо того, чтобы бегать по кругу позже.

Хороший дневник разработчика должен делать три вещи:
1) Подтолкнуть к обдумыванию своих идей и планированию каждого дня, прежде чем начинать программировать.
2) Заставить более внимательно относиться к своим успехам и трудностям, чтобы вы могли повысить свой уровень.
3) Очищать ваш разум от всего, что мешает кодированию.

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

Источник: https://stackoverflow.blog/2024/05/22/you-should-keep-a-developer-s-journal/
👍9
День 1958. #ЧтоНовенького #CSharp13
Типы-Расширения
Начиная с C# 3, методы расширения позволяют добавлять методы к базовому типу, даже если вы не можете изменить его код. LINQ — пример набора методов расширения IEnumerable<T>. Методы расширения LINQ выглядят так, как если бы они были методами экземпляра базового типа.

C# 13 идёт дальше, добавляя типы-расширения. Это новая разновидность типов языка, которая предоставляет элементы расширения для базового типа. Расширение включает методы, свойства и другие члены, которые могут быть как экземплярными, так и статическими. Типы-расширения экземпляра не могут хранить состояние, например, не могут включать экземплярные поля, но могут получать доступ к состоянию базового типа.

Виды
1. Неявные
Применяются ко всем экземплярам базового типа так же, как методы расширения.
2. Явные
Применяются только к экземплярам базового типа, которые были преобразованы в тип явного расширения (по аналогии с явной реализацией интерфейсов).

Пусть у нас есть базовые типы и нет доступа к изменению их кода:
public class Person()
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Company Company { get; init; }
}
public class Company()
{
public string Name { get; init; }
public List<Team> Teams { get; init; }
}
public class Team()
{
public string TeamName { get; init; }
public Person Lead { get; init; }
public IEnumerable<Person> Members { get; init; }
}

Небольшой код LINQ поможет определить, является ли человек лидом. Но мы не хотим писать его каждый раз, поэтому можно написать метод расширения и контролировать доступ к нему через пространства имён. Или можно использовать неявный тип-расширение и предоставить свойство IsLead всем экземплярам Person:
public implicit extension PersonExtension for Person
{
public bool IsLead
=> this.Company
.Teams
.Any(team => team.Lead == this);
}

// Использование
if (person.IsLead) { … }


Явные расширения позволяют предоставлять дополнительные возможности конкретным экземплярам типа. Например, чтобы узнать, какие команды возглавляет человек, явное расширение может предоставлять свойство Teams только лидам (через приведение экземпляра Person к типу Lead):
public explicit extension Lead for Person
{
public IEnumerable<Team> Teams
=> this.Company
.Teams
.Where(team => team.Lead == this);
}

Как неявные, так и явные типы-расширения поддерживают и статические, и экземплярные члены. Вариант использования статических членов — предоставить значения по умолчанию, специфичные для вашего сценария. В данном случае у нас одна компания, и указывать её каждый раз при создании человека неудобно:
public implicit extension CompanyExtension for Company
{
private static Company company
= new Company("C# Design");

public static Person CreatePerson(
string firstName, string lastName)
=> new(firstName, lastName, company);
}

// Использование
var person = Company
.CreatePerson("Jon", "Smith");

// … добавляем ещё людей и команды

if (person.IsLead)
{
Lead lead = person;
PrintReport(lead.Teams);
}


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

Источник: https://devblogs.microsoft.com/dotnet/dotnet-build-2024-announcements/
👍24
День 1959. #ЗаметкиНаПолях
Что Такое Идемпотентность в Программных Системах? Начало
Вы часто встретите термин «идемпотентный» в ПО, особенно при разработке распределённых облачных систем. На первый взгляд эта концепция кажется простой для понимания, но важно знать тонкости идемпотентности, если вы хотите, чтобы ваши системы были масштабируемыми и надёжными.

Что это?
Возьмём для примера пульт ДУ от телевизора, который наверняка валяется у вас в гостиной. На нём обычно есть кнопки вкл./выкл., кнопки перемещения по каналам вперёд/назад и кнопки с цифрами, переключающие на конкретный канал. Так вот, независимо от того, сколько раз вы будете нажимать кнопку с цифрой канала, например, 5, вы всегда будете попадать на 5й канал (отбросим для простоты возможность включения двузначных каналов). Поэтому эта операция идемпотентна. Однако, нажатие кнопки вкл./выкл. или кнопок перемещения по каналам вверх/вниз каждый раз будет приводить к разному результату, в зависимости от текущего состояния системы, и многократное нажатие приведёт в систему в другое (неизвестное) состояние. Поэтому эта операция не идемпотентна.

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

Сети по своей сути ненадёжны, поэтому большинство распределённых систем не могут гарантировать однократную доставку или обработку сообщений, даже при использовании брокера сообщений, вроде RabbitMQ, Azure Service Bus или Amazon SQS. Большинство брокеров предлагают доставку «хотя бы раз», полагаясь на то, что логика повторяет обработку столько раз, сколько необходимо, пока не будет подтверждено, что обработка сообщения завершена.

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

Почему бы не сделать все обработчики идемпотентными?
Это сложно. Допустим, нужно создать нового пользователя в БД и опубликовать событие UserCreated, чтобы другие части системы знали, что произошло. Псевдокод будет примерно таким:
Handle(CreateUser message)
{
DB.Store(message.User);
Bus.Publish(new UserCreated());
}

Теоретически - ОК, но что, если брокер сообщений не поддерживает транзакции? (Спойлер: большинство не поддерживают!) Если между этими двумя строками кода произойдет сбой, запись в базе данных будет создана, но сообщение UserCreated не будет опубликовано. При повторной отправке сообщения будет записана новая запись в БД, а затем сообщение будет опубликовано.

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

А если изменить порядок, и сначала отправлять сообщение, а потом сохранять пользователя? Теперь у нас обратная проблема. Мы создаём призрачное сообщение — объявление остальной части системы о событии, которое на самом деле не произошло. Если кто-нибудь попытается найти этого пользователя, то не найдёт, поскольку он не был создан. Но другие процессы продолжат работать на основе этого сообщения, возможно, выставляя счета, но не доставляя заказов!

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

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

Источник:
https://particular.net/blog/what-does-idempotent-mean
👍21
День 1960. #ЗаметкиНаПолях
Что Такое Идемпотентность в Программных Системах? Окончание

Начало

Как достичь идемпотентности?
Паттерн «Исходящие» (Outbox) обеспечивает согласованность, подобную базе данных, между операциями обмена сообщениями (как получением входящего сообщения, так и отправкой исходящих сообщений) и изменениями бизнес-данных в базе. Опираясь на транзакцию БД, мы превращаем гарантию доставки «хотя бы раз» брокера сообщений в гарантию ровно одной обработки.

Для реализации паттерна Outbox логика обработки сообщений разделена на две фазы:
1. Фаза обработки сообщения
Мы не отправляем исходящие сообщения немедленно брокеру сообщений, а храним их в памяти до завершения работы обработчика сообщений. На этом этапе мы сохраняем все накопленные исходящие сообщения в таблицу БД, используя ту же транзакцию, что и для записи бизнес-данных, и Id сообщения в качестве первичного ключа.
2. Фаза отправки
Все исходящие сообщения физически отправляются брокеру сообщений. Если всё идет хорошо, исходящие сообщения отправляются, а входящие обрабатываются. Но здесь ещё возможно возникновение проблемы и отправка не всех сообщений, что вынудит нас повторить попытку. Так возникнут дублирующие сообщения, но так и задумано.

Паттерн «Исходящие» связан с паттерном «Входящие», поэтому при обработке любого повторяющегося сообщения (или повторной попытке обработки сообщения, которое не удалось выполнить на этапе отправки), сначала извлекаются данные из таблицы исходящих сообщений. Если такое сообщение существует, это означает, что оно уже успешно обработано, надо пропустить этап обработки, и перейти к этапу отправки. Если сообщение является дубликатом, и исходящие сообщения уже отправлены, то и этап отправки также можно пропустить. На псевдокоде это выглядит так:
 
var message = PeekMessage();
// проверка на дубликат
var outbox = DB.GetOutboxData(message.Id);
// обработка
if(outbox == null)
{
using(var trans = DB.StartTransaction())
{
var result = ExecuteHandler(message);
outbox = new OutboxData(message.Id, result);
DB.StoreOutboxData(outbox);
trans.Commit();
}
}

// отправка
if(!outbox.IsDispatched)
{
Bus.DispatchMessage(outbox);
DB.SetAsDispatched(message.Id);
}

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

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

Гораздо проще использовать такую инфраструктуру, как Outbox, которая может воспользоваться транзакцией локальной базы данных, уже используемой для хранения бизнес-данных, и использовать эту транзакцию для обеспечения согласованности между операциями входящего/исходящего обмена сообщениями и бизнес-данными, хранящимися в БД.

Источник: https://particular.net/blog/what-does-idempotent-mean
👍14👎2
День 1961. #ЗаметкиНаПолях
Ожидает ли HttpClient всё Тело Ответа?
Если вы вызываете HttpClient.GetAsync или HttpClient.PostAsync, а затем ожидаете результат, ожидается получение только заголовков или всего тела ответа?

Сервер
Создадим простой сервер, который отправляет ответ с заголовком и телом. Тело будет отправляться частями, чтобы мы могли видеть, когда оно будет получено.
app.MapGet("/", async ctx =>
{
await ctx.Response.WriteAsync("1");
await Task.Delay(500);
await ctx.Response.WriteAsync("2");
await Task.Delay(500);
await ctx.Response.WriteAsync("3");
await ctx.Response.CompleteAsync();
});

Таким образом, весь запрос занимает не менее 1 секунды. Напишем простой вызов этой конечной точки.

Клиент
using var client = new HttpClient();

// измерим время до получения ответа
var sw = Stopwatch.StartNew();
await client.GetAsync("https://localhost:5001");
Console.WriteLine(
$"Время отклика: {sw.ElapsedMilliseconds}мс");

Если выполнить этот код, мы увидим, что время ответа составляет около 1 секунды. Таким образом, await ожидает получения всего тела:
Время отклика: 1088мс

Теперь посмотрим, как можно не ждать тела. Используем опцию HttpCompletionOption.ResponseHeadersRead:
var sw = Stopwatch.StartNew();
await client.GetAsync("https://localhost:5001",
HttpCompletionOption.ResponseHeadersRead);
Console.WriteLine(
$"Время отклика: {sw.ElapsedMilliseconds}мс");

Мы добавили флаг в вызов GetAsync. Теперь время отклика значительно меньше:
Время отклика: 78мс

Таким образом await ожидает только получения заголовков, и вы можете исследовать их сразу, как они будут получены. Тело всё ещё принимается в фоновом режиме. Если вы хотите дождаться получения тела, можно использовать метод ReadAsStringAsync:
using var response = 
await client.GetAsync("https://localhost:5001",
HttpCompletionOption.ResponseHeadersRead);
var body = await response.Content.ReadAsStringAsync();

Важно! Мы здесь используем using var response. Объект response продолжит удерживать ресурсы после await, поэтому необходимо удалять его как можно скорее.

Итого
Использование HttpCompletionOption.ResponseHeadersRead даёт выигрыш в производительности и памяти, и вы всё равно можете прочитать тело, если захотите. Но будьте осторожны с объектом ответа, так как он может по-прежнему удерживать ресурсы, что усложняет код. Так что это не должно быть выбором по умолчанию, если в этом нет необходимости.

Источник: https://steven-giesel.com/blogPost/e2c3bcba-4f81-42b0-9b25-060da5e819fa/does-an-httpclient-await-the-header-and-the-body
👍38
День 1962. #УрокиРазработки
Уроки 50 Лет Разработки ПО


Урок 11. Люди не просто «собирают» требования
Когда говорят «сбор требований», можно подумать о сборе цветов или грибов. С требованиями всё не так просто. Они редко существуют в сознании пользователей в сформированном виде, готовом для передачи бизнес-аналитику. Термин «выявление требований» точнее описывает, как разработчики сотрудничают с заинтересованными сторонами, чтобы понять, как те работают, и определить, какие возможности должна предоставлять будущая программная система. Задача бизнес-аналитика во время сбора информации - задавать правильные вопросы, стимулируя обсуждение и побуждать заинтересованные стороны выходить за рамки поверхностных и очевидных решений. При этом он не просто записывает всё, что ему говорят, а выявляет детали, помогая участникам структурировать знания.

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

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

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

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

3. Наблюдение
Наблюдение за работой пользователей в привычной обстановке позволяет получить информацию, поделиться которой им бы и не пришло в голову, отвечая не вопросы бизнес-аналитика. Можно подметить проблемы и узкие места. Наблюдать особенно полезно для дизайна UI.

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍7👎1
День 1963. #Шпаргалка #CSS
Единицы Размеров в CSS. Начало
Поговорим немного о фронтенде. Тем более, что разобраться с UI бывает очень непросто. Многие свойства CSS принимают числа в качестве значений. Кроме того, за ними часто следуют единицы измерения. Что такое px? Чем отличаются em и rem? И как определяются итоговые размеры?

В CSS есть два типа единиц: абсолютные и относительные.

Абсолютные единицы
Каким бы ни было число, оно именно так и рассчитывается в браузере, независимо от размера других элементов. Наиболее распространённое – пиксель (px) — наименьший строительный блок для отображения графики, основанный на разрешении. На экране с высоким разрешением пиксель будет меньше, чем на экране с низким.
Абсолютные значения предсказуемы. Но, если пользователь увеличивает масштаб страницы, то всё, что определено с абсолютным значением, соответственно увеличит свой абсолютный размер.

1. Длина
- cm – сантиметры (96px/2.54)
- mm – миллиметры (1/10cm)
- Q – четверть-миллиметры (1/40cm)
- in – дюймы (2.54cm = 96px)
- pc – пики (1/6in)
- pt – точки (1/72in)
- px – пиксели (1/96in)

2. Углы
Хороши, например, для направления линейного градиента (linear-gradient) или поворота (rotate) элемента.
- deg – градусы (полный круг – 360deg)
- grad – грады (круг – 400grad)
- rad – радианы (круг - 2π ~ 6.2832rad)
- turn – повороты (круг - 1turn)
Например, rotate(180deg)

3. Время
Тут всё просто
- s – секунды,
- ms - миллисекунды
Например, animation-duration: 2s

4. Разрешение
Количество точек на единицу измерения. Чем меньше точек, тем более «пикселизированным» и размытым будет изображение. Отлично подходит для таргетинга стилей на определенные экраны в медиа-запросах.
- dpi – точек на дюйм
- dpcm – точек на сантиметр
- dppx (или x) – точек на пиксель
Например,
@media (min-resolution: 96dpi) { … }

Спецификация определяет значение infinite для медиа-запросов под экраны без ограничений разрешения.

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

Источник:
https://css-tricks.com/css-length-units/
👍9
День 1964. #Шпаргалка #CSS
Единицы Размеров в CSS. Окончание

Начало. Абсолютные единицы

Относительные единицы
Значение относительной единицы зависит от размера чего-то ещё. Допустим, у нас есть элемент <div> с абсолютным значением высоты 200 пикселей. Эта высота никогда не изменится. Но если задать элементу относительную ширину в 50%, он займёт половину ширины страницы (либо половину ширины своего родителя). Относительное число действует как множитель для вычисления значения, в зависимости от того, относительно какого размера мы считаем.

1. Проценты
% - относительно размера родительского элемента.
Используются, когда нет другого контекста для задания размера.

2. Относительно шрифта
- em – элемент (относительно размера шрифта родительского элемента)
- rem – корневой элемент (относительно размера шрифта элемента <html>)
Аналогично:
- ch и rch – ширина символа (отличается для каждого шрифта и у разных символов, кроме моноширинных шрифтов)
- lh и rlh – высота строки
- cap и rcap – высота заглавной буквы
- ic и ric – ширина иероглифа
- ex и rex – ширина буквы X.
Большинство из этого требуется только для типографии. Однако, хорошей практикой является задавать размер шрифта в пикселях для элемента <html>, а для всех остальных элементов использовать относительные em и rem:
html {
/* Наследуется всеми элементами */
font-size: 18px;
}
.parent {
/* Изменяется при смене размера в `html` */
font-size: 1.1rem;
}
.child {
/* Изменяется при смене размера в `.parent` */
font-size: 0.9em;
}


3. Относительно области просмотра (viewport – видимая часть страницы)
Эти величины всегда считаются относительно размеров страницы.
- vh и vw – высота и ширина (100vh – высота экрана)
- vmin и vmax – минимум и максимум между vh и vw соответственно
- lvh и lvw – «большие» высота и ширина (в полноэкранном режиме)
- lvb и lvi – эквиваленты lvh и lvw для блока и строки
- svh и svw – «маленькие» высота и ширина (когда отображаются UI браузера и экранная клавиатура)
- svb и svi – эквиваленты svh и svw для блока и строки
- dvh и dvw – «динамические» высота и ширина (изменяются, например, при отображении/скрытии экранной клавиатуры)
- dvb и dvi – эквиваленты dvh и dvw для блока и строки
- dvmin и dvmax – минимум и максимум между dvh/dvb и dvw/dvi соответственно.

4. Относительно контейнера
Эти величины считаются в контейнер-запросах относительно размеров соответствующего контейнера.
- cqw – ширина контейнера (100cqw – полная ширина)
- cqh – высота контейнера
- cqi – ширина строкового контейнера
- cqb – ширина блокового контейнера
- cqmin и cqmax – минимум или максимум между cqi и cqb

Например,
.parent {
container-type: inline-size;
}
.child {
width: 100%;

@container (width > 30ch) {
.child {
width: 50cqi;
}
}
}

Когда родительский элемент блока child превысит ширину в 30 символов, он станет занимать вместо полной ширины контейнера только половину его ширины.

Источник: https://css-tricks.com/css-length-units/
👍13
День 1965. #ЧтоНовенького #CSharp13
Полуавтоматические Свойства
Полуавтоматические свойства позволяют вам добавлять логику в методы get и set, не создавая явно приватного поля как для полноценного свойства, а используя ключевое слово field.

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

Автоматические свойства
Автоматические свойства появились очень давно и используются повсеместно:
public class Test
{
public string Name { get; set; }
}

Этот код за кулисами создаёт приватное поле и два метода для получения и задания его значения. Т.е. следующий код эквивалентен коду выше:
csharp 
public class Test
{
private string _name;

public string get_Name()
{
return _name;
}
public void set_Name(string name)
{
_name = name;
}
}

Но что, если мы хотим добавить какую-то логику в метод get или set автосвойства? К примеру, мы хотим, чтобы имя всегда задавалось без лишних пробелов. До сих пор нам ничего не оставалось, кроме как отказаться от автосвойства и использовать полноценное свойство, явно создавая поле для него:
private string _name;

public string Name
{
get
{
return _name;
}
set
{
_name = value.Trim();
}
}

Полуавтоматические свойства
Начиная с С#13 мы сможем использовать для этого ключевое слово field, и не создавать поле явно:
public string Name { 
get => field;
set => field = value.Trim();
}

Интересно, что, даже если вам нужно изменить только set, то придётся реализовать и get (пусть и в таком элементарном виде). То есть, его нельзя оставить в виде get;. Возможно, к релизу это исправят.

Очевидно, что добавление нового ключевого слова может сломать чей-то существующий код. Если в вашем коде использовалось поле с названием field, то в такой ситуации будет не понятно, нужно обращаться к этому полю, либо к неявному полю для свойства Name.
Microsoft годами пыталась избегать ломающих изменений, в частности при вводе новых ключевых слов. Раньше они создавали сложные правила для разрешения этих конфликтов. Но теперь решили пойти другим путём. На примере этой функции они хотят протестировать инструмент «раннего предупреждения» о ломающем изменении. Теперь, когда вы обновляете .NET SDK на новую версию (при этом используя старую версию .NET в проектах), анализатор кода в таких случаях будет выдавать предупреждение, что этот код не будет работать в новой версии языка и предлагать автоматический рефакторинг кода под новую версию.

В этом случае для обращения к полю field из свойства нужно будет добавить @:
private string field;
public string Field {
get => @field;
set => @field = value.Trim();
}

Источник: https://www.youtube.com/watch?v=3jb9Du9pMes
👍31
День 1966. #ЗаметкиНаПолях
Безопасны Мои LINQ Запросы в EF?

Задумывались ли вы когда-нибудь о том, подвержены ли ваши LINQ-запросы в EF атакам с использованием SQL-инъекции? Т.к. вы либо используете данные прямо из текстового поля, либо вставляете всё, что вернул API в базу?

Ну, во-первых, нужно всегда проверять и экранировать вводимые данные. Но, допустим, вы этого не сделали, и …?

Хорошая новость: Entity Framework экранирует ввод. Поэтому, если у вас есть что-то вроде этого:
var userInput = "Jon \"OR 1 = 1";
var evil = await myContext
.People
.Where(p => p.Name === userInput)
.ToListAsync();

Результирующий SQL будет вроде следующего:
SELECT "p"."Id", "p"."Name"
FROM "People" as "p"
WHERE "p"."Name" = 'Jon "OR 1 = 1'

И это хорошо, потому что атака SQL-инъекцией не пройдёт, т.к. OR 1 = 1 будет рассматриваться как строка, а не как команда SQL.

Это не сработает, если вы используете чистые SQL-запросы. Поэтому, такой код:
var userInput = "Jon \"OR 1 = 1";
var evil = await myContext
.People
.FromSqlRaw(
$"SELECT * FROM People WHERE Name = '{userInput}'")
.ToListAsync();

может быть подвержен атакам SQL-инъекцией.

FromSql (до EF Core 7 - FromSqlInterpolated)
FromSql – это более безопасный способ использовать чистый SQL. Метод – часть пакета Microsoft.EntityFrameworkCore.Relational. Вот разница коротко:
var evil = await myContext.
.People
.FromSqlRaw(
$"SELECT * FROM People WHERE Name = {userInput}")
.ToListAsync();

// или
var notSoEvil = await myContext.
.People
.FromSql(
$"SELECT * FROM People WHERE Name = {userInput}")
.ToListAsync();

Хотя они выглядят одинаково, важна сигнатура методов: FromSqlRaw принимает string, а FromSqlFormattableString. FormattableString представляет собой строку с заполнителями для параметров. Так EF знает, что такое userInput, и сможет правильно его экранировать.

Если вы хотите выполнить произвольный SQL-запрос, вы можете использовать ExecuteSql на объекте Database (вместо DbSet<TYourEntity>).

Итого
Используйте FromSql (или FromSqlInterpolated). В некоторых редких случаях могут быть проблемы форматирования интерполированных строк, но они редки. Если вы столкнётесь с ними, можете использовать FromSqlRaw, но вы должны осознавать риски.

Источник: https://steven-giesel.com/blogPost/35ec8819-220a-42bc-9224-a812ec358434/are-my-ef-linq-to-sql-queries-safe
👍32
День 1967. #ЗаметкиНаПолях
Вы Пожалеете, что Использовали Естественные Ключи

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

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

Уникальность
Хороший архитектор ПО должен бросать вызов основополагающим предположениям. Допустим, вы отказались от синтетического ключа. Является ли выбранный естественный ключ (поле или сочетание полей) уникальным? Допустим, название города уникально в пределах региона. Но что, если ПО расширится до использования по всей стране? А если на несколько стран?

Идентичность
Хорошо, для таблицы городов синтетический ключ - лучший выбор, это довольно очевидно. А как насчёт, «естественных» естественных ключей? Примером может быть VIN автомобиля. Это уже определённый код, и он, вероятно, берётся из какой-то базы. А номер паспорта (или аналогичный номер в других странах)?

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

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

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

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

Возьмем, к примеру, историю о VIN-номере моей машины. Механик, заметивший несоответствие, явно истолковал это как техническую ошибку.

Рано или поздно в ваших данных появятся ошибки. Либо технические, либо опечатки пользователей, либо ошибки преобразования данных при импорте из внешней системы или после обновления. Система должна быть спроектирована так, чтобы можно было вносить исправления в данные. Сюда входит исправление внешних ключей, таких как VIN-номера, правительственные идентификаторы и т.д. Поэтому вы не можете использовать такие ключи в качестве ключей БД в своей системе.

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

Источник: https://blog.ploeh.dk/2024/06/03/youll-regret-using-natural-keys/
Автор оригинала: Mark Seemann
👍24
День 1968. #ЗаметкиНаПолях
Различия Между Span и Memory в C#

Сегодня разберём особенности типов Span и Memory в .NET, и когда что использовать.

Span<T>
Это ref-структура в .NET, представляющая собой доступ к непрерывной области памяти. Т.е. Span<T> — это всего лишь представление блока памяти, а не способ выделить память.
Span<T> может иметь несколько источников блока памяти:
- массив T[] (или его фрагмент)
- Memory<T>
- неуправляемый указатель
- результат stackalloc

Например:
int[] data = [1, 2, 3, 4, 5, 6];
var span = data.AsSpan().Slice(2, 1);

Span<T> всегда располагается в стеке и хранит только указатель на уже выделенный ссылочный тип и не выделяет новую управляемую память в куче. Span<T> нельзя упаковать или назначить переменным типа Object, Dynamic или интерфейсам. Он также не может быть полем ссылочного типа.

Использование Span<T> не требует вычисления начала указателя на ссылочный тип и смещения, поскольку эта информация уже содержится в Span<T>. Это делает вычисления с ним очень быстрыми. Более того, поскольку Span<T> не выделяет дополнительную память в куче, сборщик мусора работает быстрее, что повышает производительность всего приложения.

Memory<T>
Как и Span<T> представляет собой доступ к непрерывной области памяти, однако является структурой. Memory<T> можно разместить как в управляемой куче, так и в стеке, использовать в качестве поля в классе, а также совместно с await и yield.

Свойство Span возвращает Span<T>, что позволяет использовать Memory<T> как Span<T> в рамках одного метода. В этом смысле Memory<T> иногда называют фабрикой Span’ов.

Рекомендации
- Как правило, следует использовать Span<T> в качестве параметра для синхронного API, когда это возможно.
- Если буфер памяти должен быть доступен только для чтения, можно использовать ReadOnlySpan<T> и ReadOnlyMemory<T>.
- Если метод имеет параметр Memory<T> и возвращает void, не следует использовать этот экземпляр Memory<T> после завершения выполнения метода. Аналогично, если метод возвращает Task, не следует использовать экземпляр после завершения задачи.

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

Span<T> и Memory<T> - это представления блока памяти, которые предназначены для предотвращения копирования памяти или выделения большего количества памяти, чем необходимо, для управляемой кучи.

Для быстрых локальных вычислений и во избежание выделения ненужной памяти лучшим выбором является Span. Но, когда нужно передать его в качестве аргумента или использовать в асинхронном методе, приходится использовать Memory, который не имеет ограничений Span’а.

Источник: https://code-maze.com/csharp-differences-between-span-and-memory/
👍26
День 1969. #УрокиРазработки
Уроки 50 Лет Разработки ПО


Урок 12. Выявление требований должно помочь разработчикам услышать клиента
Идеальная среда для разработки ПО: 1 разработчик и 1 заказчик, сидящие рядом. Это редкость. У большинства проектов много заказчиков, несколько классов пользователей, множество источников требований и большое количество лиц, принимающих решения. Они реализуются командами разработчиков, насчитывающими от нескольких человек, собранных в одном месте, до сотен, разбросанных по всему миру. Таким проектам нужны другие способы взаимодействия, чтобы разработчики могли услышать клиента, выявить требования, установить приоритеты, сообщить об изменениях и принять решения.

Способы взаимодействий
1. Пользователи - разработчики
Создавать подробные письменные требования мало смысла, а вероятность недопонимания мала — при условии, что разработчик и пользователь понимают терминологию друг друга.

2. Пользователи – Менеджер по продвижению продукта – Бизнес-аналитик - Разработчики
Функции основного канала передачи информации о требованиях выполняют один или несколько ключевых представителей пользователей, называемых менеджерами по продвижению продукта (product champions), которые сотрудничают с одним или несколькими бизнес-аналитиками. Менеджеры по продвижению знают предметную область и понимают бизнес-цели проекта. Они взаимодействуют со своими коллегами, собирают требования и отзывы, а также информируют других пользователей о ходе выполнения проекта. Бизнес-аналитик помогает преодолеть разрыв в общении между ними и командой разработчиков.

3. Пользователи – Отдел маркетинга – Продакт менеджер - Разработчики
Отдел маркетинга оценивает потребности рынка и потенциал продукта. Он может работать с продакт-менеджером, ответственным за определение характеристик продукта.
Продакт-менеджер выполняет функции бизнес-аналитика в IT-проекте:
- Отвечает за вывод на рынок продукта, отвечающего потребностям рынка и представляющего реальную ценность для бизнеса.
- Обеспечивает соответствие продукта общей стратегии и целям компании.

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

5. Пользователи – Владелец продукта – Бизнес-аналитик - Разработчики
Некоторые команды признают ценность наличия в команде квалифицированного бизнес-аналитика, который может взаимодействовать с владельцем продукта. Он часто действует как помощник или заместитель владельца продукта. Владелец продукта может делегировать бизнес-аналитику часть ответственности, например работу с определёнными группами пользователей. Иногда владелец в большей степени ориентирован на продукт и рынок, тогда как бизнес-аналитик — на технические аспекты, формируя требования к решениям на основе требований пользователей.

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍2
День 1970. #Безопасность
Межсайтовая Подделка Запросов (CSRF). Начало

В 2005 г. исследователь в области информационной безопасности Сами Камкар обнаружил уязвимость в популярной в то время социальной сети Myspace. Ему удалось внедрить код JavaScript на страницу своего профиля. Это классическая XSS-атака. Код отправлял HTTP-запрос от имени жертвы, добавляя её в список друзей Камкара. Не прошло и 20 часов, как у него было более миллиона друзей на Myspace.

Название атаки (Cross-Site Request Forgery - межсайтовая подделка запросов), по сути, описывает ее анатомию. Какой-то другой сайт (созданный злоумышленником) принудительно отправляет от имени клиента поддельный запрос. Чаще всего это выглядит так:
1. Пользователь ранее посещал (целевой) сайт и создал состояние – например, выполнил вход или добавил товары в корзину. Это состояние сохраняется, обычно в виде файла cookie (скажем, сеансового файла cookie, а сессия содержит состояние входа в систему или содержимое корзины).
2. Пока браузер открыт и сессия активна, пользователь переходит на другой сайт, контролируемый злоумышленником. Этот сайт делает запрос на целевой сайт, с которым пользователь ранее взаимодействовал, одним из двух способов:
- в случае GET-запроса сайт злоумышленника содержит код JavaScript, выполняющий запрос через скрытый iframe или <img> с адресом целевого сайта в атрибуте href;
- в случае POST-запроса сайт злоумышленника содержит элемент <form> с атрибутом method=POST и адресом целевого сайта в атрибуте action. А страница содержит код JavaScript, который отправляет данные формы.

В итоге целевой сайт получает HTTP-запрос, отправленный браузером пользователя. Запрос содержит сеансовый файл cookie для идентификации пользователя. Поэтому сайт может выполнять действия «от имени» пользователя. GET-запросы обычно не так страшны, поскольку не меняют состояния (в соответствии с лучшими практиками), а вот POST-запросы могут действительно повлиять на приложение.

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

Эту атаку ещё называют "катание на сессии", т.к. сессия пользователя не крадётся, злоумышленник просто использует её "втёмную".

Именно так Сами Камкар завел новых друзей на Myspace: он нашел XSS-уязвимость и внедрил код JavaScript, который выполнял POST-запрос, чтобы «добавить пользователя в друзья».

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

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

Источник: Кристиан Венц “Безопасность
ASP.NET Core”. М.: ДМК Пресс, 2023. Глава 4.
👍21
День 1971. #Безопасность
Межсайтовая Подделка Запросов (CSRF). Окончание

Начало

В случае с CSRF есть два аспекта, которые делают атаку возможной:
1. Злоумышленник может точно предсказать HTTP-запрос. Это сделать несложно, проанализировав настоящий запрос. Данные формы передаются в открытом виде, поэтому их легко подменить, имитировав нужные параметры.
2. Активная сессия. Но файлы cookie клиента автоматически отправляются на сервер, которому они принадлежат. Злоумышленнику не нужно красть идентификатор сессии; он использует сессию пользователя.

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

Делаем HTTP-запрос непредсказуемым
Если к данным формы мы добавим дополнительный случайный токен, то атака завершится неудачей. Злоумышленник не сможет предугадать токен, поэтому не сможет создать валидный запрос. Если приложение использует одноразовые токены, то каждый из них будет действителен только один раз. Случайный токен будет явной частью HTML-формы, и такой же токен в файле cookie будет отправлен клиентом автоматически. На сервере оба этих значения проверяются. Если они отсутствуют или не совпадают, то приложение выдаёт ошибку 400 Bad Request.

В ASP.NET Core такой механизм уже есть и активирован по умолчанию. ASP.NET Core автоматически добавляет в форму дополнительное скрытое поле, вроде такого:
html 
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8FflGUpl_…U90c" />

А сервер отправляет cookie, например:
 
.AspNetCore.Antiforgery.Za7zYHoQn5w=CfDJ8FflGUpl_…0ueI

Всего символов в значениях поля и cookie около 150. Примерно первые 20 совпадают, остальные нет. При каждой перезагрузке формы токен в поле меняется, но файл cookie останется прежним. Так приложение может работать на нескольких вкладках браузера с общим файлом cookie.

Никакой дополнительной настройки не требуется, если вы используете форму следующими способами:
- <form method="post">...</form>
- @Html.BeginForm(...)

Механизм защиты от CSRF представляет собой промежуточное ПО, которое автоматически активируется, при использовании стандартных методов в классе Program:
- MVC (AddMvc(), AddControllersWithViews() или MapControllerRoute()),
- Razor Pages (AddRazorPages() или MapRazorPages()),
- Blazor (MapBlazorHub())

Если вы хотите избавиться от токенов (а у вас для этого должна быть очень веская причина, например другое приложение, отправляющее POST-запрос в вашу конечную точку), то либо:
- используйте <!form>...<!/form>;
- деактивируйте CSRF-токен для каждой формы с помощью asp-antiforgery="false".

Токен по умолчанию генерируется и добавляется в любую форму, но не проверяется автоматически. Используя фильтры в ASP.NET Core (атрибуты или глобальные фильтры), можно реализовать 3 варианта:
- AutoValidateAntiForgeryToken – требует (и проверяет) токены для всех HTTP-запросов, меняющих состояние приложения (все, кроме GET, HEAD, OPTIONS и TRACE);
- ValidateAntiForgeryToken – гарантирует, что метод-действие, помеченный этим атрибутом, проверит запрос, независимо от HTTP-метода;
- IgnoreAntiForgeryToken – отключает проверку токенов.

Источник: Кристиан Венц “Безопасность ASP.NET Core”. М.: ДМК Пресс, 2023. Глава 4.
👍15
День 1972. #ЧтоНовенького
Ссылки на Исходный Код в Документации .NET

Microsoft добавили ссылки, соединяющие документацию с исходным кодом для большинства популярных API .NET.

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

Конвейер документации работает с набором файлов DLL и пакетов NuGet. Они обрабатываются различными инструментами для преобразования их содержимого в HTML-страницы, отображаемые в Microsoft Learn. Правильное создание ссылок на исходный код требует понимания взаимосвязи между исходным кодом, двоичными файлами и GitHub, а также того, как связать их вместе с некоторыми существующими API .NET. Поэтому было принято решение пойти по пути существующего инструмента Go to definition (Перейти к определению) в Visual Studio.

Источник
👍31
День 1973. #ЗаметкиНаПолях
Особенности Bulk-Операций в EF Core

Когда вы имеете дело с тысячами или даже миллионами записей, эффективность имеет решающее значение. В EF Core 7 представлены два новых метода: ExecuteUpdate и ExecuteDelete (и их асинхронные перегрузки), предназначенные для упрощения Bulk-операций в БД. Однако они обходят трекер изменений EF Core, что может привести к неожиданному поведению, если вы об этом не знаете.

Трекер Изменений
Когда вы загружаете объекты из БД с помощью EF Core, трекер изменений начинает их отслеживать. Когда вы обновляете свойства, удаляете объекты или добавляете новые, он записывает эти изменения:
using (var context = new AppDbContext())
{
// Загрузка
var pr = context.Products
.FirstOrDefault(p => p.Id == 1);
// Изменение
pr.Price = 99.99;

// Здесь трекер знает, что pr изменён

// Добавление
var newPr = new Product {
Name = "New Gadget", Price = 129.99 };
context.Products.Add(newPr);

// Удаление
context.Products.Remove(pr);

// Сохранение всех изменений в БД
context.SaveChanges();
}

Когда вы вызываете SaveChanges, EF Core использует трекер изменений, чтобы определить, какие команды SQL следует выполнить. Это гарантирует, что БД будет синхронизирована с вашими изменениями.

Bulk-операции и трекер изменений
Bulk-операции не используют трекер изменений. Это решение может показаться нелогичным, но за ним стоит веская причина: производительность. Непосредственно выполняя инструкции SQL в БД, EF Core устраняет накладные расходы на отслеживание изменений отдельных объектов.
using (var context = new AppDbContext())
{
// Увеличиваем цену всех продуктов в категории электроника на 10%
context.Products
.Where(p => p.Category == "Electronics")
.ExecuteUpdate(
s => s.SetProperty(p => p.Price, p => p.Price * 1.10));
// Все объекты типа Product в памяти по-прежнему будут иметь старые цены
}

В этом примере метод ExecuteUpdate эффективно преобразует операцию в одну инструкцию SQL UPDATE:
UPDATE [p]
SET [p].[Price] = [p].[Price] * 1.10
FROM [Products] as [p];

Но, если вы проверите экземпляры Product, которые EF Core уже загрузил в память, вы обнаружите, что их свойства Price не изменились. Это может показаться удивительным, если вы не знаете, как массовые обновления взаимодействуют с системой отслеживания изменений. Это же применимо и к методу ExecuteDelete.

Перехватчики EF Core также не запускаются для операций ExecuteUpdate и ExecuteDelete. Если нужно отслеживать или изменять Bulk-операции, вы можете создать триггеры в БД на обновление или удаление.

Проблема: поддержание согласованности
Если ExecuteUpdate завершается успешно, изменения сразу фиксируются в БД, т.к. Bulk-операции обходят трекер изменений и не участвуют в обычной транзакции, управляемой SaveChanges. Если впоследствии SaveChanges завершится сбоем, вы окажетесь в несогласованном состоянии. Изменения, внесенные с помощью ExecuteUpdate, уже сохранятся. Любые изменения, сделанные «в памяти», потеряются.

Самый надежный способ обеспечить согласованность — обернуть в транзакцию и ExecuteUpdate, и операции, которые приводят к SaveChanges:
using (var context = new AppDbContext())
using (var transaction = context.Database.BeginTransaction())
{
try
{
context.Products
.Where(p => p.Category == "Electronics")
.ExecuteUpdate(
s => s.SetProperty(p => p.Price, p => p.Price * 1.10));

// … другие изменения сущностей
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}

Если SaveChanges завершится неудачно, транзакция будет отменена с отменой изменений, внесенных как ExecuteUpdate, так и любыми другими операциями внутри транзакции. Это сохранит вашу БД в согласованном состоянии.

Источник: https://www.milanjovanovic.tech/blog/what-you-need-to-know-about-ef-core-bulk-updates
👍23
.NET Разработчик pinned «Вы пользуетесь .NET MAUI?»