如何在不向Principal模型添加导航属性的情况下在引用的模型中添加外键

时间:2018-05-18 15:23:05

标签: c# sql-server entity-framework entity-framework-6 ef-code-first

我在Parent和Child模型之间有一对多的关系。按照项目的约定,我需要创建一个单独的类来映射从EntityTypeConfiguration类扩展的每个实体。

public class Parent
{
    public int Id { get; set; }
    [Required]
    public string ParentName { get; set; }
    //public IEnumerable<Child> Children { get; set; }
}

public class Child
{
    [Key]
    public int ChildId { get;  set; }

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

    [ForeignKey]
    public int CustomParentId { get; set; }

    public Parent Parent { get; set; }
}

public class ChildMap : EntityTypeConfiguration<Child>
{

}

如何在不将Child导航属性添加到Parent模型的情况下在ChildMap中添加外键?

1 个答案:

答案 0 :(得分:1)

为了清晰起见,将我的评论扩展为答案。子记录可以包含对没有父集合的父级的引用,并且可以在子级中指定父级ID字段或不使用父级ID字段的情况下完成:

public class ParentMap : EntityTypeConfiguration<Parent>
{
  ToTable("Parents");
  HasKey(x => x.Id)
    .Property(x => x.Id)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}

//With Parent ID in Child:
public class ChildMap : EntityTypeConfiguration<Child>
{
  ToTable("Children");
  HasKey(x => x.ChildId)
    .Property(x => x.ChildId)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

  HasRequired(x => x.Parent)
    .WithMany()
    .HasForeignKey(x => CustomParentId);
}

// Without Parent ID in child. (Column in table called "CustomParentId")
public class ChildMap : EntityTypeConfiguration<Child>
{
  ToTable("Children");
  HasKey(x => x.ChildId)
    .Property(x => x.ChildId)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

  HasRequired(x => x.Parent)
    .WithMany()
    .Map(x => x.MapKey("CustomParentId"));
}

有关您实体的一个重要细节: 在您的Child类中,Parent属性需要标记为虚拟以启用延迟加载。例如:

public class Child
{
  // .. Without virtual
  public Parent Parent { get; set; }
}

//vs.
public class Child
{
  // .. With virtual
  public virtual Parent Parent { get; set; }
}

如果您使用以下代码加载孩子......

var child = myContext.Children
  .SingleOrDefault(x => x.ChildId == 1);

然后使用...

访问父级
string parentName = child.Parent?.Name;

如果您没有虚拟父级,则不会加载父级引用,因此Parent将为#null。如果Parent是虚拟的,那么将进行延迟加载调用以检索父级,并且您将获得关联的实体。延迟加载通常是有帮助的,但效率不高,因为它可能导致第二次往返数据库。在这两种情况下,如果你这样做:

var child = myContext.Children
  .Include(x => x.Parent)
  .SingleOrdefault(x => x.ChildId == 1);

然后,父引用将被急切加载并可访问,虚拟或否。

另外,检索数据的更好方法是使用单独的视图模型,并从数据和相关数据中选择所需的字段。例如,使用viewmodel返回具有父ID和名称的子详细信息:

var child = myContext.Children
  .Select(x => new ChildSummaryViewModel 
  {
    ChildId = x.ChildId,
    Name = x.Name,
    ParentId = x.Parent.Id,
    ParentName = x.Parent.Name
  })
  .SingleOrdefault(x => x.ChildId == 1);

这不需要父项上的.Include(),并将从相关父项加载详细信息。在你有一个相关实体的情况下,这似乎不太实际,但是在你处理具有许多相关实体的大型实体的情况下,这可以大大减少查询时间和触发额外延迟加载查询的风险,并减少从数据库发回的数据大小。 (而不是整个实体,只是你关心的属性。)

更新实体时,应使用.Include()相关实体来进行调整或改变关系。