asp.net 样板错误地将实体保存为已删除

asp.net boilerplate incorrectly saving entity as deleted

这是我第一次 post 在这里,如果我没有提供足够的信息,请见谅。

我们正在使用带有 SQL 服务器数据库的 ABP v5.1。我们有一个 Message 实体,它继承自 FullAuditedEntity<int>,具有消息状态、类型和关联公司的各种 ID。

为了获取与状态和类型 ID 关联的值,我们从数据库中的 table 中进行查找,其中包含 ID、Group 和 Title 列。

用于消息的 CRUD 操作的 class 是 MessageAppService,它继承自 AsyncCrudAppService 并覆盖了创建和更新,在使用查找值。

问题是,当我提交对调用 MessageAppService.UpdateAsync 的消息的更新时,它会将 IsDeleted 布尔值设置为 true,而输入中的布尔值是 false。只有当我尝试从数据库中进行查找时才会发生这种情况。注释掉代码以获得预期行为的查找结果。

我将 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;
        }
    }
}

留言class

[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 class

[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 class

[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
        };
    }
}

查找class

[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);
    }
}

可能是被跟踪实体中的更改跟踪信息发生冲突。

添加 .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);
}