EntityFramework-Plus icon indicating copy to clipboard operation
EntityFramework-Plus copied to clipboard

Collection Properties Not Populated In Multi-Level Includes When Using Futures

Open jssignore1 opened this issue 7 years ago • 9 comments

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!

jssignore1 avatar Sep 05 '18 19:09 jssignore1

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 ExtensionsEntity Framework ClassicBulk OperationsDapper PlusLinqToSql Plus

Runtime Evaluation Eval.Execute("x + y", new {x = 1, y = 2}); // return 3 C# Eval FunctionSQL Eval Function

JonathanMagnan avatar Sep 05 '18 21:09 JonathanMagnan

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!

jssignore1 avatar Sep 06 '18 00:09 jssignore1

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!

jssignore1 avatar Sep 06 '18 00:09 jssignore1

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

JonathanMagnan avatar Sep 14 '18 14:09 JonathanMagnan

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

jssignore1 avatar Sep 24 '18 23:09 jssignore1

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

JonathanMagnan avatar Sep 25 '18 02:09 JonathanMagnan

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.

rustyemmert avatar Apr 23 '19 21:04 rustyemmert

@JonathanMagnan Should I create a new issue for this instead of commenting on an old one?

rustyemmert avatar Apr 25 '19 17:04 rustyemmert

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

JonathanMagnan avatar Apr 25 '19 19:04 JonathanMagnan