Rudi Visser
Rudi Visser

Reputation: 21979

Entity Framework Core ignoring .Include(..) without .ToList(..) indirectly

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:

Workaround:

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

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

Upvotes: 9

Views: 6714

Answers (5)

DarkFinger
DarkFinger

Reputation: 131

by naming convention, it should be worked you can try using this piece of code to manually configure relations between your models

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<TestEntityA>()
        .HasOne(x => x.TestEntityB)
        .WithMany()
        .HasForeignKey(x => x.TestEntityBId);
}

Upvotes: 1

Mojtaba
Mojtaba

Reputation: 3513

I am not sure if this answers your question but it is kind of a workaround.

change BProperty to be a method like getBProperty

public string getBProperty (TestEntityB tb) {  return tb.Property;  }

and then write your query like

context.TestEntityAs
    //.Include(m => m.TestEntityB) --- you don't need include anymore!
    .Any(m => m.getBProperty(m.TestEntityB)) == "Hello World");

Upvotes: 0

Yeskendir Toleubekov
Yeskendir Toleubekov

Reputation: 168

I'm not sure but try [ForeignKey(nameof(TestEntityBId ))]

public class TestEntityA
{
    public int Id { get; set; }
    public int TestEntityBId { get; set; }
    [ForeignKey(nameof(TestEntityBId ))] 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; }
}

Upvotes: 0

Eduard Blahoi
Eduard Blahoi

Reputation: 21

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;

Upvotes: 0

IvanJazz
IvanJazz

Reputation: 773

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

Upvotes: 0

Related Questions