我正在沿实体框架学习ASP.NET Core(v 2.2)。我已经使用CQRS和存储库模式以及MediatR和Auto-Mapper作为实用程序进行开发和应用。
我正在使用Identity来管理我的应用程序用户,因此我创建了一个User
类,该类继承了IdentityUser
并向身份添加了一些额外的字段,例如名字和姓氏。此外,主要应用程序用户扩展了User
并分别添加了几个特定于用户类型的额外字段。
我似乎无法更新用户的任何信息,因为EF似乎已经在跟踪该实体。我仅更新某些字段(例如名字或姓氏)。我还在调试中注意到EF对所有相关实体执行了级联更新。让我们将用户视为Medic
。该用户具有诊断程序的链接(MedicDiagnostic
链接表,其中包含用于医生和诊断程序的外键)。更新名字也会在所有诊断程序中更新。在存储库中执行SaveChangesAsync
时会引发异常。
Exception has occurred: CLR/System.InvalidOperationException
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll: 'The instance of entity type 'Medic' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using
完整日志here。
我应该如何处理?我曾尝试创建一个通用的UserRepository
,但是这失败了,因为我找不到动态加载相关数据的方法,所以我创建了一个Frankenstein,其中既包含用于检索医疗人员的上下文,又包含相关数据,但是仍使用UserRepository
。有更好的方法来解决这个问题吗?
完整的存储库可用here
现在,对于某些代码段:
Medic
控制器中的GET / POST编辑方法:
// GET: Medic/Edit/id
[Authorize (Roles = "Receptionist,Admin,Medic")]
public async Task<IActionResult> Edit (string id) {
if (id == null) {
return NotFound ();
}
var medicEditModel = await _mediator.Send (new GetMedicByIdForEditQuery (id));
if (medicEditModel == null) {
return NotFound ();
}
return View (medicEditModel);
}
// POST: Medic/Edit/id
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize (Roles = "Receptionist,Admin,Medic")]
public async Task<IActionResult> Edit (string id, [Bind ("Medic")] MedicEditModel medicEditModel) {
if (medicEditModel.ModelState.IsValid) {
var editSucceeded = await _mediator.Send (new EditMedicCommand (id, medicEditModel.Medic));
if (editSucceeded) {
return RedirectToAction ("Details", "Medic", new { id = id });
}
}
return View ();
}
编辑命令和处理程序:
public class EditMedicCommand : IRequest<bool>
{
public EditMedicCommand(string medicId, MedicEditDTO medic)
{
Medic = medic;
MedicId = medicId;
}
public string MedicId { get; set; }
public MedicEditDTO Medic { get; set; }
}
public class EditMedicCommandHandler : IRequestHandler<EditMedicCommand, bool>
{
private readonly IMedicRepository _medicRepository;
private readonly ILogger<EditMedicCommandHandler> _logger;
private readonly IMapper _mapper;
public EditMedicCommandHandler(IMedicRepository medicRepository,
ILogger<EditMedicCommandHandler> logger,
IMapper mapper)
{
_medicRepository = medicRepository;
_logger = logger;
_mapper = mapper;
}
public async Task<bool> Handle(EditMedicCommand request, CancellationToken cancellationToken)
{
Medic existingMedic = await _medicRepository.GetMedicByID(request.MedicId);
if (existingMedic == null)
{
var errorMessage = $"No medic found for id {request.MedicId}";
_logger.LogError(errorMessage);
return false;
}
existingMedic.rank = request.Medic.rank;
existingMedic.firstName = request.Medic.firstName;
existingMedic.lastName = request.Medic.lastName;
existingMedic.personalNumericalCode = request.Medic.personalNumericalCode;
existingMedic.gender = request.Medic.gender;
existingMedic.Email = request.Medic.Email;
existingMedic.UserName = request.Medic.Email;
existingMedic.address = request.Medic.address;
try
{
await _medicRepository.UpdateMedic(existingMedic, request.Medic.Password);
}
catch (Exception)
{
return false;
}
return true;
}
}
Medic存储库:
public class MedicRepository : IMedicRepository
{
private readonly IUserRepository<Medic> _userRepository;
private readonly IDbContext _context;
private readonly ILogger<MedicRepository> _logger;
private const string _role = "Medic";
public MedicRepository(
IUserRepository<Medic> userRepository,
ILogger<MedicRepository> logger,
IDbContext context)
{
_userRepository = userRepository;
_logger = logger;
_context = context;
}
public async Task AddMedic(Medic medic, string Password)
{
await _userRepository.Add(medic, Password, _role);
}
public async Task UpdateMedic(Medic medic, string NewPassword)
{
if (!String.IsNullOrEmpty(NewPassword))
{
medic.PasswordHash = _userRepository.UpdatePassword(medic, NewPassword);
}
await _userRepository.Update(medic);
}
public async Task<Medic> GetMedicByID(string id)
{
return await _context.Medics
.Include(medic => medic.Diagnostics).ThenInclude(diagnostic => diagnostic.Diagnostic)
.ThenInclude(medication => medication.Medications)
.ThenInclude(medication => medication.Medication)
.ThenInclude(medicine => medicine.Medicines)
.Include(medic => medic.Departments).ThenInclude(department => department.Department)
.FirstOrDefaultAsync(medic => medic.Id == id);
}
public async Task<IEnumerable<Medic>> GetAllMedics()
{
return await _userRepository.GetAll(_role);
}
public async Task<bool> AnyMedic(Func<Medic, bool> predicate)
{
return await _userRepository.Any(predicate, _role);
}
public async Task RemoveMedic(Medic medic)
{
await _userRepository.Remove(medic);
}
}
UserRepository
:
public class UserRepository<TEntity> : IUserRepository<TEntity> where TEntity : User {
private readonly UserManager<TEntity> _userManager;
private readonly ILogger<UserRepository<TEntity>> _logger;
public UserRepository (
ApplicationDbContext context,
IServiceProvider serviceProvider,
ILogger<UserRepository<TEntity>> logger
) {
_userManager = serviceProvider.GetRequiredService<UserManager<TEntity>> ();
_logger = logger;
}
public async Task Add (TEntity user, string password, string role) {
if (user != null) {
var userCreation = await _userManager.CreateAsync (user, password);
if (userCreation.Succeeded) {
await _userManager.AddToRoleAsync (user, role);
} else {
var errorMessage = $"Failed to create user {user.ToString()}.Aborting creation!";
_logger.LogError (errorMessage);
throw new TaskFailedException (errorMessage);
}
} else {
var errorMessage = "Null user! Aborting creation!";
_logger.LogError (errorMessage);
throw new InvalidArgumentException (errorMessage);
}
}
public async Task Remove (TEntity user) {
if (user != null) {
var userDeleteion = await _userManager.DeleteAsync (user);
if (!userDeleteion.Succeeded) {
var errorMessage = $"Failed to remove user {user.ToString()}.Aborting removal!";
_logger.LogError (errorMessage);
throw new TaskFailedException (errorMessage);
}
} else {
var errorMessage = "Null user! Aborting creation!";
_logger.LogError (errorMessage);
throw new InvalidArgumentException (errorMessage);
}
}
public async Task Update (TEntity user) {
if (user != null) {
var userUpdate = await _userManager.UpdateAsync (user);
if (!userUpdate.Succeeded) {
var errorMessage = $"Failed to update user {user.ToString()}.Aborting update!";
_logger.LogError (errorMessage);
throw new TaskFailedException (errorMessage);
}
} else {
var errorMessage = "Null user! Aborting creation!";
_logger.LogError (errorMessage);
throw new InvalidArgumentException (errorMessage);
}
}
public async Task<IEnumerable<TEntity>> GetAll (string role) {
return await _userManager.GetUsersInRoleAsync (role);
}
public async Task<TEntity> GetByEmail (string email) {
return await getUserByEmail (email).ToAsyncEnumerable ().FirstOrDefault ();
}
public async Task<bool> Any (Func<TEntity, bool> predicate, string role) {
IEnumerable<TEntity> usersWithRole = await GetAll (role);
return usersWithRole.Any (predicate);
}
private async Task<TEntity> getUserByEmail (string email) {
TEntity user = null;
if (email != null) {
user = await _userManager.FindByEmailAsync (email);
} else {
var errorMessage = "Null email address provided! Aborting search by email!";
_logger.LogError (errorMessage);
throw new InvalidArgumentException (errorMessage);
}
return user;
}
public async Task<TEntity> GetById (string id) {
TEntity user = null;
if (id != null) {
user = await _userManager.FindByIdAsync (id);
} else {
var errorMessage = "Null id provided! Aborting search by id!";
_logger.LogError (errorMessage);
throw new InvalidArgumentException (errorMessage);
}
return user;
}
public string UpdatePassword (TEntity user, string password) {
return _userManager.PasswordHasher.HashPassword (user, password);
}
}
谢谢!