EF Core attaching all entities while adding query on existing query

Viewed 108

I have some complex scenarios, but I will show them here as simple. So, my context class is pretty simple:

public class AppDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("data source=DILSHODKPC;integrated security=True;Database=ODataTEst; MultipleActiveResultSets=true");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var student = modelBuilder.Entity<Student>();
        student.HasKey(s => s.Id);
    }

    public IQueryable<T> GetEntities<T>() where T : class
    {
        return Set<T>();
    }
}

My model is simple too:

public class Student
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public int Score { get; set; }
}

I have 8 records on the database:

records

My problem is when I add a query to the existing entity framework is tracking all entities. Look at two scenarios:

Scenario 1:

AppDbContext appDbContext = new AppDbContext();
var query1 = appDbContext.GetEntities<Student>().Where(s => s.Score > 3);
var query2 = query1.Where(s => s.Name == "Messi");
var loadedEntitiesCount = query2.ToList().Count();//Count is 4. It is good
var trackedEntitiesCount = appDbContext.ChangeTracker.Entries<Student>().Select(e => e.Entity).Count();//Count is 4. It is good

In this scenario, everything is expected.

Scenario 2:

 static void Main(string[] args)
        {
            AppDbContext appDbContext = new AppDbContext();
            var query1 = GetFirstQuery(appDbContext);
            var query2 = query1.Where(s => s.Name == "Messi");
            var loadedEntitiesCount = query2.ToList().Count();//Count is 4. It is good
            var trackedEntitiesCount = appDbContext.ChangeTracker.Entries<Student>().Select(e => e.Entity).Count();//Count is 8. Confusing
        }

        static IEnumerable<Student> GetFirstQuery(AppDbContext appDbContext)
        {
            return appDbContext.GetEntities<Student>().Where(s => s.Score > 3);
        }

In this scenario, the count of tracked entities is 8. I was not expecting it

Scenario 3:

   AppDbContext appDbContext = new AppDbContext();
            var query1 = GetFirstQuery(appDbContext).AsQueryable();
            var query2 = query1.Where(s => s.Name == "Messi");
            var loadedEntitiesCount = query2.ToList().Count();//Count is 4. It is good
            var trackedEntitiesCount = appDbContext.ChangeTracker.Entries<Student>().Select(e => e.Entity).Count();//Count is 4. It is good

The count of tracked entities is 4.

The question is why ef tracking all entities in Scenario 2? Is it something expected? What are best practices to add query on existing query with different kinds of collections? (IEnumerable, IQueryable)

1 Answers

Generally speaking, if you don't want objects to be tracked, you can call .AsNoTracking() on your query. Then the results of this query will not be tracked.

In your specific case:

static IEnumerable<Student> GetFirstQuery(AppDbContext appDbContext)

this means the query ends here, because you return an IEnumerable. The result will then be further filtered in memory of your application, so you get the correct result, but all data will have been queried from the database.

Use IQueryable<> if you want the query to continue and further filters or expressions to be transported to the database instead of being executed in the memory of your application:

static IQueryable<Student> GetFirstQuery(AppDbContext appDbContext)
Related