Entity Framework Core ignoring .Include(..) withou

2020-07-08 07:15发布

As noted in "Loading Related Data" from EF Core Documentation we can use .Include(..) to Eagerly Load navigation properties from the DbSet (or generic IQueryable<T> linking back to an EF context).

This means that, given the following models:

public class TestEntityA
{
    public int Id { get; set; }
    public int TestEntityBId { get; set; }
    public TestEntityB TestEntityB { get; set; }

    public string BProperty { get { return TestEntityB.Property; } }
}

public class TestEntityB
{
    public int Id { get; set; }
    public string Property { get; set; }
}

.. then code such as the following should work:

context.TestEntityAs
    .Include(m => m.TestEntityB)
    .Any(m => m.BProperty == "Hello World");
    /*
     * Note that the below DOES work by using the nav property directly
     * in the query, but that is not always going to be an option for
     * whatever reason. If it's .Included it should be available through
     * INNER JOINing it into the base query before the .Any runs.
     * .Any(m => m.TestEntityB.Property == "Hello World");
     */

However it doesn't.

I note that there is a caveat where .Include() could be ignored should a query not return the type that is initially requested:

If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored. [snip] By default, EF Core will log a warning when include operators are ignored.

I'm not sure how, in the above call to .Any() that is relevant. Yes, the query is not returning the original type (it's returning a bool of course) but at the same time, the Warning is also not logged to advise that it is being ignored.

My questions here are:

  • Is this a usage case that is expected to work? Should I raise a bug in EF Core?
  • If it's not expected, a workaround is as below (to call .ToList()) but that would obviously load everything, to find out if we have anything on a .Any() which could easily be a query (and would be as such in EF6). What is a workaround to get this .Any() to work on the server side thus not requiring the ToList to put it in memory?

Workaround:

context.TestEntityAs
    .Include(m => m.TestEntityB)
    .ToList()
    .Any(m => m.BProperty == "Hello World");

Full reproducible sample: https://gist.github.com/rudiv/3aa3e1bb65b86ec78ec6f5620ee236ab

2条回答
forever°为你锁心
2楼-- · 2020-07-08 07:55

The behaviour is expected but you can use explicit loading for more efficient query as shown below.

2 separate queries, but no need to load all of TestEntityBs

// First query
var testEntityAs = context.TestEntityAs.ToList();
var testEntityAsIds = testEntityAs.Select(t => t.Id);

// Second query, can apply filter of Hello World without loading all TestEntityBs
context.TestEntityBs
    .Where(t => testEntityAsIds.Contains(t.Id) && t.Property == "Hello World")
    .Load();

// In memory check
var isAny = testEntityAs.Any(t => !string.IsNullOrEmpty(t.BProperty));
查看更多
时光不老,我们不散
3楼-- · 2020-07-08 08:01

Depending on the data, likely the efficient way to achieve that would be to load a single record:

context.TestEntityAs
    .Include(m => m.TestEntityB)
    .Where(m => m.BProperty == "Hello World")
    .FirstOrDefault() != null;
查看更多
登录 后发表回答