Шаблон asp.net неправильно сохраняет объект как удаленный

Это мой первый пост здесь, так что извините, если я не предоставляю достаточно информации.

Мы используем ABP v5.1 с базой данных SQL Server. У нас есть объект Message, который наследуется от FullAuditedEntity<int> с различными идентификаторами для состояния сообщения, типа и связанной компании.

Чтобы получить значения, связанные с идентификатором состояния и типа, мы получаем поиск из таблицы в базе данных, в которой есть столбцы Id, Group и Title.

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

Проблема в том, что когда я отправляю обновление в сообщение, которое вызывает MessageAppService.UpdateAsync, оно устанавливает для логического значения IsDeleted значение true, когда на входе оно ложно. Это происходит только в том случае, если я пытаюсь получить поиск из базы данных. Комментирование кода для получения результатов поиска приводит к ожидаемому поведению.

Я скопировал код для AsyncCrudAppService.UpdateAsync в свой код, чтобы увидеть, где он меняет IsDeleted на false, и он меняет его при вызове await CurrentUnitOfWork.SaveChangesAsync().

Как я могу помешать ему пометить сообщение как удаленное, когда этого явно не должно быть?

Вот соответствующий код:

MessageAppService

public class MessageAppService : AsyncCrudAppService<Message, MessageDto, int, PagedMessageResultRequestDto,
        CreateUpdateMessageDto, CreateUpdateMessageDto>, IMessageAppService
    {
        private readonly IRepository<Message> _messageRepository;
        private readonly MessageGroupAppService _messageGroupAppService;
        private readonly RecipientGroupAppService _recipientGroupAppService;
        private readonly MessageRecipientAppService _messageRecipientAppService;
        private readonly RecipientAppService _recipientAppService;
        private readonly RoleManager _roleManager;
        private readonly UserManager _userManager;
        private readonly INotificationPublisher _notificationPublisher;
        private readonly IConfiguration _configuration;
        private readonly LookUpAppService _lookUpAppService;

        public MessageAppService(IRepository<Message> messageRepository,
                                MessageGroupAppService messageGroupAppService,
                                RecipientGroupAppService recipientGroupAppService,
                                MessageRecipientAppService messageRecipientAppService,
                                RecipientAppService recipientAppService,
                                RoleManager roleManager,
                                UserManager userManager,
                                INotificationPublisher notificationPublisher,
                                IConfiguration configuration,
                                LookUpAppService lookUpAppService)
            : base(messageRepository)
        {
            _messageRepository = messageRepository;
            _messageGroupAppService = messageGroupAppService;
            _recipientGroupAppService = recipientGroupAppService;
            _messageRecipientAppService = messageRecipientAppService;
            _recipientAppService = recipientAppService;
            _roleManager = roleManager;
            _userManager = userManager;
            _notificationPublisher = notificationPublisher;
            _configuration = configuration;
            _lookUpAppService = lookUpAppService;
        }

        public override async Task<MessageDto> CreateAsync(CreateUpdateMessageDto input)
        {
            return await ProcessMessage(input, true);
        }

        public override async Task<MessageDto> UpdateAsync(CreateUpdateMessageDto input)
        {
            return await ProcessMessage(input, false);
        }

        private async Task<MessageDto> ProcessMessage(CreateUpdateMessageDto input, bool create)
        {
            // Calling this causes `base.UpdateAsync` to set `IsDeleted` to `true`
            var messageState = (await _lookUpAppService.GetLookup("MessageState", input.StateLookUpId)).Title;

            var emailApprovers = false;
            var sendMessage = false;

            switch (messageState)
            {
                case "Pending":
                    // Calling this causes `base.UpdateAsync` to set `IsDeleted` to `true`
                    var company = (await _lookUpAppService.GetLookup("Company", input.CompanyLookUpId)).Title;

                    var permissionName = $"{company.ToUpper()}.Message.Approve";

                    if (!await PermissionChecker.IsGrantedAsync(permissionName))
                    {
                        emailApprovers = true;
                    }

                    break;
                case "Approved":
                    input.ApprovingUserId = AbpSession.UserId.Value;
                    sendMessage = true;
                    break;
            }

            MessageDto message;
            if (create)
            {
                message = await base.CreateAsync(input);
            }
            else
            {
                // `AsyncCrudAppService.UpdateAsync(input)` code from ABP git repo
                CheckUpdatePermission();

                var entity = await GetEntityByIdAsync(input.Id);

                MapToEntity(input, entity);

                // `entity` has correct values before this line
                await CurrentUnitOfWork.SaveChangesAsync();
                // `entity` is now soft deleted

                message = MapToEntityDto(entity);
            }

            if (input.GroupIds != null)
            {
                await _messageGroupAppService.UpdateMessageGroups(input.GroupIds, message.Id);
            }

            if (emailApprovers)
            {
                await EmailApprovers(message);
            }

            if (sendMessage)
            {
                await StartSendMessage((CreateUpdateMessageDto)message);
            }

            return message;
        }
    }
}

Класс сообщения

[Table("BmMessages")]
public class Message : FullAuditedEntity<int>
{
    public const int MaxTitleLength = 50;
    public const int MaxBodyLength = 2000;

    [Required]
    [StringLength(MaxTitleLength)]
    public string Title { get; set; }

    [StringLength(MaxBodyLength)]
    public string Body { get; set; }

    [ForeignKey(nameof(ApprovingUserId))]
    public User ApprovingUser { get; set; }
    public long? ApprovingUserId { get; set; }

    [ForeignKey(nameof(StateLookUpId))]
    public LookUp StateLookUp { get; set; }
    public int StateLookUpId { get; set; }

    [ForeignKey(nameof(TypeLookUpId))]
    public LookUp TypeLookUp { get; set; }
    public int TypeLookUpId { get; set; }

    [ForeignKey(nameof(CompanyLookUpId))]
    public LookUp CompanyLookUp { get; set; }
    public int CompanyLookUpId { get; set; }

    public DateTime? ScheduledTime { get; set; }

    public Message(string title, string body = null)
    {
        Title = title;
        Body = body;
    }

    public Message(int typeLookUpId, int stateLookUpId, int companyLookUpId, string title, string body = null)
    {
        TypeLookUpId = typeLookUpId;
        StateLookUpId = stateLookUpId;
        CompanyLookUpId = companyLookUpId;
        Title = title;
        Body = body;
    }
}

Класс MessageDto

[AutoMapFrom(typeof(Message))]
public class MessageDto : FullAuditedEntityDto<int>
{
    public string Title { get; set; }
    
    public string Body { get; set; }

    public DateTime ScheduledTime { get; set; }

    public User ApprovingUser { get; set; }
    public long? ApprovingUserId { get; set; }

    public int StateLookUpId { get; set; }
    public LookUp StateLookUp { get; set; }

    public int TypeLookUpId { get; set; }
    public LookUp TypeLookUp { get; set; }

    public int CompanyLookUpId { get; set; }
    public LookUp CompanyLookUp { get; set; }

    public int[] GroupIds { get; set; }

    public int RecipientCount { get; set; }
}

Класс CreateUpdateMessageDto

[AutoMapTo(typeof(Message))]
public class CreateUpdateMessageDto : FullAuditedEntityDto<int>
{
    [Required]
    [MaxLength(Message.MaxTitleLength)]
    public string Title { get; set; }

    [Required]
    [MaxLength(Message.MaxBodyLength)]
    public string Body { get; set; }

    public DateTime ScheduledTime { get; set; }

    public User ApprovingUser { get; set; }
    public long? ApprovingUserId { get; set; }

    [Required]
    public int StateLookUpId { get; set; }
    public LookUp StateLookUp { get; set; }

    [Required]
    public int TypeLookUpId { get; set; }
    public LookUp TypeLookUp { get; set; }

    [Required]
    public int CompanyLookUpId { get; set; }
    public LookUp CompanyLookUp { get; set; }

    public int[] GroupIds { get; set; }

    public static explicit operator CreateUpdateMessageDto(MessageDto messageDto)
    {
        return new CreateUpdateMessageDto()
        {
            Id = messageDto.Id,
            Title = messageDto.Title,
            Body = messageDto.Body,
            ScheduledTime = messageDto.ScheduledTime,
            StateLookUpId = messageDto.StateLookUpId,
            StateLookUp = messageDto.StateLookUp,
            TypeLookUpId = messageDto.TypeLookUpId,
            TypeLookUp = messageDto.TypeLookUp,
            CompanyLookUpId = messageDto.CompanyLookUpId,
            CompanyLookUp = messageDto.CompanyLookUp,
            GroupIds = messageDto.GroupIds
        };
    }
}

Класс поиска

[Table("BmLookUps")]
public class LookUp : Entity
{
    [Required]
    public string Title { get; set; }

    [Required]
    public string Group { get; set; }

    public LookUp(string title, string group)
    {
        Title = title;
        Group = group;
    }
}

Пример ввода и результата (некоторые из этих значений являются нулевыми во входных данных, поскольку они заполняются на стороне сервера)

Input
CreateUpdateMessageDto

ApprovingUser         = null,
ApprovingUserId       = null,
Body                  = "Lorem Ipsum",
CompanyLookUp         = null,
CompanyLookUpId       = 17,
CreationTime          = {12/20/2020 11:52:08 PM},
CreatorUserId         = null,
DeleterUserId         = null,
DeletionTime          = null,
GroupIds              = {int[0]},
Id                    = 73,
IsDeleted             = false,
LastModificationTime  = null,
LastModifierUserId    = null,
ScheduledTime         = {12/29/2020 11:08:00 PM},
StateLookUp           = null,
StateLookUpId         = 1,
Title                 = "Test",
TypeLookUp            = null,
TypeLookUpId          = 8

Output
MessageDto

ApprovingUser         = null,
ApprovingUserId       = null,
Body                  = "Lorem Ipsum",
CompanyLookUp         = null,
CompanyLookUpId       = 17,
CreationTime          = {12/20/2020 11:52:08 PM},
CreatorUserId         = null,
DeleterUserId         = 6,
DeletionTime          = {12/21/2020 1:33:52 AM},
GroupIds              = null,
Id                    = 73,
IsDeleted             = true, // THIS SHOULD BE FALSE
LastModificationTime  = {12/20/2020 11:52:13 PM},
LastModifierUserId    = 6,
RecipientCount        = 0,
ScheduledTime         = {12/29/2020 11:08:00 PM},
StateLookUp           = null,
StateLookUpId         = 1,
Title                 = "Test",
TypeLookUp            = null,
TypeLookUpId          = 8

Обновлять: В соответствии с запросом, вот соответствующий код для метода GetLookup. Имеет 2 перегрузки.

public class LookUpAppService : ProjectAppServiceBase, ILookUpAppService
{
    private readonly IRepository<LookUp, int> _lookUpRepository;

    public LookUpAppService(IRepository<LookUp, int> lookUpRepository)
    {
        _lookUpRepository = lookUpRepository;
    }

    public async Task<LookUp> GetLookup(string Group, int Id)
    {
        return await _lookUpRepository.FirstOrDefaultAsync(l => l.Group == Group && l.Id == Id);
    }
        
    public async Task<LookUp> GetLookup(string Group, string Title)
    {
        return await _lookUpRepository.FirstOrDefaultAsync(l => l.Group == Group && l.Title == Title);
    }
}
Почему в Python есть оператор &quot;pass&quot;?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
734
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это может быть связано с противоречивой информацией об отслеживании изменений в отслеживаемых объектах.

Добавьте .GetAll().AsNoTracking() как в:

public async Task<LookUp> GetLookup(string Group, int Id)
{
 // return await _lookUpRepository.FirstOrDefaultAsync(l => l.Group == Group && l.Id == Id);
    return await _lookUpRepository.GetAll().AsNoTracking().FirstOrDefaultAsync(l => l.Group == Group && l.Id == Id);
}
    
public async Task<LookUp> GetLookup(string Group, string Title)
{
 // return await _lookUpRepository.FirstOrDefaultAsync(l => l.Group == Group && l.Title == Title);
    return await _lookUpRepository.GetAll().AsNoTracking().FirstOrDefaultAsync(l => l.Group == Group && l.Title == Title);
}

Другие вопросы по теме