EF 代码首先映射 table 与来自相同 class 的 2 个 FK

EF code first mapping table with 2 FK's from same class

我正在尝试创建一个 Friendship 映射 table,它有 2 个来自同一个 class(用户)的 FK。在添加迁移时出现以下错误:

Unable to determine the principal end of an association between the types 'GameAPI.Models.UserFriendMap' and 'GameAPI.Models.User'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

这是我的两个模型 classes.

public class User
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public int UserID { get; set; }

    [Required]
    public string Username { get; set; }

    public ICollection<UserFriendMap> Friendships { get; set; }
}

public class UserFriendMap
{
    [Required]
    [Key]
    [Column(Order = 1)]
    public int UserID { get; set; }

    [Required]
    [Key]
    [Column(Order = 2)]
    public int FriendID { get; set; }

    [ForeignKey("UserID"), InversePropertyAttribute("Friendships")]
    public User User { get; set; }

    [ForeignKey("FriendID"), InversePropertyAttribute("Friendships")]
    public User Friend { get; set; }

    [Required]
    public DateTime FriendshipDate { get; set; }
}

我尝试重写 DbContext 中的 OnModelCreating 方法:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<UserFriendMap>()
        .HasKey(m => new { m.UserID, m.FriendID });

    modelBuilder.Entity<UserFriendMap>()
            .HasRequired(m => m.User)
            .WithMany()
            .HasForeignKey(m => m.UserID);

    modelBuilder.Entity<UserFriendMap>()
            .HasRequired(m => m.Friend)
            .WithMany()
            .HasForeignKey(m => m.FriendID);

}

这会导致显示新的错误消息。

Schema specified is not valid. Errors: The relationship 'GameAPI.Models.UserFriendMap_User' was not loaded because the type 'GameAPI.Models.User' is not available.

如有任何帮助,我们将不胜感激。提前致谢!!

我有办法解决你的问题,但你需要修改你的模型。为了创建好友关系,我创建了一个继承 User 的 Friend class。在这个class是我声明属性FriendshipTime的地方,你不需要为它声明一个Id,它使用User的Id。现在,在 User 中你应该有一个 Friend 的集合。

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

    [Required]
    public string Username { get; set; }

    public ICollection<Friend> Friendships { get; set; }
}

public class Friend:User
{
    public int UserID { get; set; }

    [ForeignKey("UserID"), InversePropertyAttribute("Friendships")]
    public User User { get; set; }

    [Required]
    public DateTime FriendshipDate { get; set; }
}

使用此模型,您可以执行以下操作:

 var user = new User()
          {
            Username = "User1",
            Friendships = new List<Friend>() { new Friend() { Username ="User2", FriendshipDate = DateTime.Now }, new Friend() { Username = "User3", FriendshipDate = DateTime.Now } }
          };

我想做的其他声明是在 Key 字段是 Integer 的情况下,Code First 默认为 DatabaseGeneratedOption.Identity。使用 Guid,您需要明确 配置这个。这些是您可以配置为的唯一类型 Code First 生成数据库时的标识

我总是尝试解决此类问题 "bottom up",即首先尝试没有任何映射(流畅或数据注释)的 class 模型:

public class User
{
    public int UserID { get; set; }
    public string Username { get; set; }
    public virtual ICollection<UserAssociation> Associations { get; set; }
}

public class UserAssociation
{
    public int UserID { get; set; }
    public int FriendID { get; set; }

    public DateTime FriendshipDate { get; set; }

    public virtual User User { get; set; }
    public virtual User Friend { get; set; }
}

(使用更中性的名称 UserAssociation)。

很快就失败了,因为没有为 UserAssociation 定义键。所以我添加了关键注释:

[Key, Column(Order = 1)]
public int UserID { get; set; }

[Key, Column(Order = 2)]
public int FriendID { get; set; }

现在模型已创建,但 UserIDFriendID 未用作外键。相反,EF 自己添加了两个外键字段。不好。所以我添加了外键指令:

[Key, Column(Order = 1)]
[ForeignKey("User"), InverseProperty("Associations")]
public int UserID { get; set; }

[Key, Column(Order = 2)]
[ForeignKey("Friend"), InverseProperty("Associations")]
public int FriendID { get; set; }

这导致异常:

Introducing FOREIGN KEY constraint ... may cause cycles or multiple cascade paths.

这是因为两个外键都是用级联删除创建的。 SQL 服务器不允许两个级联外键合而为一 table。防止级联删除只能通过流畅的映射来完成,所以我删除了所有注释(我不喜欢混合流畅的映射和注释)并添加了这些映射:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().HasMany(u => u.Associations)
                .WithRequired(a => a.User).HasForeignKey(a => a.UserID)
                .WillCascadeOnDelete(false);
    modelBuilder.Entity<User>().HasMany(u => u.Associations)
                .WithRequired(a => a.Friend).HasForeignKey(a => a.FriendID)
                .WillCascadeOnDelete(false);

    modelBuilder.Entity<UserAssociation>()
                .HasKey(a => new {a.UserID, a.FriendID});
}

现在是宾果游戏。如您所见,您很接近,但是您从协会方面开始了流畅的映射。此外,您使用 WithMany() 而不是 WithMany(u => u.Associations))。但是,使用这样的映射我无法让它工作(不知何故,EF 似乎没有及时知道 User class)。

另一点是,在添加数据时,EF 只接受新的 UserAssociation 如果它们被添加到相应的上下文 DbSet 中,而不是如果它们被添加到 User.Associations 中。此外,在现有用户之间创建关联时,我必须在新 UserAssociation 中设置原始外键值。设置对象引用导致

An error occurred while saving entities that do not expose foreign key properties for their relationships.

这没有意义,因为 UserAssociation 确实公开了这些关键属性。

所以看起来这些自引用的多对多关联与显式连接 class 我们只是勉强避免了 EF 存在的各种问题。感觉不太好。