Это мой первый пост здесь, так что извините, если я не предоставляю достаточно информации.
Мы используем 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);
}
}
Это может быть связано с противоречивой информацией об отслеживании изменений в отслеживаемых объектах.
Добавьте .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);
}