提问人:Connor Engstrom 提问时间:9/30/2022 更新时间:9/30/2022 访问量:680
实体框架 - 如何更新与我的对象相关的表
Entity Framework - How to update a table related to my object
问:
首先,介绍一下背景。我正在开发一个以 Angular 为前端的全栈项目,因此我的项目中有相当数量的中间类,例如我的许多类的 DTO 版本,以及位于我的 DataContext 类和 Controller 类之间的存储库。无论如何,我有一个 AppUser 类,该类在其他属性中具有 ProfilePhoto 类型的字段:
public class AppUser
{
public int Id { get; set; }
public string UserName { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public string KnownAs { get; set; }
public ProfilePhoto ProfilePhoto { get; set; }
public string Gender { get; set; }
public DateTime DateOfBirth { get; set; }
public string Country { get; set; }
public string About { get; set; }
public string Influences { get; set; }
public ICollection<Goal> Goals { get; set; }
public DateTime Created { get; set; } = DateTime.Now;
public DateTime LastActive { get; set; } = DateTime.Now;
public ICollection<AuthoredTab> AuthoredTabs { get; set; }
public ICollection<FavoritedTab> FavoriteTabs { get; set; }
public ICollection<LikedTab> LikedTabs { get; set; }
public ICollection<PracticeRoutineDto> PracticeRoutines { get; set; }
}
忽略错误的密码存储原则,稍后将更改为使用 Microsoft Identity。下面是 ProfilePhoto 类供参考:
public class ProfilePhoto
{
public int Id { get; set; }
public string Url { get; set; }
public string PublicId { get; set; }
public AppUser AppUser { get; set; }
public int AppUserId { get; set; }
}
它是完全定义的,因此实体框架知道我的表是如何相关的。在我的 UsersController 类中,我有一个 UpdateProfilePhoto() 方法,它接受来自用户的文件,该文件将通过 UI 上传:
[HttpPost("add-photo")]
public async Task<ActionResult<ProfilePhotoDto>> UpdateProfilePhoto([FromForm]IFormFile file)
{
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
var result = await _photoService.AddPhotoAsync(file);
if (result.Error != null) return BadRequest(result.Error.Message);
var photo = new ProfilePhoto
{
Url = result.SecureUrl.AbsoluteUri,
PublicId = result.PublicId
};
user.ProfilePhoto = photo;
if (await _userRepository.SaveAllAsync())
{
return _mapper.Map<ProfilePhotoDto>(user.ProfilePhoto);
}
return BadRequest("Problem adding photo");
}
照片被正确上传到云存储,如果我删除一些代码行,我能够正确映射我的PhotoDto并返回到我在Postman中测试的请求。问题肯定是(99.9%确定?)让我的数据库正确更新。
我的错误将接近尾声,但这里有一些更多信息,用于进一步的背景/解释。 GetUsername() 来自 ClaimsPrincipal 类的扩展,如下所示:
public static class ClaimsPrincipalExtensions
{
public static string GetUsername(this ClaimsPrincipal user)
{
return user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
GetUserByUsernameAync() 来自我的 UserRepository 类:
public async Task<AppUser> GetUserByUsernameAsync(string username)
{
return await _context.Users
.Include(x => x.PracticeRoutines)
.Include(x => x.FavoriteTabs)
.Include(x => x.LikedTabs)
.SingleOrDefaultAsync(x => x.UserName == username);
}
SaveAllAsync() 也来自我的 UserRepository 类:
public async Task<bool> SaveAllAsync()
{
return await _context.SaveChangesAsync() > 0;
}
现在回到我的 [HttpPost] UpdateProfilePhoto() 方法。无论我如何调整代码,我都会收到两个错误之一。在当前版本中,我回来了:
Microsoft.EntityFrameworkCore.DbUpdateException:发生错误 同时保存实体更改。有关详细信息,请参阅内部异常。 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): 无法在对象 'dbo.具有唯一索引的 ProfilePhoto” “IX_ProfilePhoto_AppUserId”。重复的键值为 (6)。这 语句已终止。
这使得它看起来工作正常,但显然它不能在我的 ProfilePhotos 数据库表中与同一 AppUser 关联第二个 ProfilePhoto。
我还尝试调整我的代码,以便现有的 AppUser 只是更新其关联的 ProfilePhoto 对象的字段(而不是尝试添加/更新新的 ProfilePhoto 对象)。以下是略微调整的方法:
[HttpPost("add-photo")]
public async Task<ActionResult<ProfilePhotoDto>> UpdateProfilePhoto([FromForm]IFormFile file)
{
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
var result = await _photoService.AddPhotoAsync(file);
if (result.Error != null) return BadRequest(result.Error.Message);
var photo = new ProfilePhoto
{
Url = result.SecureUrl.AbsoluteUri,
PublicId = result.PublicId
};
user.ProfilePhoto.Url = photo.Url;
user.ProfilePhoto.PublicId = photo.PublicId;
if (await _userRepository.SaveAllAsync())
{
return _mapper.Map<ProfilePhotoDto>(photo);
}
return BadRequest("Problem adding photo");
}
这个版本给了我这个错误:
System.NullReferenceException:对象引用未设置为实例 对象。在 ThirtyDaysOfShred.API.Controllers.UsersController.UpdateProfilePhoto(IFormFile 文件)位于 D:\MUSIC PRODUCTION BUSINESS\30 DAYS OF SHRED\30 Days of Shred App\ThirtyDaysOfShred.API\Controllers\UsersController.cs:第 70 行 at lambda_method15(闭合 , 对象 ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, 对象控制器, Object[] 参数)在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask'1 actionResultValueTask) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker、Task lastTask、State next、作用域作用域、对象状态、布尔值 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed 上下文)在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& 接下来,Scope& scope, Object& state, Boolean& isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker invoker、Task lastTask、State next、作用域作用域、对象状态、布尔值 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker、Task lastTask、State next、作用域作用域、对象状态、布尔值 isCompleted) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker、Task task、IDisposable scope) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker、Task task、IDisposable scope) 在 Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint 终结点、任务 requestTask、ILogger 记录器)在 Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate 接下来,HttpContext 上下文、AuthorizationPolicy 策略、 PolicyAuthorizationResult authorizeResult) 在 Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext 上下文)在 Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext 上下文)在 Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) 在 Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext、ISwaggerProvider、swaggerProvider) 在 ThirtyDaysOfShred.API.Middleware.ExceptionMiddleware.InvokeAsync(HttpContext 上下文)在 D:\MUSIC PRODUCTION BUSINESS\30 DAYS OF SHRED\30 Days of Shred App\ThirtyDaysOfShred.API\Middleware\ExceptionMiddleware.cs:line 24 程序“[78712] iisexpress.exe”已退出,并带有代码 4294967295(0xffffffff)。
其中提到的代码第 70 行是“用户。ProfilePhoto.Url = 照片。网址;“。当我使用调试器单步执行该方法时,我可以看到“var user”的类型为 AppUser,并且我看到所有字段都根据它从数据库获取它的时间正确填充。自从我是一名学生开发人员以来,我已经尝试解决了大约 8 个小时的这个问题,我觉得由于缺乏经验,这个问题很容易解决。
这是否与我的方法在技术上返回“AppUser”的“任务”有关?或者,我在方法中使用了“await”这一事实抵消了这一点?最后,我正在使用MS SQL Server,但我非常怀疑这是一个问题,因为我希望所有Microsoft的东西都能很好地协同工作。这种经历让我讨厌实体框架,并希望我只是自己手动完成准备好的 SQL 查询,以便我可以完美地自定义它......任何帮助都非常感谢!谢谢!
答:
正如 Poul 所指出的,我的是空的。我能够通过手动创建一个实例来修复所有问题,并且 Entity 足够聪明,可以进行更新而不是插入到我的数据库中。以下是更改后的代码:user.ProfilePhoto
new ProfilePhoto{};
[HttpPost(“添加照片”)] public async Task<ActionResult> UpdateProfilePhoto([FromForm]IFormFile 文件) { var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
var result = await _photoService.AddPhotoAsync(file);
if (result.Error != null) return BadRequest(result.Error.Message);
var photo = new ProfilePhoto
{
Id = user.Id,
Url = result.SecureUrl.AbsoluteUri,
AppUser = user,
PublicId = result.PublicId,
AppUserId = user.Id
};
user.ProfilePhoto = photo;
if (await _userRepository.SaveAllAsync())
{
return _mapper.Map<ProfilePhotoDto>(user.ProfilePhoto);
}
return BadRequest("Problem adding photo");
}
我确信有一种方法可以以某种方式解决这个问题,但也许这会帮助其他人解决类似的问题。确保 Entity 可以创建与类关联的子对象。DataContext.cs
评论
Task
await
user
user.ProfilePhoto
AppUser
ProfilePhoto