Collection Properties Not Populated In Multi-Level Includes When Using Futures
Edit: better explanation here: https://github.com/zzzprojects/EntityFramework-Plus/issues/405#issuecomment-418925410
Hi,
On .net core 2.1 / EntityFrameworkPlus.EfCore 1.8.5, I noticed that some data was missing from collection properties in my return object when using a more complicated (multi-level includes) Linq statement as a Future(). Specifically, let's look at the following statement:
var future = context.A.Where(a.Id == 1)
.Include(a => a.B)
.ThenInclude(b => b.C)
.Include(a => a.B)
.ThenInclude(b => b.D)
.ThenInclude(d => d.E)
.Include(a => a.B)
.ThenInclude(b => b.F)
.Include(a => a.B)
.ThenInclude(b => b.G).Future();
var data = await future.ToListAsync();
In the above example, we can assume the following: A is a class with a child B B is a class with children: List<C>, List<D>, F, and G D is a class with child E.
So the important info there is that only C and D are the collection property children.
When executing the above code, I notice that F and G are populated fine, whereas C and D are both empty lists, even when data should be present. On top of that, I grabbed the SQL generated from the linq statements, and ran it directly and still got the correct data. If I simply remove the .Future() from the Linq statement, the query will execute correctly and give me the populated fields I was missing (but of course without the .Future() this adds a DB roundtrip which I need to avoid).
Extra Details: One more important part that I noticed is that if I debug with a breakpoint after the futures have executed, and if i check the future object, I noticed that the future.Query property actually shows the correct information, with the fields populated. However when trying to use this property directly to obtain the data it always resulted in either the data missing again or additional round trips to the DB.
The last critical bit is that in another place of my code, I need to do the exact same Linq statement except I am starting at the B object instead of with an A object. And in this case, I do not need to use Future() at all. It all works fine in this situation.
So basically, my assumption is that because I am using Future() here, for some reason the collection properties cannot be populated even though the generated sql is still correct and I am able to see the relevant data after the queries have executed, it is just not populated in my returned object.
Thanks in advance!
Hello @jssignore1 ,
Do you think you could provide a "working" project example/test with this issue?
It will help my developer investigate the issue more efficiently.
You can send it to: [email protected] if you need to keep the source private
We now always ask for a sample project since most issues are either missing some essential information, have some syntax errors or are resolved by the requestor when creating it
Having a test project allows us to give faster support and a better experience to users of our free libraries.
Best Regards,
Jonathan
Performance Libraries
context.BulkInsert(list, options => options.BatchSize = 1000);
Entity Framework Extensions • Entity Framework Classic • Bulk Operations • Dapper Plus • LinqToSql Plus
Runtime Evaluation
Eval.Execute("x + y", new {x = 1, y = 2}); // return 3
C# Eval Function • SQL Eval Function
Hi, I was working on a demo project for you when I realized that the specific case to reproduce the bug is even more complicated than I thought. I haven't narrowed it down yet but it seems that when I combine the above Linq query as a Future() with many other queries as Futures (about 8 in total), this is the only time that the bug appears. Whereas, if I only perform the above Linq query as a Future(), and comment out the rest of my query futures, it works ok.
So let me continue to debug on my end and I will update the issue tomorrow. Thanks!
Ok @JonathanMagnan , I was able to greatly simplify the bug case, I will provide another code paste below with an explanation, but if that isn't enough I can come back tomorrow and try and get you a demo project.
var otherFuture = _context.B.Future();
var future = _context.A
.Include(a => a.B)
.Include(a => a.C).Future();
var data = future.ToList();
In the above case, we just have a class 'A' which has two children: a List<B> and a C.
As long as both futures are present, when calling ToList() on 'future', the 'B' property of the resulting 'A' will not be populated. However, if we comment out the 'otherFuture' line, then the 'B' property will be correctly populated.
Let me know if you still need help reproducing.
Thanks!
Hello @jssignore1 ,
My developer reproduced your scenario but didn't success to reproduce the issue.
Could you help us to find what we are missing?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Z.EntityFramework.Plus.Lab.EF6
{
public partial class Form_Request_FuturesInclude : Form
{
public Form_Request_FuturesInclude()
{
InitializeComponent();
// CLEAN
using (var context = new EntityContext())
{
context.C.RemoveRange(context.C);
context.B.RemoveRange(context.B);
context.A.RemoveRange(context.A);
context.SaveChanges();
}
// SEED
using (var context = new EntityContext())
{
context.A.Add(new A() { ColumnInt = 1, C = new C() { ColumnInt = 4 }, B = new List<B>() {new B() {ColumnInt = 5}
, new B() { ColumnInt = 6 } } } );
context.B.Add(new B() { ColumnInt = 2 });
context.C.Add(new C() { ColumnInt = 3 });
context.SaveChanges();
}
//using (var _context = new EntityContext())
//{
// var otherFuture = _context.B;
// var data = _context.A
// .Include(a => a.B)
// .Include(a => a.C).ToList();
//}
// TEST
using (var _context = new EntityContext())
{
var otherFuture = _context.B.Future();
var future = _context.A
.Include(a => a.B)
.Include(a => a.C).Future();
var data = future.ToList();
}
}
public class EntityContext : DbContext
{
public EntityContext() : base("CodeFirstEntities")
{
}
public DbSet<A> A { get; set; }
public DbSet<B> B { get; set; }
public DbSet<C> C { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Types().Configure(x =>
x.ToTable(GetType().DeclaringType != null
? GetType().DeclaringType.FullName.Replace(".", "_") + "_" + x.ClrType.Name
: ""));
base.OnModelCreating(modelBuilder);
}
}
public class A
{
public int ID { get; set; }
public List<B> B { get; set; }
public C C { get; set; }
public int ColumnInt { get; set; }
}
public class B
{
public int ID { get; set; }
public int ColumnInt { get; set; }
}
public class C
{
public int ID { get; set; }
public int ColumnInt { get; set; }
}
}
}
Best Regards,
Jonathan
Hey sorry for the delayed response, we've moved on to using Dapper since there were other (bigger) issues in our project with regards to Entity Framework in general.
Anyway, that example looks mostly the same as what I have, except the one major difference I see is that my B class has a property that references its parent, A:
public class B
{
public int ID { get; set; }
public int ColumnInt { get; set; }
public int AId {get; set;}
}
So im guessing that might be the problem. Other than that, the only differences I see are small like I didn't have a DbSet for C.
In any case, thanks for taking the time to look into the issue
Thank for letting me known. We will probably investigate it again but since you don't need it anymore, we will move it in low priority.
Just to let you known, we have another project Entity Framework Classic, in a few weeks, we will support some method like Dapper does to provide very fast querying.
If you have time, I will be very interested to hear about the other issues you got in regards to Entity Framework to make sure we cover them in our version. You can send them in private here: [email protected]
Best Regards,
Jonathan
I'm encountering this problem now on EF Core. I agree that a simpler use case should be able to reproduce it.
Pseudocode:
var query = dbSet.Where(c => c.PassesBasicFilter);
if someCondition
query = query.Where() ...
... etc.
Then actual code:
query = query.OrderByDescending(c => c.ReleaseDate)
.Include("Items.Product")
.Include(c => c.Images);
var futureCount = query.DeferredCount().FutureValue();
var futurePage = query.Future();
var results = futurePage.ToList();
var count = futureCount.Value;
When this is run, Images and Products come back with 0 for count, when they should come back with something like 1 and 4 entities.
The c used is an object that has a collection images and a collection of products, which are also entities.
The code works fine if there is only one future, but that breaks the point.
@JonathanMagnan Should I create a new issue for this instead of commenting on an old one?
Hello @rustyemmert ,
Yes, please create a new issue and make sure to include a runnable project/solution or Fiddle that reproduce it.
Best Regards,
Jonathan