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