关系错误

时间:2013-06-25 11:55:12

标签: asp.net-mvc entity-framework ef-code-first

我有一个User表和一个Avatar表。一个用户可以拥有许多头像(或null)。但是我需要标记当前的哪个头像,所以我在User表中有一个Avatar_Id,它是当前的头像。和“阿凡达”中的ForeignKey User_Id告诉我哪个用户是所有者。

当我尝试填充一些数据以测试关系时,尝试这样做会给我带来很多错误和麻烦。

public class User
{
    [Key]
    public int Id { get; set; }
    public Avatar Avatar { get; set; }
    public virtual ICollection<Avatar> Avatars { get; set; }
}

public class Avatar
{
    [Key, ForeignKey("User")]
    public int Id { get; set; }
    public User User { get; set; }
}

测试部分:

var user = new User();
var avatar = new Avatar()
{
    User = user
};
// user.Avatar = avatar; // <- this gives [a circular] error; without this I have null.
db.Users.Add(user);
db.Avatars.Add(avatar);
db.SaveChanges();

这导致我Avatar_Id = NULL表中的UserUser_Id = NULL表中的Avatar。我希望这些字段填满(好吧,Avatar_Id可以为null)。

3 个答案:

答案 0 :(得分:1)

最好在表中使用头像制作布尔字段'IsDefault'并检查,同时添加/更新头像,不再为此用户提供默认头像。您也可以在头像类中添加相同的属性。

答案 1 :(得分:1)

@Fabricio我不能在发布之前测试这段代码,但我相信它会起作用。

public class User
{
    [Key]
    public int UserId { get; set; }

    public int AvatarId { get; set; }

    [ForeignKey("AvatarId")]
    public Avatar Avatar { get; set; }

    public ICollection<Avatar> Avatars { get; set; }
}

public class Avatar
{
    [Key]
    public int AvatarId { get; set; }

    [ForeignKey("User")]
    public int UserId { get; set; }

    public User User { get; set; }
}

问题是你把两个外键合并为一个。现在你在Avatar表中有一个外键,在User表中有一个外键,每个表示一种关系模式。 外键"AvatarId"表示外键的特殊形式,唯一的+外键(构建一对一关系的第二种形式)。您可以在此处详细了解此信息:http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx

答案 2 :(得分:0)

我已经给了这个一点但是因为我曾经模拟了一个类似的案例,并且不介意重新评估这些选项。

仔细看看你的处所:

  

一个用户可以拥有多个头像(或null)

这个简短的句子意味着一对一关联User-Avatar必须是双向可选的,因为没有 User的{​​{1}} 不能可能会引用一个自己的头像,当用户有多个头像时,只有其中一个可以将Avatar称为用户的默认值。 (它们都将用户称为所有者)。

因此,您只能将其建模为0..1 - 0..1关联。因此User的主键不能是用户的外键。 (无论如何,否则用户只能拥有一个头像)。

如果这不会引起臭名昭着的“可能导致周期或多个级联路径”异常,那么这可能是由Jonny Piazzi的模型完成的。用户和阿凡达都互相引用,你必须明确告诉EF哪些FK没有级联。这只能通过流畅的映射来完成:

Avatar

这会在用户中放置一个可以为空的非级联FK列protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<User>() .HasOptional(u => u.Avatar) .WithOptionalDependent() .Map(m => m.MapKey("AvatarId")) .WillCascadeOnDelete(false); ... } (这就是AvatarId依赖于User)的原因。


现在你的第二个问题是填充模型时的鸡 - 例如问题。

只有在您调用Avatar两次并将这些调用包装在事务范围内时,才能执行此操作。例如:

SaveChanges

现在EF可以在通过外键引用它之前决定首先生成哪个键(using (var tran = new TransactionScope()) { var user = new User(); var avatar = new Avatar(); user.Avatars = new HashSet<Avatar>(); user.Avatars.Add(avatar); user.Avatars.Add(new Avatar()); user.Avatars.Add(new Avatar()); db.Users.Add(user); db.SaveChanges(); user.Avatar = avatar; // set FK db.SaveChanges(); tran.Complete(); } )。随后在User中设置FK。

但......这是最好的模特吗?

也许,也许不是。

问题在于,您的模型不会强制执行业务规则,即用户只能将其自身的某个头像作为默认头像。 User可以参考任何头像。因此,您必须编写业务逻辑来强制执行业务规则。

使用YD1m的解决方案(无User.AvatarId,但列User.AvatarId),此业务规则将被隐式强制执行。但是现在你必须编写业务逻辑来强制只有一个头像是默认的。

由你决定你认为哪种更可行。

(对于记录:回过头来,我采用了后一种选择)