实体框架“关系无法改变”,简单的一对多

时间:2013-08-23 19:18:11

标签: c# entity-framework entity-framework-4 ef-code-first

我正在尝试使用以下代码删除关系:

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

}

2 个答案:

答案 0 :(得分:1)

您在源代码中的评论:

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

无关?很可能这是所讨论的一对多关系的外键,因为映射约定会将其检测为外键,因为它与主键属性具有相同的名称UserId中的UserProfile。并且因为该属性具有不可为空的类型int,EF会将该关系检测为 required ,这意味着如果没有用户对其进行投票,则不会存在任何视频。

当您从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等中添加导航属性。但在所有情况下,必须将关系定义为可选,以使代码正常工作。

旁注:这种关系不应该是多对多的吗?用户可以投票支持许多视频,许多用户可以对视频进行投票吗?但这是另一个问题......

答案 1 :(得分: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不允许导航属性中的空值。此外,当我尝试将外键设为可选时 - 我看到它们仍然保留在数据库中。我不需要那里的所有孤儿记录,所以我使用了这个解决方案。