-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Support partial loading (i.e. not eagerly loading all scalar properties) #1387
Comments
See #9385 for some more ideas in this area. |
See #8138 for additional ideas in this area--specifically around lazy loading of scalar properties. |
As a workaround (and that's good enough for me), I do something like this:
This works with EF 2.0 (you need to |
@faibistes Does this produce one query or 2 queries against the same table? If one query, is the table joining with itself on the Id? Any issues from this? |
I have tested faibistes solutions and it worked for EF Core 2.1. |
@hmdhasani The solution @faibistes showed is Table splitting, that's the recommended way to lazy load scalar properties. Owned entities will always be eagerly loaded. |
Ok, anyway the downside of using table split is adding an unnecessary complexity to data model that can be a high cost for big data models.
And
I wonder is there currently any way to do this? ( maybe by somehow interposition or manipulation of query generation process ) |
After trying some approaches I ended up with this workaround:
into this:
by overriding DefaultQuerySqlGenerator.GenerateList()
I added AsNoTracking() to become sure the value of ignored column will not change. here is the full code: ( most parts were taken from https://www.chasingdevops.com/sql-generation-ef-core/ )
it requires this in DbContext:
|
Are there any approaches that are less complicated than @hmdhasani ? |
See #21251 for ideas on how to combine with |
Thanks @AndriySvyryd but it would be better if it has Exclude :) |
I'd also like to upvote this idea. The amount of data returned by a query has a large effect on performance. It's currently quite difficult in to return only the data needed without writing new code every time. My suggestion is like this. Add two extension methods IncludeProperties<T>(this IQueryable<T> source, Type entityType, IEnumerable<string> propertyNames);
ExcludeProperties<T>(this IQueryable<T> source, Type entityType, IEnumerable<string> propertyNames); These do not do anything by themselves, but they add metadata to the expression tree and act as a filter when generating the SQL query for an entity of the given type so that only matching properties are included in the select statement. It would be used like this class User{
public Guid Id {get;set;}
public string Name {get;set;}
public string PsswordHash {get;set;}
//More properties...
}
class Post{
public Guid Id {get;set;}
public string Title {get;set;}
public string Content {get;set;}
public Guid AuthorId {get;set;}
public DateTimeOffset Date {get;set;}
public User Author{get;set;}
}
class PostHelper{
public static readonly List<string> CommonProperties = new List<string>(){ nameof(Post.Id), nameof(Post.Title) };
}
PostsDbSet
.IncludeProperties(typeof(Post), PostHelper.CommonProperties)
.IncludeProperties(typeof(User), new string[]{nameof(User.Id), nameof(User.Name)})
.Where(item => item.Date > myDate).Include(item => item.Author); The generated SQL would then only include the Id and Title properties in the select query for the Post, and the Author would be included but only with the Id and Name. With such a query, I guess tracking should be disabled by default as it would be confusing to track entities with only partial properties. With these methods, it would be far easier to write efficient queries that only return the required data without having to write out all the properties in every single query or define complex expressions for later use (which don't really work for navigation properties anyway). Data transfer objects can then be created from entities using standard functions which run after the data has been fetched from the database rather than trying to write a reusable expression incorporating the data transfer object. |
Just since it doesn't seem to appear above, I'm mentioning the obvious solution of projecting out to an anonymous type (as documented in our perf docs): var results = context.Blogs
.Where(....)
.Select(b => new { b.Id, b.Name }) This will efficiently project out only the two properties in SQL. The main drawback here is that you get a different type and not Blog, which may be problematic to pass on in your code (but a new Blog can be constructed from that as a workaround). Also, this is by nature an untracked query. |
@roji - The other main drawback of that is that you have to write out the properties every time you write such a query, and then also do the mapping to your eventual data type every time, which is extremely cumbersome, especially if you end up adding new properties to your model. You can define a reusable expression with a data transfer object but that is also cumbersome and doesn't work for child navigation properties. My proposed solution above allows you to define a set of 'CommonProperties' to include like the example I gave, which can be reused. |
If the goal is to always load the same set of common properties, and then construct a Blog instance from that, it's pretty trivial should be pretty trivial to just do that in an extension method that can be used everywhere: public static IEnumerable<Blog> WithMinimalProperties(this IQueryable<Blog> source)
{
return source
.Select(b => new { b.Id, b.Name })
.AsEnumerable()
.Select(x => new Blog { Id = x.Id, Name = x.Name });
} You could then call it as follows: var blogs = ctx.Blogs.WithMinimalProperties().ToList(); ... or something along those lines. Not saying it's a perfect solution, but unless you specifically need tracking, something like this seems fine to me. |
Of course, after writing the above I realized you can just do: public static IQueryable<Blog> WithMinimalProperties(this IQueryable<Blog> source)
=> source.Select(b => new Blog { Id = b.Id, Name = b.Name }); |
That's not bad for simple cases but is it possible to mix and match that with partial navigation properties? Like can I optionally include Blog.Author.Name as well, without writing out all the properties for blog again? |
I don't see why not: public static IQueryable<Blog> WithMinimalProperties(this IQueryable<Blog> source)
=> source.Select(b => new Blog
{
Id = b.Id,
Name = b.Name,
Posts = b.Posts.Select(p => new Post { Id = p.Id }).ToList()
}); This produces the following SQL: SELECT [b].[Id], [b].[Name], [p].[Id]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId]
WHERE [b].[Name] = N'hello'
ORDER BY [b].[Id], [p].[Id] Note that both Blog and Post have additional properties which aren't being fetched here. Note also that this doesn't wire the Posts to point back to their Blog, as EF Core does, but you should be able to do that as well if you want. |
@roji - Ok, but the point is I don't always want to include the post properties. Maybe I should have provided a fuller example in the first place. Suppose I have a class User{
public Guid Id {get;set;}
public string Name {get;set;}
public string PsswordHash {get;set;}
//More properties...
}
class Post{
public Guid Id {get;set;}
public string Title {get;set;}
public string Content {get;set;}
public Guid AuthorId {get;set;}
public DateTimeOffset Date {get;set;}
public User Author{get;set;}
} Then I define some core properties associated with each class like this. (I write them like this although I don't mind writing them another way). class PostHelper{
public static readonly List<string> CoreProperties = new List<string>(){ nameof(Post.Id), nameof(Post.Title) };
}
class UserHelper{
public static readonly List<string> CoreProperties = new List<string>(){ nameof(User.Id), nameof(User.Name) };
} I don't want to declare/write out these properties more than once. Now I want to be able to do the following. I want to be able to select users from the users table, only returning the user's CoreProperties. I want to be able to select Posts from the Posts table, only returning the post's CoreProperties (and not including the Author). I want to be able to select Posts from the Posts table, including the post's core properties and including the post's Author with only CoreProperties for the author. I can't see any way of doing that currently without writing out the properties multiple times. Please correct me if I'm wrong! |
As I said above, this isn't a perfect solution, and yes, it would require duplicating the list of common properties in multiple places. If you really want to, you should be able to centralize the list of common properties by manually building the expression tree passed to Select inside WithMinimalProperties above (i.e. with Expression.New and similar); that would allow you to have a single method which produces the initializers for the common properties, which can be called from any place where you need to initialize a given type. But of course that gets a bit more complicated. I'm not saying that built-in partial loading in EF Core should be implemented - although if so, the main reason is probably per-property change tracking rather than just centralizing the list of common properties. But the code I posted above - albeit imperfect - should provide you with an acceptable workaround until the full feature is implemented. |
@roji - thanks for the discussion. Honestly, I think you are underestimating how off-putting it is to have to repeatedly write out a list of properties, and I think there would be a great benefit from a simple way to include partial properties in a reusable way, even without the consideration of entity tracking. The solution I suggested has the advantage that it's still possible to use the rest of the EF core API (which is great in general) such as Is there any chance of moving on the discussion slightly, such as coming up with a proposed API? Or is it still too uncertain whether this feature would be included? |
Note that above I proposed repeating the common properties only in each WithMinimalProperties definition. When writing queries, you'd just write WithMinimalProperties without having to specify the properties.
This issue is in the backlog, so we won't be getting to work on it for 6.0; as such, it's probably too early to go into any concrete discussion (this is why I'm also suggesting looking at workarounds). |
Ok, thanks again for the discussion. Fingers crossed this or something equivalent will make it into v7.0 :) |
Current work around using TagWith and an interceptor to rewrite the SQL. I imagine it'd also be possible to write an extension method like Select that takes a list of what to exclude |
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This one is a good workaround, but it's quicker most of the time to just exclude 1 or 2 columns than selecting those needed. |
Can you please add partial loading and by that I mean in addition to lazy and eager loading of relationships, the third option - lets call it partial for now - loads the related entity but only containing the primary keys and no other data. If needed I could resolve the rest of the data manually.
For example I have a Contact entity with one to many relationship with Address. With partial loading, I will get Contact.Address.AddressId only. I could then resolve the rest of the properties manually. Maybe all maybe the ones I specify via DbContext.Entry(address).Resolve(a=>a.Line1;....
The text was updated successfully, but these errors were encountered: