Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use of .Include can give unexpected results. Can someone tell me if my understandings are correct? #24871

Closed
darcome opened this issue May 10, 2021 · 4 comments

Comments

@darcome
Copy link

darcome commented May 10, 2021

I have the following POCO classes:

public class BaseObject<T> : IBaseObject where T : BaseObject<T>, new ()
{
	public	Int64			Id			{get;set;}
	public	string			Name		{get;set;}
	public	DateTime		Created		{get;set;} = DateTime.Now;
	public	DateTime		Updated		{get;set;} = DateTime.Now;
}

public class Article : BaseObject<Article>
{
	public	string				Url			{get;set;} = "";
	public	string				Title		{get;set;} = "";
	public	string				ImageUrl	{get;set;} = "";
	public	string				Summary		{get;set;} = "";
	public	long				WebsiteId	{get;set;}
	public	Website				Website		{get;set;} = null;
	public	long				SectionId	{get;set;}
	public	Section				Section		{get;set;} = null;
	public	List<Category>		Categories	{get;set;} = new List<Category> ();
	public	bool				IsAutomatic	{get;set;} = false;
}

public class Category : BaseObject<Category>
{
	public	string				Slug		{get;set;} = "";
	public	long				SectionId	{get;set;}
	public	Section				Section		{get;set;} = null;
	public	List<Article>		Articles	{get;set;} = new List<Article> ();
}

As you can see, Article has a List of Category that in turn has a list of Article.

I am in an asp.net core website and I have the following function:

public	async	Task<List<Article>> GetLatestAsync	(int page, int size, Section s)
{
	List<Article> result = new List<Article> ();

	IQueryable<Article> query = dbSet.Where (x => x.SectionId == s.Id).OrderByDescending (x => x.Created);
	query = query.Include (x => x.Categories);
	query = query.Skip ((page - 1) * size).Take (size);

	result = await query.ToListAsync ();

	return result;
}

It is written in pieces because I am commenting pieces of it to test various things :D

My understanding is that ef core 5 doesn't load sub objects unless they are specified using an .Include, and even then, it should only include the first level. Therefore I was expecting that my .Include would include the property Categories but the property Articles of the Category class would not be loaded because there is no .Include to allow it.

Instead, I saw that the Articles property is filled with Articles, and I suppose it's because of this Tip I found on this page:

https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager?fbclid=IwAR16EgtEHu1bNkvBfRLv3mP4RJh1TsdB2x9HqWR_SH41Z00cyIAX7YT_Vf4

which states that:

Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

Now, this is a bit confusing, because my understanding was that if you don't use an .Include sub objects would not be loaded and I was relying on it.

This is a problem, for example, if you have to "jsonize" the result, because it would end up with a string of 15Mb (in my case) and I cannot even use JSON.NET settings about references because this json is used in JavaScript. And JavaScript doesn't understand $RefId: 7 (for example).

I solved this problem by creating a function that will set the Articles property to Enumerable.Empty<Article> ().ToList ().

But my question is... Is there a way to instruct ef core not to load sub objects even if they are in cache, unless this is explicitly requested?

If it is not possible, is something that makes sense to add this feature in a future release? If not, what other solution could I use to solve this problem?

Thanks in advance for your help!

@maumar
Copy link
Contributor

maumar commented May 11, 2021

@darcome you can use AsNoTracking option in your queries - it will circumvent identity resolution completely so your queries will end up with only the navigations that were explicitly loaded in a given query.

Here is some more info on tracking/notracking queries: https://docs.microsoft.com/en-us/ef/core/querying/tracking

@stevendarby
Copy link
Contributor

#11564

@darcome
Copy link
Author

darcome commented May 11, 2021

At the moment I don't have the code in front of me, so I cannot try, but what happens if, for example in my case, the Categories property has four Articles, but ef core has only 3 of them in memory? I would end up with only those 3, so my code thinks that the list has 3 articles instead of 4?

This would be very bad.

@ajcvickers
Copy link
Contributor

@darcome See #11564 for a long discussion of this. Also, see Change Tracking to understand how fixup of navigation properties work.

Closing this as a duplicate of #11564.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants