我有一个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
必须是双向可选的,因为没有Avatar
s的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
(这就是为什么User
是Avatar
的依赖项)。
现在是第二个问题,填充模型时的鸡的问题。
只有当您两次调用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
),该业务规则是隐式执行的。但现在您必须编写业务逻辑来强制执行只有一个化身是默认的。
由你来决定你认为什么更可行。
(记录在案:很久以前,我选择了后一个选项)