关系出错



我有一个User表和一个Avatar表。一个用户可以有多个化身(或空)。但我需要标记哪个头像是当前的,所以我在用户表中有一个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();

这导致我在User表中使用了Avatar_Id = NULL,在Avatar表中使用User_Id = NULL。我希望这些字段被填充(好吧,Avatar_Id可以为空)。

最好在带有头像的表中创建布尔字段"IsDefault",并在添加/更新该用户不再有默认头像的头像时进行检查。你也可以在化身类中添加相同的属性。

@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

我给出了一些提示,因为我曾经对类似的案例进行过建模,不介意重新评估选项。

仔细查看您的经营场所:

一个用户可以有许多化身(或空)

这句短句意味着一对一关联User-Avatar必须是双向可选的,因为没有Avatars的User不可能指代其自己的化身,并且当用户有多个化身时,其中只有一个可以将User指代为用户的默认化身。(它们都将用户称为所有者)。

因此,您只能将其建模为0..1–0..1关联。所以Avatar的主键对用户来说不能是外键。(无论如何都不能,否则用户只能有一个头像)。

如果Jonny Piazzi的模型没有抛出臭名昭著的"可能导致循环或多个级联路径"异常,那么这可能是由Jonny Piizzi的模型完成的。user和Avatar都是相互引用的,您必须明确地告诉EF哪一个FK不是级联的。这只能通过流畅的映射来完成:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
      .HasOptional(u => u.Avatar)
      .WithOptionalDependent()
      .Map(m => m.MapKey("AvatarId"))
      .WillCascadeOnDelete(false);
    ...
}

这在User中放置了一个可为null的、非级联的FK列AvatarId(这就是为什么UserAvatar的依赖项)。


现在是第二个问题,填充模型时的鸡的问题。

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

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

现在EF可以决定先生成哪个密钥(User的),然后再通过外键引用它。随后在User中设置FK。

但是。。。这是最好的型号吗

也许,也许不是。

问题是,您的模型没有强制执行业务规则,即用户只能将自己的一个头像作为默认头像。User.AvatarId可以指代任何化身。因此,您必须编写业务逻辑来执行业务规则。

使用YD1m的解决方案(没有User.AvatarId,但有一列Avatar.IsDefault),该业务规则是隐式执行的。但现在您必须编写业务逻辑来强制执行只有一个化身是默认的。

由你来决定你认为什么更可行。

(记录在案:很久以前,我选择了后一个选项)

相关内容

最新更新