两个实体之间的代码优先外键
Code First Foreign Keys between two entities
好的,我在这里遗漏了什么,或者这只能通过数据注释来完成吗?
我有一个文档实体模型,它有一个指向添加文档的用户的外键(一对一关系):
[Table("Documents", Schema = "Configuration")]
public class Document : IPrimaryKey {
[Key]
public long Id { get; set; }
[Required]
public string OrginalName { get; set; }
[Required]
public DocumentTypes DocumentType { get; set; }
[Required]
public MIMETypes MIMEType { get; set; }
[Required]
public byte[] Data { get; set; }
[DefaultValue(false)]
public bool IsPublic { get; set; }
[Required]
public DateTimeOffset DateTimeAdded { get; set; }
[Required]
public long AddedByUser { get; set; }
[ForeignKey("AddedByUser")]
public virtual Details Details { get; set; }
}
然后我有一个用户(详细信息)实体,它可以有一个图像文件(存储在文档实体模型中(none|一对一关系):
[Table("Details", Schema = "User")]
public class Details : IPrimaryKey {
[Key]
public long Id { get; set; }
[Required]
public string UserId { get; set; }
[ForeignKey("UserId")]
public AppUser User { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Address> Addresses { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Email> Emails { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<PhoneNumber> PhoneNumbers { get; set; }
public ICollection<NotificationHistory> NotificationHistory { get; set; }
public long TimeZoneId { get; set; }
public long? ImageId { get; set; }
[ForeignKey("ImageId")]
public virtual Document Document { get; set; }
[ForeignKey("TimeZoneId")]
public virtual TimeZone TimeZone { get; set; }
}
当我尝试创建迁移时出现此错误:
Unable to determine the principal end of an association between the
types 'StACS.PeoplesVoice.DataAccessLayer.EntityModels.User.Details'
and
'StACS.PeoplesVoice.DataAccessLayer.EntityModels.Configuration.Document'.
The principal end of this association must be explicitly configured
using either the relationship fluent API or data annotations.
更新:
虽然仍在研究这个问题,但我做了两处更改并能够解决错误,但这在我的数据库中产生了意外结果。
在我添加的文档实体中:
public virtual ICollection<Details> Details { get; set; }
在我添加的详细信息(用户)实体中:
puflic virtual ICollection<Document> Documents { get; set; }
在我的数据库 Tables 中,我现在在我想要的字段上有外键,但我分别有一个辅助外键。
我试过只删除单个虚拟引用并只留下 ICollection 虚拟引用,现在我根本没有外键。
已更新 (基于 Akash Kava 建议):
我做了以下修改
[Table("Documents", 架构 = "Configuration")]
public class 文档:IPrimaryKey {
[必需的]
public string OrginalName { get;放; }
[Required]
public DocumentTypes DocumentType { get; set; }
[Required]
public MIMETypes MIMEType { get; set; }
[Required]
public byte[] DocumentData { get; set; }
[DefaultValue(false)]
public bool IsPublic { get; set; }
[Required]
public DateTimeOffset DateTimeAdded { get; set; }
[Required]
public long AddedByUser { get; set; }
[Key]
public long Id { get; set; }
[ForeignKey("AddedByUser")]
[InverseProperty("Image")]
public virtual Details User { get; set; }
}
[Table("Details", Schema = "User")]
public class Details : IPrimaryKey {
[Required]
public string UserId { get; set; }
[ForeignKey("UserId")]
public AppUser User { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Address> Addresses { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Email> Emails { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<PhoneNumber> PhoneNumbers { get; set; }
public ICollection<NotificationHistory> NotificationHistory { get; set; }
public long TimeZoneId { get; set; }
public long? ImageId { get; set; }
[ForeignKey("ImageId")]
[InverseProperty("User")]
public Document Image { get; set; }
[ForeignKey("TimeZoneId")]
public virtual TimeZone TimeZone { get; set; }
[Key]
public long Id { get; set; }
}
我注释掉了 Fluent API 代码
Unable to determine the principal end of an association between the
types 'StACS.PeoplesVoice.DataAccessLayer.EntityModels.User.Details'
and
'StACS.PeoplesVoice.DataAccessLayer.EntityModels.Configuration.Document'.
The principal end of this association must be explicitly configured
using either the relationship fluent API or data annotations.
好的,我终于明白了。遗憾的是,这不是很直接,因为我认为数据注释应该可以工作,但事实并非如此。
你必须使用 Fluent API:
modelBuilder.Entity<Details>()
.HasOptional(x => x.Document)
.WithMany()
.HasForeignKey(x => x.ImageId);
modelBuilder.Entity<Document>()
.HasRequired(x => x.User)
.WithMany()
.HasForeignKey(x => x.AddedByUser);
您也可以使用数据注释实现相同的效果,您缺少 InverseProperty
属性,它解决了这种情况下的歧义。从概念上讲,每个导航 属性 都有反向导航 属性,EF 会根据类型自动检测并假定反向 属性,但是如果两个实体通过多个 FK 属性相互关联,则必须在相应的导航属性上明确指定 InverseProperty 属性。
我建议在每个导航上放置 InverseProperty 属性,这有助于减少 EF 的启动时间,因为 EF 不必确定和验证模型。
示例,
public class AccountEmail {
public long AccountID {get;set;}
// Inverse property inside Account class
// which corresponds to other end of this
// relation
[InverseProperty("AccountEmails")]
[ForeignKey("AccountID")]
public Account Account {get;set;}
}
public class Account{
// Inverse property inside AccountEmail class
// which corresponds to other end of this
// relation
[InverseProperty("Account")]
public ICollection<AccountEmail> AccountEmails {get;set;}
}
我编写了一个文本模板,它根据当前架构生成所有这些导航属性。从 https://github.com/neurospeech/atoms-mvc.net/tree/master/db-context-tt 下载所有三个文件,您可能需要对其进行自定义,因为它会根据我们的框架添加更多内容,但它会直接从您的数据库生成纯代码模型。
好的,我在这里遗漏了什么,或者这只能通过数据注释来完成吗?
我有一个文档实体模型,它有一个指向添加文档的用户的外键(一对一关系):
[Table("Documents", Schema = "Configuration")]
public class Document : IPrimaryKey {
[Key]
public long Id { get; set; }
[Required]
public string OrginalName { get; set; }
[Required]
public DocumentTypes DocumentType { get; set; }
[Required]
public MIMETypes MIMEType { get; set; }
[Required]
public byte[] Data { get; set; }
[DefaultValue(false)]
public bool IsPublic { get; set; }
[Required]
public DateTimeOffset DateTimeAdded { get; set; }
[Required]
public long AddedByUser { get; set; }
[ForeignKey("AddedByUser")]
public virtual Details Details { get; set; }
}
然后我有一个用户(详细信息)实体,它可以有一个图像文件(存储在文档实体模型中(none|一对一关系):
[Table("Details", Schema = "User")]
public class Details : IPrimaryKey {
[Key]
public long Id { get; set; }
[Required]
public string UserId { get; set; }
[ForeignKey("UserId")]
public AppUser User { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Address> Addresses { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Email> Emails { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<PhoneNumber> PhoneNumbers { get; set; }
public ICollection<NotificationHistory> NotificationHistory { get; set; }
public long TimeZoneId { get; set; }
public long? ImageId { get; set; }
[ForeignKey("ImageId")]
public virtual Document Document { get; set; }
[ForeignKey("TimeZoneId")]
public virtual TimeZone TimeZone { get; set; }
}
当我尝试创建迁移时出现此错误:
Unable to determine the principal end of an association between the types 'StACS.PeoplesVoice.DataAccessLayer.EntityModels.User.Details' and 'StACS.PeoplesVoice.DataAccessLayer.EntityModels.Configuration.Document'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
更新:
虽然仍在研究这个问题,但我做了两处更改并能够解决错误,但这在我的数据库中产生了意外结果。
在我添加的文档实体中:
public virtual ICollection<Details> Details { get; set; }
在我添加的详细信息(用户)实体中:
puflic virtual ICollection<Document> Documents { get; set; }
在我的数据库 Tables 中,我现在在我想要的字段上有外键,但我分别有一个辅助外键。
我试过只删除单个虚拟引用并只留下 ICollection 虚拟引用,现在我根本没有外键。
已更新 (基于 Akash Kava 建议):
我做了以下修改 [Table("Documents", 架构 = "Configuration")] public class 文档:IPrimaryKey { [必需的] public string OrginalName { get;放; }
[Required]
public DocumentTypes DocumentType { get; set; }
[Required]
public MIMETypes MIMEType { get; set; }
[Required]
public byte[] DocumentData { get; set; }
[DefaultValue(false)]
public bool IsPublic { get; set; }
[Required]
public DateTimeOffset DateTimeAdded { get; set; }
[Required]
public long AddedByUser { get; set; }
[Key]
public long Id { get; set; }
[ForeignKey("AddedByUser")]
[InverseProperty("Image")]
public virtual Details User { get; set; }
}
[Table("Details", Schema = "User")]
public class Details : IPrimaryKey {
[Required]
public string UserId { get; set; }
[ForeignKey("UserId")]
public AppUser User { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Address> Addresses { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<Email> Emails { get; set; }
[CollectionRequired(MinimumCollectionCount = 1)]
public ICollection<PhoneNumber> PhoneNumbers { get; set; }
public ICollection<NotificationHistory> NotificationHistory { get; set; }
public long TimeZoneId { get; set; }
public long? ImageId { get; set; }
[ForeignKey("ImageId")]
[InverseProperty("User")]
public Document Image { get; set; }
[ForeignKey("TimeZoneId")]
public virtual TimeZone TimeZone { get; set; }
[Key]
public long Id { get; set; }
}
我注释掉了 Fluent API 代码
Unable to determine the principal end of an association between the types 'StACS.PeoplesVoice.DataAccessLayer.EntityModels.User.Details' and 'StACS.PeoplesVoice.DataAccessLayer.EntityModels.Configuration.Document'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
好的,我终于明白了。遗憾的是,这不是很直接,因为我认为数据注释应该可以工作,但事实并非如此。
你必须使用 Fluent API:
modelBuilder.Entity<Details>()
.HasOptional(x => x.Document)
.WithMany()
.HasForeignKey(x => x.ImageId);
modelBuilder.Entity<Document>()
.HasRequired(x => x.User)
.WithMany()
.HasForeignKey(x => x.AddedByUser);
您也可以使用数据注释实现相同的效果,您缺少 InverseProperty
属性,它解决了这种情况下的歧义。从概念上讲,每个导航 属性 都有反向导航 属性,EF 会根据类型自动检测并假定反向 属性,但是如果两个实体通过多个 FK 属性相互关联,则必须在相应的导航属性上明确指定 InverseProperty 属性。
我建议在每个导航上放置 InverseProperty 属性,这有助于减少 EF 的启动时间,因为 EF 不必确定和验证模型。
示例,
public class AccountEmail {
public long AccountID {get;set;}
// Inverse property inside Account class
// which corresponds to other end of this
// relation
[InverseProperty("AccountEmails")]
[ForeignKey("AccountID")]
public Account Account {get;set;}
}
public class Account{
// Inverse property inside AccountEmail class
// which corresponds to other end of this
// relation
[InverseProperty("Account")]
public ICollection<AccountEmail> AccountEmails {get;set;}
}
我编写了一个文本模板,它根据当前架构生成所有这些导航属性。从 https://github.com/neurospeech/atoms-mvc.net/tree/master/db-context-tt 下载所有三个文件,您可能需要对其进行自定义,因为它会根据我们的框架添加更多内容,但它会直接从您的数据库生成纯代码模型。