ASP.NET Core MVC上传视频时出现PK_User主键冲突问题求助
上传视频时的主键冲突问题
错误信息
SqlException: Violation of PRIMARY KEY constraint 'PK_User'. Cannot insert duplicate key in object 'dbo.User'. The duplicate key value is (831769c6-fc01-4203-a345-0aa12702b95e)
上传视频时遇到矛盾:不为Video模型传入User对象,Video.User属性会为null;传入User对象后,EF会尝试创建新用户,触发主键冲突错误。
相关代码
控制器Upload方法
public async Task<IActionResult> Upload(VideoUploadViewModel model) { if (ModelState.IsValid) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); var user = await _userRepository.GetUserByIdAsync(userId); var video = new Video { Title = model.Title, Description = model.Description, DurationSeconds = model.DurationSeconds, CategoryId = model.CategoryId, User = user, UserId = userId }; await _videoRepository.AddVideoAsync(video, model.VideoFile); return RedirectToAction("Index"); } var categories = await _categoryRepository.GetAllCategoriesAsync(); ViewBag.Categories = categories; return View(model); }
VideoRepository的AddVideoAsync方法
public async Task AddVideoAsync(Video video, IFormFile videoFile) { try { if (videoFile != null && videoFile.Length > 0) { var fileName = Guid.NewGuid().ToString() + Path.GetExtension(videoFile.FileName); var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "videos", fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await videoFile.CopyToAsync(stream); } video.FilePath = fileName; } _context.Videos.Add(video); await _context.SaveChangesAsync(); } catch (Exception) { if (!string.IsNullOrEmpty(video.FilePath)) { var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "videos", video.FilePath); if (File.Exists(filePath)) { File.Delete(filePath); } } throw; } }
Video实体类
public class Video { [Key] public int VideoId { get; set; } [Required(ErrorMessage = "Поле 'Название видео' обязательно для заполнения")] [Display(Name = "Название видео")] [StringLength(100, ErrorMessage = "Длина 'Названия' не должна превышать 100 символов")] public string Title { get; set; } [Required(ErrorMessage = "Поле 'Описание видео' обязательно для заполнения")] [Display(Name = "Описание видео")] [StringLength(500, ErrorMessage = "Длина 'Описания' не должна превышать 500 символов")] public string Description { get; set; } [Required(ErrorMessage = "Поле 'Продолжительность видео' обязательно для заполнения")] [Display(Name = "Продолжительность видео")] [Range(0, int.MaxValue, ErrorMessage = "Значение 'Продолжительность' должно быть неотрицательным")] public int DurationSeconds { get; set; } [ScaffoldColumn(false)] public string FilePath { get; set; } [NotMapped] [Display(Name = "Файл видео")] [Required(ErrorMessage = "Выберите видеофайл")] public IFormFile VideoFile { get; set; } [ScaffoldColumn(false)] public DateTime UploadDate { get; set; } [Display(Name = "Категория видео")] [Required(ErrorMessage = "Поле 'Категория видео' обязательно для заполнения")] public int CategoryId { get; set; } [ForeignKey("CategoryId")] public Category Category { get; set; } public string UserId { get; set; } [ForeignKey("UserId")] public User User { get; set; } public ICollection<Comment> Comments { get; set; } public ICollection<Like> Likes { get; set; } public ICollection<View> Views { get; set; } public Video() { UploadDate = DateTime.UtcNow; Comments = new List<Comment>(); Likes = new List<Like>(); Views = new List<View>(); } }
User实体类
public class User : IdentityUser { [Key] public override string Id { get; set; } [Required(ErrorMessage = "Поле 'Email' обязательно для заполнения")] [EmailAddress(ErrorMessage = "Некорректный формат 'Email'")] public override string Email { get; set; } [Required(ErrorMessage = "Поле 'Логин' обязательно для заполнения")] [StringLength(50, MinimumLength = 3, ErrorMessage = "Длина 'Логина' должна быть от 3 до 50 символов")] public override string UserName { get; set; } [ScaffoldColumn(false)] [Display(Name = "Дата регистрации")] public DateTime RegistrationDate { get; set; } public ICollection<Video> Videos { get; set; } public ICollection<SearchHistory> SearchHistory { get; set; } public ICollection<WatchHistory> WatchHistory { get; set; } public ICollection<WatchLater> WatchLaterList { get; set; } public ICollection<Notification> Notifications { get; set; } public ICollection<View> Views { get; set; } public ICollection<Comment> Comments { get; set; } public ICollection<Like> Likes { get; set; } public User() { RegistrationDate = DateTime.UtcNow; Videos = new List<Video>(); SearchHistory = new List<SearchHistory>(); WatchHistory = new List<WatchHistory>(); WatchLaterList = new List<WatchLater>(); Notifications = new List<Notification>(); Views = new List<View>(); Comments = new List<Comment>(); Likes = new List<Like>(); } }
UserRepository类
public class UserRepository : IUserRepository { private readonly UserManager<User> _userManager; public UserRepository(UserManager<User> userManager) { _userManager = userManager; } public async Task<User> GetUserByIdAsync(string id) { return await _userManager.FindByIdAsync(id); } }
解决方案
方案1:仅设置UserId,不关联User对象
EF只需外键UserId就能建立关联,无需传入完整的User对象。修改Video初始化代码:
var video = new Video { Title = model.Title, Description = model.Description, DurationSeconds = model.DurationSeconds, CategoryId = model.CategoryId, UserId = userId // 只设置外键,移除User = user };
此方式下EF不会尝试创建新用户,仅关联已存在的用户记录。
方案2:将User对象附加到DbContext
若必须保留User属性赋值,需告知EF该User是已存在实体,避免被标记为新增。在AddVideoAsync方法中,_context.Videos.Add(video);之前添加:
if (video.User != null) { _context.Attach(video.User).State = EntityState.Unchanged; }
该操作会让EF将User视为已存在实体,不会执行插入操作。
方案3:确保User对象被当前DbContext跟踪
UserManager.FindByIdAsync返回的User可能不在当前_context的跟踪范围内。若你的DbContext继承自IdentityDbContext,可修改UserRepository的查询方式:
public async Task<User> GetUserByIdAsync(string id) { return await _context.Users.FindAsync(id); }
此方式获取的User会被DbContext跟踪,添加Video时EF不会将其识别为新实体。
内容的提问来源于stack exchange,提问作者user23356758
相关产品推荐
相关产品推荐

