简体   繁体   中英

asp.net boilerplate incorrectly saving entity as deleted

This is my first post on here so sorry if I don't provide enough information.

We are using ABP v5.1 with a SQL Server database. We have a Message entity which inherits from FullAuditedEntity<int> with various IDs for message state, type, and associated company.

To get the values associated with the state and type id's we get a lookup from a table in the database which has Id, Group, and Title columns.

The class for CRUD operations for messages is MessageAppService which inherits from AsyncCrudAppService and has overrides for create and update which also calls the overridden base methods after doing some input manipulation using the lookup values.

The issue is that when I submit an update to a message which calls MessageAppService.UpdateAsync , it sets the IsDeleted boolean to true when it is false in the input. This only happens if I try to get a lookup from the database. Commenting out the code to get the lookups results in the expected behaviour.

I copied the code for AsyncCrudAppService.UpdateAsync into my code to see where it changes IsDeleted to false and it is changing it on the await CurrentUnitOfWork.SaveChangesAsync() call.

How can I stop it from marking the message as deleted when it clearly shouldn't be?

Here is the relevant code:

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

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

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

Example input and result (some of these values are null in the input because they are populated server side)

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

Update: As requested, here is the relevant code for the GetLookup method. It has 2 overloads.

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

It may be due to conflicting change tracking information in tracked entities.

Add .GetAll().AsNoTracking() as in:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM