3

The abridged version of my WebApi controller is like so:

[HttpGet, Route("")]
public async Task<IHttpActionResult> Search(bool includeEntities)
{
    IQueryable<VersionTopic> results = DbContext.VersionTopics;
    if (includeEntities)
    {
        results = results.Include(o => o.CreatedBy);
        results = results.Include(o => o.LastSavedBy);
        results = results.Include(o => o.Topic.LastSavedBy);
        results = results.Include(o => o.Topic.CreatedBy);
        results = results.Include(o => o.Topic.PACKType.LastSavedBy);
        // etc...
    }

    results = results.OrderBy(o => o.SortOrder);

    return Ok(result.ToList());
}

For some reason, the LastSavedBy entity is ALWAYS populated, even when the includeEntities parameter is false.

Why would it be eager loading just this one entity but none of the others (as is required)?

Here's a screenshot:

enter image description here

My model is defined as so:

public class VersionTopic
{
    [Key]
    [Required]
    public Guid VersionTopicId { get; set; }

    [Required]
    [Index("IX_VersionTopic_VersionId_TopicId", IsUnique = true, Order = 0)]
    public Guid VersionId { get; set; }

    [Required]
    [Index("IX_VersionTopic_VersionId_TopicId", IsUnique = true, Order = 1)]
    public Guid TopicId { get; set; }

    [Required(AllowEmptyStrings = true)]
    [MaxLength(250)]
    public string Name { get; set; }

    public string KeyMessage { get; set; }

    [Required]
    public int SortOrder { get; set; }

    [Required]
    public Guid CreatedById { get; set; }

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

    [Required]
    public Guid LastSavedById { get; set; }

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

    public virtual ICollection<VersionRecommendation> VersionRecommendations { get; set; } = new List<VersionRecommendation>();

    [ForeignKey("CreatedById")]
    public virtual ApplicationUser CreatedBy { get; set; }

    [ForeignKey("LastSavedById")]
    public virtual ApplicationUser LastSavedBy { get; set; }

    [ForeignKey("TopicId")]
    public virtual Topic Topic { get; set; }

    [ForeignKey("VersionId")]
    public virtual Version Version { get; set; }

    public VersionTopic()
    {
        VersionTopicId = Guid.NewGuid();
    }
}
Sean
  • 14,359
  • 13
  • 74
  • 124
  • Lazy loading effect? Is the `LastSavedBy` property marked `virtual`? – Ivan Stoev Mar 20 '17 at 11:41
  • @IvanStoev it is marked `virtual`, but then so are all the other related entities. Why is it that only this particular one is being included? I thought I had to explicitly include them. – Sean Mar 20 '17 at 12:06
  • Have you run a DB profiler? You will probably get requests for the last SavedBy user when you return the lost. With the include the SQL is probably more efficient. Lazy loading does not mean non-loading. It means it is loaded when needed as opposed to in advance. When you return the result, the data _is_ needed and therefore requested from the DB. – Sefe Mar 20 '17 at 12:13
  • 1
    Actually there could be another reason, if some `ApplicationUser` entity is already tracked by (loaded in) the context, the navigation property fixup will populate the corresponding member even w/o specifying `Include`. You can see the **Tip** in the [Loading Related Data - Eager Loading](https://learn.microsoft.com/en-us/ef/core/querying/related-data) of the EF Core documentation, the same applies to EF6. – Ivan Stoev Mar 20 '17 at 12:14
  • @Sefe it has the exact same logic as other related entities, which don't get loaded. – Sean Mar 20 '17 at 12:36
  • @IvanStoev I think you're onto something. It's only loading the `LastSavedBy` entities when it involves my details, i.e. me being the logged in user. So the Authentication is initially loading my ApplicationUser details when it checks my login. Seems it's then attaching my details when it sees my UserId in a related entity. The the SQL sent to the database doesn't even touch the ApplicationUser table. So it must be getting the data elsewhere. Any idea whether you can disable this from happening? – Sean Mar 20 '17 at 12:37
  • Unfortunately it's "by design" and cannot be controlled :( The only solution is fresh new context (or manually cleaning it up - weird). – Ivan Stoev Mar 20 '17 at 12:45
  • When some are loaded and some are not, don't look into lazy loading. Lazy/eager loading does not decide, _if_ an entity is loaded, but _when_. – Sefe Mar 20 '17 at 12:51
  • @IvanStoev thanks. Do you want to copy your comment into an answer and I'll mark it answered? It's an useful gotcha as it bloats my JSON traffic, so someone else might want to know. – Sean Mar 20 '17 at 12:52
  • No, most probably it's a duplicate. For sure It has been asked for EF Core [here](http://stackoverflow.com/questions/42310340/can-i-stop-entity-framework-core-from-populating-my-result-with-partial-data) and referred [here](http://stackoverflow.com/questions/42327515/ef-core-returns-null-relations-untill-direct-access). But may be you right, I don't see similar for EF6 and below, so let me compose an answer. – Ivan Stoev Mar 20 '17 at 13:09

1 Answers1

4

It's basically explained in the following Tip from Loading Related Data - Eager Loading section of the EF Core documentation, but the same applies to EF6 and below:

Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

Unfortunately this behavior is not controllable, so the only options to avoid it is to use fresh new DbContext (or manually cleaning up the navigation properties which are not needed, but that's annoying and easy to forget).

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343