简体   繁体   English

实体框架 - 如何更新与我的 object 相关的表

[英]Entity Framework - How to update a table related to my object

First, a little context.首先,一点背景。 I'm working on a full-stack project with Angular as the front end, so there are a decent amount of intermediary classes in my project such as DTO versions of many of my classes, and a repository that is between my DataContext class and my Controller classes.我正在开发一个以 Angular 作为前端的全栈项目,因此我的项目中有相当数量的中间类,例如我的许多类的 DTO 版本,以及位于我的 DataContext class 和我的之间的存储库Controller 节课。 Anyway, I have an AppUser class that among other properties has a field of type ProfilePhoto:无论如何,我有一个 AppUser class,在其他属性中有一个 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; }
    }

Ignore the bad password storing principles, it will be changed to use Microsoft Identity later.忽略坏密码存储原则,后面会改为使用Microsoft Identity。 Here is the ProfilePhoto class for reference:这是个人资料照片 class 供参考:

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

It is fully defined so that Entity Framework knows how my tables are related.它是完全定义的,因此实体框架知道我的表是如何相关的。 In my UsersController class, I have an UpdateProfilePhoto() method which accepts a file from the user that will be uploaded through the UI:在我的 UsersController class 中,我有一个 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");
        }

The photo is being correctly uploaded to the cloud storage, and If I remove some lines of code, I am able to get my PhotoDto mapped correctly and returned to my request which I tested in Postman. The issue is definitely (99.9% sure?) with getting my database to update correctly.照片被正确上传到云存储,如果我删除一些代码行,我能够正确映射我的 PhotoDto 并返回到我在 Postman 测试的请求。这个问题肯定是(99.9% 确定?)让我的数据库正确更新。

My errors will be near the end, but here is some more info for further context/explanation.我的错误将接近尾声,但这里有一些更多信息以供进一步上下文/解释。 The GetUsername() comes from an extension of the ClaimsPrincipal class as below: GetUsername() 来自 ClaimsPrincipal class 的扩展,如下所示:

public static class ClaimsPrincipalExtensions
    {
        public static string GetUsername(this ClaimsPrincipal user)
        {
            return user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        }
    }

The GetUserByUsernameAync() comes from my UserRepository class: GetUserByUsernameAync() 来自我的 UserRepository class:

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

And the SaveAllAsync() also comes from my UserRepository class: SaveAllAsync() 也来自我的 UserRepository class:

public async Task<bool> SaveAllAsync()
        {
            return await _context.SaveChangesAsync() > 0;
        }

Now back to my [HttpPost] UpdateProfilePhoto() method.现在回到我的 [HttpPost] UpdateProfilePhoto() 方法。 No matter how I adjust my code, I'm getting one of two errors.无论我如何调整我的代码,我都会遇到两个错误之一。 In the current version, I'm getting back:在当前版本中,我回来了:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. Microsoft.EntityFrameworkCore.DbUpdateException:保存实体更改时出错。 See the inner exception for details.有关详细信息,请参阅内部异常。 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert duplicate key row in object 'dbo.ProfilePhoto' with unique index 'IX_ProfilePhoto_AppUserId'. ---> Microsoft.Data.SqlClient.SqlException (0x80131904):无法在具有唯一索引“IX_ProfilePhoto_AppUserId”的 object“dbo.ProfilePhoto”中插入重复键行。 The duplicate key value is (6).重复键值为 (6)。 The statement has been terminated.该语句已终止。

Which makes it seem like it's working correctly, but obviously it can't have a second ProfilePhoto in my ProfilePhotos database table associated with the same AppUser.这让它看起来工作正常,但显然它不能在我的 ProfilePhotos 数据库表中有第二个 ProfilePhoto 与同一个 AppUser 关联。

I also tried to adjust my code so that the existing AppUser simply has the fields of its associated ProfilePhoto object updated (instead of trying to add/update a new ProfilePhoto object).我还尝试调整我的代码,以便现有的 AppUser 仅更新其关联的 ProfilePhoto object 的字段(而不是尝试添加/更新新的 ProfilePhoto 对象)。 Here is the slightly adjusted method:这是稍微调整的方法:

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

And this version gives me this error:这个版本给了我这个错误:

System.NullReferenceException: Object reference not set to an instance of an object. at ThirtyDaysOfShred.API.Controllers.UsersController.UpdateProfilePhoto(IFormFile file) in D:\MUSIC PRODUCTION BUSINESS\30 DAYS OF SHRED\30 Days of Shred App\ThirtyDaysOfShred.API\Controllers\UsersController.cs:line 70 at lambda_method15(Closure, Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspN System.NullReferenceException: Object 引用未设置为 object 的实例。在 D:\MUSIC PRODUCTION BUSINESS\30 DAYS OF SHRED\30 DAYS OF SHRED.API.Controllers.UsersController.UpdateProfilePhoto(IFormFile 文件)。 API\Controllers\UsersController.cs:line 70 at lambda_method15(Closure, Object ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore .Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean已完成)在 Microsoft.AspN etCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routi etCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) 在 Microsoft.AspNetCoreA.waitedInvokerg.Invoker.Invoker |13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state ,Boolean 已完成)在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker 调用程序、任务任务、IDisposable 范围)在 Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker 调用程序、任务任务、IDisposable范围)在 Microsoft.AspNetCore.Routi ng.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at ThirtyDaysOfShred.API.Middleware.ExceptionMiddleware.InvokeAsync(HttpContext context) in D:\MUSIC PRODUCTION BUSINESS\30 DAYS OF SHRED\30 Days of Shred App\ThirtyDaysOfShred.API\Middleware\ExceptionMiddleware.cs:line 24 The program '[78712] iisexpress.exe' has exited with code 429496 ng.EndpointMiddleware.g__AwaitRequestTask|6_0(终结点端点、任务请求任务、ILogger 记录器)在 Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(下一个 RequestDelegate、HttpContext 上下文、AuthorizationPolicy 策略、PolicyAuthorizationResult authorizeResult)在 Microsoft.AspNetCore.Authorization.AuthorizationMiddleware调用(HttpContext 上下文)在 Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext 上下文)在 Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)在 Swashbuckle.AspNetCore.Swagger.SwaggerMiddlewareProvider.Invoke httpContext ) 在 D:\MUSIC PRODUCTION BUSINESS\30 DAYS OF SHRED\30 Days of Shred App\ThirtyDaysOfShred.API\Middleware\ExceptionMiddleware.cs:line 24 中的 ThirtyDaysOfShred.API.Middleware.ExceptionMiddleware.InvokeAsync(HttpContext context) 程序'[ 78712] iisexpress.exe' 已退出,代码为 429496 7295 (0xffffffff). 7295 (0xffffffff)。

Where the line 70 of code mentioned is "user.ProfilePhoto.Url = photo.Url;".其中第 70 行代码是“user.ProfilePhoto.Url = photo.Url;”。 When I step through the method using the debugger, I can see that "var user" is of type AppUser, and I see all of the fields correctly populated based on when it got it from the database.当我使用调试器逐步执行该方法时,我可以看到“var user”属于 AppUser 类型,并且我看到所有字段都根据从数据库中获取它的时间正确填充。 I've tried to troubleshoot this single issue for about 8 hours since I'm a student developer, and I feel like the issue is something easy I'm not seeing due to inexperience.自从我是一名学生开发人员以来,我已经尝试解决这个单一问题大约 8 个小时,而且我觉得这个问题很容易,但由于缺乏经验,我看不到。

Does it have to do with the fact that my method is returning a "Task" of "AppUser" technically?这是否与我的方法在技术上返回“AppUser”的“任务”这一事实有关? Or is that canceled out by the fact that I used "await" on the method?还是因为我在该方法上使用了“等待”这一事实而被抵消了? Lastly, I'm using MS SQL Server, but I highly doubt that's an issue since I would hope all Microsoft things play nicely together.最后,我使用的是 MS SQL 服务器,但我非常怀疑这是一个问题,因为我希望所有 Microsoft 的东西都能很好地协同工作。 This experience is making my hate Entity Framework and wishing I just did prepared SQL queries by hand on my own so that I could customize it all perfectly... Any help is extremely appreciated!这种经历让我讨厌 Entity Framework,并希望我自己手工准备了 SQL 查询,这样我就可以完美地定制它......非常感谢任何帮助! Thanks!谢谢!

As Poul pointed out, my user.ProfilePhoto was null. I was able to fix everything by manually creating an instance of a new ProfilePhoto{};正如 Poul 指出的那样,我的user.ProfilePhoto是 null。我能够通过手动创建new ProfilePhoto{}; , and Entity was smart enough to then do an update instead of insertion into my database. ,并且 Entity 足够聪明,可以进行更新而不是插入到我的数据库中。 Here is the altered code:这是修改后的代码:

[HttpPost("add-photo")] public async Task<ActionResult> UpdateProfilePhoto([FromForm]IFormFile file) { var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); [HttpPost("add-photo")] public async Task<ActionResult> 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
        {
            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");
    }

I'm sure there's a way to solve this problem in the DataContext.cs somehow, but maybe this will help someone else with a similar issue.我确信有一种方法可以以某种方式解决DataContext.cs中的这个问题,但也许这会帮助遇到类似问题的其他人。 Make sure Entity can create child objects associated with your classes.确保实体可以创建与您的类关联的子对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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