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

Entity Framework - How to update a table related to my object

提问人:Connor Engstrom 提问时间:9/30/2022 更新时间:9/30/2022 访问量:680

问:

首先,介绍一下背景。我正在开发一个以 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 查询,以便我可以完美地自定义它......任何帮助都非常感谢!谢谢!

C# NET 实体框架 .net-core nullreferenceexception

评论

0赞 Poul Bak 9/30/2022
当您使用 时,您的方法应返回。这绝对是正确的。Taskawait
0赞 Poul Bak 9/30/2022
不知何故,要么是 null。useruser.ProfilePhoto
1赞 Connor Engstrom 9/30/2022
你是对的!谢谢!显然,当 Entity 从数据库中抓取我的用户时,它能够创建一个对象,但它无法正确创建该对象。也许有一种方法可以清理我的 DataContext 类,了解它与数据库的通信方式,但现在我调整代码以手动创建实例。AppUserProfilePhoto

答:

0赞 Connor Engstrom 9/30/2022 #1

正如 Poul 所指出的,我的是空的。我能够通过手动创建一个实例来修复所有问题,并且 Entity 足够聪明,可以进行更新而不是插入到我的数据库中。以下是更改后的代码:user.ProfilePhotonew 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