2

我正在尝试删除与以下代码的关系:

                var serverVideo = InternalGetVideo(db, videoId);

                // get owning user
                var owner = InternalGetUser(db, serverVideo.UserId);

                // Add/remove video to user's upvotedVideos
                if (owner.UpvotedVideos.Contains(serverVideo))
                {
                    serverVideo.UpVotes--;
                    SaveChanges(db); // Works
                    owner.UpVotes--;
                    SaveChanges(db); // Works
                    owner.UpvotedVideos.Remove(serverVideo);
                    SaveChanges(db); // Breaks
                }

                SaveChanges(db);

但我总是得到这个错误:

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新的关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

我不是要删除视频,我只是想从用户的 UpvotedVideos 属性中删除它(它在那里):

[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string Username { get; set; }
    public int UpVotes { get; set; }
    public virtual List<Video> UpvotedVideos { get; set; }
    public string Email { get; set; }
}

视频不包含对 UserProfiles 的引用 - 这是一对多的关系。知道我做错了什么吗?

视频类:

[DataContract]
public class Video
{
    [Key]
    [DataMember(IsRequired = false)]
    public int VideoId { get; set; }

    [DataMember(IsRequired = false)]
    public int UserId { get; set; }  // user that created the video, unrelated

    [Required]
    [DataMember]
    public virtual IList<Tag> Tags { get; set; }

}
4

2 回答 2

1

您在源代码中的评论:

public int UserId { get; set; }  // user that created the video, unrelated

无关?这很可能是所讨论的一对多关系的外键,因为映射约定会将其检测为外键,因为它UserIdUserProfile. 并且由于该属性具有不可为空的类型int,EF 将根据需要检测关系,这意味着如果没有用户支持,则任何视频都无法存在。

这就是当您从UpvotedVideos集合中删除视频时异常所说的内容,这意味着与当前支持该视频的用户的关系被删除。您可以这样做,但是因为每个视频都必须有一个对其进行投票的用户,所以您必须将该视频分配给另一个用户或完全删除该视频。

好吧,显然您不希望视频必须有一个支持它的用户,而是一个视频可以没有任何支持。在这种情况下,关系必须是可选的。您可以通过多种方式实现此目的:

  • 通过重命名属性来打破常规映射UserId,例如

    public int CreatorId { get; set; }  // user that created the video, unrelated
    

    现在,这个属性实际上是不相关的,只是一个没有关系的标量属性。默认情况下,UpvotedVideos集合引入的关系现在是可选的,因为它在另一端没有必需的外键属性。

  • 使用 Fluent API 覆盖映射约定:

    modelBuilder.Entity<UserProfile>()
        .HasMany(u => u.UpvotedVidoes)
        .WithOptional()
        .Map(m => m.MapKey("UpvoterId")); // <- FK column name
    
  • 引入第二个(可为空的)属性并将其定义为带有数据注释的外键:

    [DataContract]
    public class Video
    {
        [Key]
        [DataMember(IsRequired = false)]
        public int VideoId { get; set; }
    
        [DataMember(IsRequired = false)]
        public int UserId { get; set; } // user that created the video, unrelated
    
        public int? UpvoterId { get; set; }
    
        [Required]
        [DataMember]
        public virtual IList<Tag> Tags { get; set; }
    }
    

    并在UserProfile

    [ForeignKey("UpvoterId")]
    public virtual List<Video> UpvotedVideos { get; set; }
    

可能还有更多变体,包括在 中添加导航属性Video等。但在所有情况下,必须将关系定义为可选的,以使您的代码正常工作。

旁注:关系不应该是多对多的顺便说一句吗?一个用户可以为很多视频点赞,而一个视频可以被很多用户点赞?但这是另一个问题...

于 2013-08-23T20:34:45.250 回答
0

代替

owner.UpvotedVideos.Remove(serverVideo);

尝试:

var video = owner.UpvotedVideos.Where(x => x == serverVideo).FirstOrDefault();
db.Videos.Remove(video); // notice, I'm removing it straight from my DB context
//so make sure you have Videos as a part of your DbContext
db.Entry(video).State = EntityState.Deleted; // tell the ChangeTracker you've removed somethings
db.SaveChanges();

当您尝试从 中删除时owner,EF 不允许在导航属性中使用空值。此外,当我尝试使外键成为可选时 - 我看到它们仍然保留在数据库中。我不需要所有这些孤儿记录,所以我使用了这个解决方案。

于 2013-11-02T21:45:31.120 回答