gvd
gvd

Reputation: 1592

How can I add Foreign Key in referenced model without adding navigation property to Principal model

I have a one-to-many relationship between Parent and Child models. Following the convention of the project I need create a separate class to map each entity extending from EntityTypeConfiguration class.

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>
{

}

How can I add Foreign Key in ChildMap without adding Child navigation property to Parent model?

Upvotes: 0

Views: 915

Answers (1)

Steve Py
Steve Py

Reputation: 34908

Expanding my comment into an answer for clarity. Child records can contain a reference to their parent without a parent collection, and can be done with or without a parent ID field designated in the child:

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

One important detail about your entities: In your Child class, the Parent property needs to be marked as virtual to enable lazy loading. For instance:

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

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

If you use the following code to load a child...

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

then go to access the parent with...

string parentName = child.Parent?.Name;

In the case where you don't have a virtual parent, the Parent reference was not loaded so Parent will be #null. If Parent was virtual then a lazy load call would be made to retrieve the parent and you'd get the associated entity. Lazy loading in general is helpful, but not very efficient as it results in a likely second round-trip to the database. In both cases if you did:

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

Then the parent reference would be eager loaded and accessible, virtual or no.

Alternatively a better way to retrieve data is to use a separate view model and select the fields you want from the data and related data. For instance using a viewmodel that returns the child details with the parent ID and name:

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

This does not need the .Include() on the parent, and will load details from the related parent. In the case where you have one related entity this won't seem that practical, but in cases where you are dealing with large entities with many related entities this can greatly reduce the query time and risks of triggering extra lazy load queries, plus reduce the size of data sent back from the database. (rather than entire entities, just the properties you care about.)

When updating entities you should use .Include() related ones to make adjustments or alter relationships.

Upvotes: 1

Related Questions