Search code examples
c#ef-code-firstentity-framework-4.1entity-relationshipnavigation-properties

Navigation Property not loading when only the ID of the related object is populated


I am trying to establish a many-to-one relationship. The entity that represents the “many” has a navigation property pointing back to the parent entity. It looks like this:

public abstract class BaseEntity
{

    /// <summary>
    /// Key Field for all entities
    /// </summary>
    /// 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }


    /// <summary>
    /// Date entity was created
    /// </summary>
    public DateTime DateCreated { get; set; }

    /// <summary>
    /// Last date Modified
    /// </summary>
    public DateTime DateModified { get; set; }

    /// <summary>
    /// keep track of Row Version used for concurrency
    /// </summary>
    [Timestamp]
    public Byte[] RowVersion { get; set; }

}

public abstract class Document : BaseEntity
{
    #region Primitive Properties   


    /// <summary>
    /// Boolean value to determine if Document is in an active state
    /// </summary>
    public bool IsActive { get; set; }

    /// <summary>
    /// Document comments and information
    /// </summary>
    [Required]
    public string Description { get; set; }

    #endregion

    #region Navigation Properties

    public ICollection<Comment> Comments { get; set; }

    /// <summary>
    /// FK back to User who owns document
    /// </summary>
    //public Guid OwnerId { get; set; }

    public Guid OwnerId { get; set; }
    /// <summary>
    /// Navigation Back to User who owns document
    /// </summary>
    public User Owner { get; set; }

    #endregion
}

public class Project : BaseEntity
{
    public string Name { get; set; }
    public string ProjectNumber { get; set; }
    public string Description { get; set; }

    public string CreatedBy { get; set; }
    public string ModifiedBy { get; set; }
    public string Currency { get; set; }

    #region Navigation Properties

    public virtual Address Address { get; set; }
    public virtual CompanyCode CompanyCode { get; set; }
    public virtual ICollection<Contact> TeamMembers { get; set; }

    #endregion
}    

 public class Rfi : Document
 {
    public string Number { get; set; }

    #region Navigation Properties

    //This points back to a Project Entity
    public virtual Guid ProjectId { get; set; }
    public virtual Project Project { get; set; }

    #endregion
}

So, when I insert the above entity, I am passing the ProjectId from the application into the Rfi entity (not the entire Project entity). Everything saves fine. The issue I am having is, when I pull the Rfi object back out of the database, the ProjectId is being populated, but the Project entity is null. I am using Lazy Loading, by default. Do I need to specify a navigation property on the Project entity, too? I don’t really want to. Unless, I can perform a mapping on my Rfi to accomplish this.

Update: I assumed EF 4.1 would load my objects for me, but it seems, sometimes I need to explicitly include what objects I want to load. I am not entirely sure why. I am using a repository to query my entities. Here is the method I used to query the Rfi object:

    public IQueryable<TEntity> GetQuery(Expression<Func<TEntity, bool>> predicate)
    {
       return _context.Set<TEntity>().AsQueryable();
    }

What I ended up doing, in my Service layer I call it like this:

public Rfi FindByNumber(string number)
{
     var rfi = rfiRepository.GetQuery(r => r.Number == number).Include(r => r.Project).Single;
     return rfi
}

Solution

  • Turns out the mapping I created for the proeprty was creating a duplicate ID in the database. I had, for example, ProjectId and Project_ID in the database. I was populating ProjectId when a new item was saved to the context, but the _ID was not being populated. This is what EF 4.1 is using to relate the data. In my mapping I was tring to set the Project so it would not CascadeOnDelete. This is what my mapping looks like:

    HasOptional(rfi => rfi.Project)
        .WithOptionalDependent()
        .WillCascadeOnDelete(false);
    

    This mapping was creating 2 IDs in the database. Once I removed the mapping everything was working. I just need to figure out the correct mapping so I can remove the CascadeOnDelete, make the property optional, and only have one ID.

    I figured if out with the Help of EF Power Tools. You can reverse engineer your DB into POCOs. I changed the above line to:

    HasOptional(r => r.Project)
        .WithMany()
        .HasForeignKey(r => r.ProjectId)
        .WillCascadeOnDelete(false);
    

    Even with a fluent interface mappings are a bit difficult to master. To understand how relationships are mapped in EF I created a simple database with my tables and foreign key assignments. I then used the Power Tools’ option for reverse engineering code first. Brilliant!