Ответы пользователя по тегу .NET
  • Какие есть способы создания, обновления отдельных changelog.md для нескольких проектов .csproj в едином репозитории?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    А разве флаг --proj-name не для этого в Versionize добавлен?

    Usage: versionize [command] [options]
    
    Options:
    ...
      --proj-name    Name of a project defined in the configuration file (for monorepos)


    UPD: справедливости ради, в README доку для него добавили месяц назад (на текущий момент), поэтому в самом туле его может не быть еще
    Ответ написан
    3 комментария
  • Как сделать Bulk update методами LINQ (Entity Framework Core) дополнительной таблицы при связи много-ко-многим?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Для данного случая предлагаю update в цикле, обернутую в транзакцию.
    Так как даже в SQL такое не выразить

    Примерно так
    public async Task ApplyRange(Guid pharmacyId, IEnumerable<(Guid id, int count)> products)
    {
        await _table.BeginTransactionAsync();
        for (var (id, count) in products)
        {
              await _table.Where(e => e.PharmacyId == pharmacyId && e.ProductId == id)
                                  .ExecuteUpdateAsync(entity => entity.SetProperty(x => x.Count, x => x.Count + count))
        }
        await _table.CommitTransactionAsync();
    }


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

    P.S.
    1. Есть CreateExecutionStrategy, который и реализует семантику транзакции
    2. Код писал здесь в редакторе, поэтому может даже не компилироваться - главная задача дать идею
    Ответ написан
    4 комментария
  • Как правильно тестировать базу данных в .NET?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Решение простое - создаешь мок БД для тестов.
    1. Тест начинается - запускаешь БД и заполняешь данными необходимыми (как сказал Василий Банников можно сделать дамп с удаленными чувствительными данными)
    2. После каждого теста необходимо выполнить откат - если какие-то данные были добавлены/удалены/изменены
    3. При завершении тестирования удаляешь БД

    На мой взгляд, здесь просто много инфраструктурной работы. Полезные инструменты:
    1. Testcontainers - запускаешь БД в контейнере. Сам ей пользовался, есть много шаблонов для разных БД, чтобы с нуля не писать все. Можно также скрипт инициализации (схема, дамп) добавить - вот тебе и настройка
    2. В зависимости от фреймворка есть разные механизмы запуска кода после каждого тест-кейса. Если про xUnit, то:
      1. Тестовый класс реализует IDisposable - выполняется после каждого тест-кейса. Можно тут реализовать логику отката БД
      2. Для инициализации самого контейнера (чтобы каждый раз не запускать заново) - IClassFixture



    Также никто не отменял внешний инстанс БД использовать - просишь дба создать отдельную БД специально для тестов, просто запускать теперь параллельно не получится
    Ответ написан
    Комментировать
  • Почему получается подключить Core в Dockerfile?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Проверь контекст из которого запускаешь docker build. Ошибка не связана с Libs - все падает еще на этапе копирования первого проекта

    P.S. это последний аргумент (обычно . ставят), например docker build -t sample-image:latest .или
    docker build -t sample-image:latest source-directory
    Ответ написан
    2 комментария
  • Почему служба Windows с Timer отрабатывает только один раз?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Скорее всего проблема с GC.
    Ты создаешь локальную переменную таймера и никуда не сохраняешь. В результате, по окончании OnStart на эту переменную никто не указывает и в результате ее собирает GC. Дополнительно метод Count - статический, т.е. ссылки на текущий объект Service1 тоже не сохраняется.
    Попробуй 2 вещи:
    - Сделать Count не статическим (но след. вариант лучше)
    - Хранить этот таймер в поле (инициализируешь в OnStart). Дополнительно в этой случае стоит останавливать этот таймер в OnStop
    Ответ написан
    2 комментария
  • Как правильно работать с форматом HL7 в рамках .NET?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Я тоже не работал с HL7, но если просуммировать что нашел, то:
    - HL7 - Health Level 7
    - Это прикладной протокол (протокол приложений), как 7 уровень OSI. Описывает данные для приложения. Как например, HTTP - текстовый и ничего про TCP
    - Т.к. это стандарт, то есть множество реализаций (Version 2, Version 3, CDA, CDD и другие)
    - Предназначен для работы с медицинскими документами (электронный документооборот)

    Без этих всяких GPT нашел библиотеки:
    - clear-hl7-net - реализация Version 2.0 лежит на гитхабе для netstandard2.1 и net7.0
    - целая статья на хабре, в которой гора этих реализация

    Лично от меня: я бы не советовал браться за этот заказ - слишком велики риски и ответственность высокая (тут жизнь людей)
    Ответ написан
    2 комментария
  • Как отсортировать вложенные друг в друга объекты?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    В голову приходит схема сортировки тройки (id родителя; id потомка; название):
    - id родителя - id узла-родителя, может быть null
    - id потомка - id любого узла-потомка, может быть null
    - название - это поле Name (скорее всего сортировка по нему нужна)

    И тогда сравнение будет по соответствующим элементам тройки.
    Причем, id родителя и id потомка могут быть null и в таком случае сравнение будет null first (т.е. null больше) и null last (т.е. null меньше) для id родителя и id потомка соответственно. А если равны, то уже по имени (Name) сравниваем.
    Вроде бы условия соблюдаются:
    - Без родителя первый: id родителя = null - больший вес при сравнении
    - Самый последний - объект без дочерних элементов: id потомка = null - больший вес при сравнении
    - Дальше по убыванию к дочерним: id - можно как числа сравнивать

    P.S. "дальше по убыванию, к дочерним" не совсем понял, что имеется ввиду
    Ответ написан
    Комментировать
  • Как связать grpc message з MediatR?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Почему не сделать отдельный IRequest, у которого единственное поле - этот массив байтов модели?
    Либо при получении этого массива делай его парсинг и создавай отдельный, готовый класс модели IRequest
    Ответ написан
  • В чем отличия jetbrains dotTrace и clrstack?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    dotTrace - полноценная утилита для профилирования приложений, нахождения горячих точек. В общем платный и удобный инструмент. Можно сказать, что это полноценная "IDE" для профилирования, которая может подсказать проблемы
    clrstack - это уже консольная утилита, для визуализации уже снятого дампа стека (дамп снять можно через dotnet dump). Тут работа через консоль (менее наглядно, возможно чем через GUI на dotTrace), но бесплатно. В сравнении с dotTrace, это уже просто небольшая тулза, которая облегчит профилирование, но всякие выводы должен делать уже сам (нет подсказок каких-то)

    UPD: clrstack вроде как только для windows судя по исходникам
    Ответ написан
    4 комментария
  • Как передаются регистры в трансляторах? Как происходит переход, от виртуальных регистров к машинным?

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

    Просто иметь оптимизации, которые знают, какой регистр какое название имеет.

    Раз уж ты поставил тег .NET, то у платформы есть свой JIT компилятор, который пишется под каждую платформу (CPU) и имеет знание о ее регистрах.
    Вот пример того, как это делается в исходном коде:
    1. Каждая платформа регистрирует свои регистры и информацию о них.
    #if defined(TARGET_XARCH)
    
    #if defined(TARGET_X86)
    /*
    REGDEF(name, rnum,   mask, sname) */
    REGDEF(EAX,     0,   0x01, "eax"   )
    REGDEF(ECX,     1,   0x02, "ecx"   )
    REGDEF(EDX,     2,   0x04, "edx"   )
    REGDEF(EBX,     3,   0x08, "ebx"   )
    REGDEF(ESP,     4,   0x10, "esp"   )
    REGDEF(EBP,     5,   0x20, "ebp"   )
    REGDEF(ESI,     6,   0x40, "esi"   )
    REGDEF(EDI,     7,   0x80, "edi"   )
    REGALIAS(RAX, EAX)
    REGALIAS(RCX, ECX)
    REGALIAS(RDX, EDX)
    REGALIAS(RBX, EBX)
    REGALIAS(RSP, ESP)
    REGALIAS(RBP, EBP)
    REGALIAS(RSI, ESI)
    REGALIAS(RDI, EDI)
    
    #else // !defined(TARGET_X86)
    
    /*
    REGDEF(name, rnum,   mask, sname) */
    REGDEF(RAX,     0, 0x0001, "rax"   )
    REGDEF(RCX,     1, 0x0002, "rcx"   )
    REGDEF(RDX,     2, 0x0004, "rdx"   )
    REGDEF(RBX,     3, 0x0008, "rbx"   )
    REGDEF(RSP,     4, 0x0010, "rsp"   )
    REGDEF(RBP,     5, 0x0020, "rbp"   )
    REGDEF(RSI,     6, 0x0040, "rsi"   )
    REGDEF(RDI,     7, 0x0080, "rdi"   )
    REGDEF(R8,      8, 0x0100, "r8"    )
    REGDEF(R9,      9, 0x0200, "r9"    )
    REGDEF(R10,    10, 0x0400, "r10"   )
    REGDEF(R11,    11, 0x0800, "r11"   )
    REGDEF(R12,    12, 0x1000, "r12"   )
    REGDEF(R13,    13, 0x2000, "r13"   )
    REGDEF(R14,    14, 0x4000, "r14"   )
    REGDEF(R15,    15, 0x8000, "r15"   )
    
    REGALIAS(EAX, RAX)
    REGALIAS(ECX, RCX)
    REGALIAS(EDX, RDX)
    REGALIAS(EBX, RBX)
    REGALIAS(ESP, RSP)
    REGALIAS(EBP, RBP)
    REGALIAS(ESI, RSI)
    REGALIAS(EDI, RDI)
    
    #endif // !defined(TARGET_X86)

    2. Для каждой платформы реализуются своя пара кодогенератор/эмиттер

    // Кодогенератор
    void CodeGen::genCodeForBinary(GenTreeOp* treeNode)
    {
        GenTree* op1 = treeNode->gtGetOp1();
        GenTree* op2 = treeNode->gtGetOp2();
    
        instruction ins = genGetInsForOper(treeNode->OperGet(), targetType);
    
        regNumber op1reg = op1->isUsedFromReg() ? op1->GetRegNum() : REG_NA;
        regNumber op2reg = op2->isUsedFromReg() ? op2->GetRegNum() : REG_NA;
    
        GenTree* dst;
        GenTree* src;
    
        // This is the case of reg1 = reg1 op reg2
        // We're ready to emit the instruction without any moves
        if (op1reg == targetReg)
        {
            dst = op1;
            src = op2;
        }
        // We have reg1 = reg2 op reg1
        // In order for this operation to be correct
        // we need that op is a commutative operation so
        // we can convert it into reg1 = reg1 op reg2 and emit
        // the same code as above
        else if (op2reg == targetReg)
        {
            dst = op2;
            src = op1;
        }
        // dest, op1 and op2 registers are different:
        // reg3 = reg1 op reg2
        // We can implement this by issuing a mov:
        // reg3 = reg1
        // reg3 = reg3 op reg2
        else
        {
            var_types op1Type = op1->TypeGet();
            inst_Mov(op1Type, targetReg, op1reg, /* canSkip */ false);
            regSet.verifyRegUsed(targetReg);
            gcInfo.gcMarkRegPtrVal(targetReg, op1Type);
            dst = treeNode;
            src = op2;
        }
        // try to use an inc or dec
        if (oper == GT_ADD && !varTypeIsFloating(treeNode) && src->isContainedIntOrIImmed() && !treeNode->gtOverflowEx())
        {
            if (src->IsIntegralConst(1))
            {
                emit->emitIns_R(INS_inc, emitTypeSize(treeNode), targetReg);
                genProduceReg(treeNode);
                return;
            }
            else if (src->IsIntegralConst(-1))
            {
                emit->emitIns_R(INS_dec, emitTypeSize(treeNode), targetReg);
                genProduceReg(treeNode);
                return;
            }
        }
        regNumber r = emit->emitInsBinary(ins, emitTypeSize(treeNode), dst, src);
    }
    
    // Эммитер
    /*****************************************************************************
     *
     *  Add an instruction with two register operands.
     */
    
    void emitter::emitIns_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, insOpts instOptions)
    {
        if (IsMovInstruction(ins))
        {
            assert(!"Please use emitIns_Mov() to correctly handle move elision");
            emitIns_Mov(ins, attr, reg1, reg2, /* canSkip */ false);
        }
    
        emitAttr size = EA_SIZE(attr);
    
        assert(size <= EA_64BYTE);
        noway_assert(emitVerifyEncodable(ins, size, reg1, reg2));
    
        /* Special case: "XCHG" uses a different format */
        insFormat fmt = (ins == INS_xchg) ? IF_RRW_RRW : emitInsModeFormat(ins, IF_RRD_RRD);
    
        instrDesc* id = emitNewInstrSmall(attr);
        id->idIns(ins);
        id->idInsFmt(fmt);
        id->idReg1(reg1);
        id->idReg2(reg2);
    
        if ((instOptions & INS_OPTS_EVEX_b_MASK) != INS_OPTS_NONE)
        {
            // if EVEX.b needs to be set in this path, then it should be embedded rounding.
            assert(UseEvexEncoding());
            id->idSetEvexbContext(instOptions);
        }
    
        UNATIVE_OFFSET sz = emitInsSizeRR(id);
        id->idCodeSize(sz);
    
        dispIns(id);
        emitCurIGsize += sz;
    }


    Дальше нам остается просто получить название регистра по его числу - это мы сделали на 1 шаге при их регистрации.

    Здесь применяется условная компиляция. Но в рантайме (динамически) это тоже можно реализовать - просто кидаешь везде простые массивы и индексы.
    Ответ написан
    2 комментария
  • Как в XMLSpy конвертировать в строку .NET объект?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    Может попробовать toString(), который в самом js?
    Ответ написан
  • Как определить Xml файл, на котором вылетает программа?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    Нет, скорее всего это не XML файл.

    Судя по логам (System.Runtime.Remoting.Proxies) используется RPC запрос, к сервису HMI (я не знаю, что это за сервис, но возможная расшифровка Human Machine Interface). Суть в том, что он должен отдавать ответ в формате XML, но почему-то этого не делает.

    Что случилось (изменился API, возникло у него исключение и т.д.) мне не известно, но проблема скорее всего на их стороне
    Ответ написан
    3 комментария
  • Как реализовать атомарное обновление 2 файлов?

    AshBlade
    @AshBlade Автор вопроса
    Просто хочу быть счастливым
    В итоге пришел к такому решению:
    Моя основная проблема - не как атомарно обновить 2 файла, а создавать снапшот приложения и очищать лог (чтобы размер не становился огромным) так, чтобы согласованность не нарушалась.

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

    Так как я использую RAFT, то необходимость в UNDO логе при записи в лог отпадает:
    - запись append only, т.е. закоммиченные записи перезаписываться не будут
    - лог может быть в несогласованном состоянии только если не до конца будут перезаписаны незакоммиченные записи, но это можно понять по чек-суммам, которые идут после каждой записи (можем откатить лог)

    Также нашел недостаток в моей реализации - я храню индекс последней закоммиченной записи на диске (хранил в логе). Этого можно не делать и спокойно хранить в памяти, инициализируя на старте в 0 (ничего не закоммичено). Такой подход я также нашел в реализации рафт от HashiCorp (точнее есть открытый issue). Этот индекс будет инициализирован либо существующим лидером, либо при старте он будет корректно выставлен согласно консенсусу.

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

    Похожие идеи я нашел в:
    - etcd - создание новых файлов сегментов через атомарный rename
    - kafka - все записи хранит в xxxxxxx.log файле, где xxxxxxx означает офсет первой записи
    Ответ написан
    Комментировать
  • Сравнение скорости копирования массивов разными способами?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    Ошибки:
    1. В каждом тесте лучше передавать массивы через аргументы, а не словарем - это тоже немного влияет
    2. Циклы внутри не нужны - сам фреймворк запускает это все множество раз под капотом

    вопрос почему

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

    на не больших массивах, использование которых в реальной жизни крайне редко.

    Это откуда такие данные?
    - Enumerable с 1 элементом
    - Результат фильтрации выбрал только 2-3 штуки
    - Сериализация примитивных (и не только) типов

    Я вообще молчу, что stackalloc надо вызывать с маленьким размером, а работать с ним через Span

    UPD: запустил бенчмарк у себя и получил примерно такие же результаты. Система: AMD Ryzen 5, Ubuntu 22.04, 16 GB RAM

    Предполагаю, что причина в следующем:
    1. .NET 8 вышел только недавно и нет достаточного кол-ва патчей
    2. Платформа сейчас больше ориентируется на Web и на такие моменты, как оптимизация копирования, времени уделяется мало

    P.S. скорее причина в 1
    Ответ написан
    2 комментария
  • Есть ли смысл завершать задачи генерируя ошибку(token.ThrowIfCancellationRequested()) если есть спобос получше?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Зачем завершать процесс генерируя ошибку(token.ThrowIfCancellationRequested())?

    1. Чтобы не плодить постоянно Result классы с шаблонными Cancelled.
    2. Как иначе определить успешно завершилась задача или токен был отменен?

    Вот есть способ завершать задачу return-ном в методе задачи.

    У тебя void, а если должен вернуть что-то? См. п. 2

    try-catch ресурса затратная конструкция

    Если это код который запускается 10000000... раз в мс, то да - затратная. А если это бизнес-логика, которая часто меняется, то нет - так быстрее вносить изменения - просто добавь еще один обработчик (не надо вносить кучу флагов)
    Ответ написан
    Комментировать
  • Jenkins, Jenkinsfile, dotnet pack, почему ошибка?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    stage("publish") {
        when {
            expression {
                GIT_BRANCH == 'build'
            }
        }
        steps {
            bat 'dotnet pack -c Release -o ".\artifacts" --no-build'
        }
    }


    Попробуй так. Я заменил тип кавычек с двойных на одинарные
    Ответ написан
  • Что плохого в использованиe в MethodImpl(MethodImplOptions.AggressiveOptimization)?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Во-первых, каждый раз это писать утомишься.
    Во-вторых, в .NET умный алгоритм оптимизации, если надо будет он оптимизирует сам.
    В-третьих, оптимизация ресурсоемкий процесс. В данном случае все быстро произошло. А теперь представить огромный проект на несколько миллионов строк кода, где некоторые части вообще не используются либо запускаются только 1 раз. Все они должны будут оптимизироваться самым ярым образом.

    В итоге: никто не запрещает его везде использовать, просто это "Экономически" не выгодно
    Ответ написан
    Комментировать
  • Почему в CoreCLR Int32.TryParse сделано не самым оптимальным образом?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Причина 1 (расширяемость)
    Если в будущем добавлять новые варианты NumberStyles, которые null должны обрабатывать иначе, либо какую-нибудь комбинацию, которая при null возвращает int.MinValue (например), то при проверке на null код сработает неправильно.

    Причина 2 (контракт)
    На вход всегда должны подаваться правильные данные. Очень странно, если будешь выполнять какую-либо работы с неправильными входными значениями.
    Я, например, всегда валидирую данные перед тем как выполнять работу.

    Причина 3 (легаси/совместимость)
    Может в старых версиях (.NET Framework) было такое поведение - исключение при неправильных данных
    Ответ написан
    2 комментария
  • Как правильно публиковать nuget packages с зависимыми проектами?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    1. Ссылаться нужно не на проект, а на другой пакет - тебе тогда надо публиковать 2 отдельных пакета

    2. Изменить .csproj так, чтобы в пакете был нужный .dll
    Ответ написан
  • Какой хостинг выбрать для ТГ бота на .Net?

    AshBlade
    @AshBlade Куратор тега C#
    Просто хочу быть счастливым
    Боты используют лонг-поллинг, поэтому DNS имя покупать/арендовать не надо, ровно как и платить за HTTP траффик приходящий (запросы). P.S. если ты на кол-беках делаешь, то надо.

    В данном случае подойдет любой хостинг, который поддерживает контейнеры.
    Такими могут служить:
    - Яндекс.Облако
    - Selectel
    - Hostinger
    - Digital Ocean

    и т.д. Просто вбиваешь - хостинг докер контейнеров.

    Если тебе нужно, хостить полноценное приложение (без докера), то круг выбора ссужается. Подсказать не могу.

    Лично я пользовался яндекс облаком - разворачивал полноценный Backend-Frontend на докеркомпозе. При регистрации дают бесплатные деньги (около 3тыс.)
    Ответ написан
    5 комментариев