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

Explicit loading for list of entries: context.Entries(...)...Load() #7350

Closed
Remleo opened this issue Jan 4, 2017 · 10 comments
Closed

Explicit loading for list of entries: context.Entries(...)...Load() #7350

Remleo opened this issue Jan 4, 2017 · 10 comments

Comments

@Remleo
Copy link

Remleo commented Jan 4, 2017

For explicit loading EF Core offers (from docs):

var blog = context.Blogs
        .Single(b => b.BlogId == 1);

context.Entry(blog)
        .Reference(b => b.Owner)
        .Load();

If I have, for expample, list of 10 entries, I need to 10 times call Reference(...).Load() in foreach, that generate 10 SQL queries to DB.

How about optimized method Entries():

var blogs = context.Blogs
        .Where(...)
        .ToList();

context.Entries(blogs)
        .Reference(b => b.Owner)
        .Load();

which make a single SQL query like: select .... where [BlogOwner].[BlogId] in (?, ?, ?, ?, ?, ?)

Sorry, but I have not found similar functionality. Thanks

@Remleo Remleo changed the title Explicit loading for list of entiries: context.Entries(...)...Load() Explicit loading for list of entries: context.Entries(...)...Load() Jan 4, 2017
@divega
Copy link
Contributor

divega commented Jan 9, 2017

To things you can try:

  • Use eager loading, i.e. Include(b => b.Owner)
  • Write a query like this (I haven't actually tested it, but it should work):
var ids = context.ChangeTracker
    .Entries<Blog>()
    .Select(e => e.Entity.BlogId)
    .ToList();
context.Owner.Where(o => ids.Contains(o.BlogId)).Load();

@divega divega closed this as completed Jan 9, 2017
@divega divega added the closed-no-further-action The issue is closed and no further action is planned. label Jan 9, 2017
@divega
Copy link
Contributor

divega commented Jan 9, 2017

@Remleo BTW, we would be interested in understanding your scenario better, e.g. why didn't you use eager loading in this case.

@Remleo
Copy link
Author

Remleo commented Jan 11, 2017

Sometimes may need to eager/explicit load a relationship after the parent model has already been retrieved. For example, this may be useful if I need to dynamically decide whether to load related models:

// This code is responsible for retrieve specific blog entities
// But it has no idea about inner logic in BlackBox.SomeMethod()
// That is why it dont load Navigations
var neededBlogs = context.Blogs.Where(....).ToList();
....
if (someDynamicCondition)
        BlackBox.SomeMethod(context, neededBlogs);

....

public class BlackBox {
        public static void SomeMethod(DbContext context, IEnumerable<Blog> blogs)
        {
            // There might be code that ensure Owner loading, 
            // because this method has no idea about is `blogs` was preloaded `Owners` or not.
            // Also `Loader` should be intelligent enough for load only empty Navigations
            // so calling this method multiple times is safe
            context.Entries(blogs).Reference(b => b.Owner).Load(); // dry code... my vision :)

            foreach (var blog in blogs)
            {
                if (!string.IsNullOrEmpty(blog.Owner.email) && someDynamicCondition)
                {
                    SendNotificationToOwner(blog.Owner.email, "Alert!");
                }
            }
        }
}

This code works:

var ids = context.ChangeTracker
    .Entries<Blog>()
    .Select(e => e.Property(b => b.BlogId))
    .ToList();
context.Owner.Where(o => ids.Contains(o.BlogId)).Load();

but code is not "dry". where statement need to be hardcoded and match FK for Navigation-property. Context knows all about FK so it's his "job" to load Navigation-properties properly.

Sorry for my english (

@ajcvickers
Copy link
Member

Reopening so we can visit this in triage.

@ajcvickers ajcvickers reopened this Jan 12, 2017
@ajcvickers ajcvickers removed the closed-no-further-action The issue is closed and no further action is planned. label Jan 12, 2017
@rowanmiller
Copy link
Contributor

Closing but will reconsider if we see more requests. We would consider a PR with the feature. You could also look at implementing it as an extension method.

@adduss
Copy link

adduss commented Jun 20, 2017

+1

This is totally valid example for any TPT inheritance. Base model does not contains navigation properties and if we woluld like to display list with all inherited types with the common property fe. "Name", but loaded from different related entities of derivered models. We can't use include because there is no navigation property to do so, and if we load directly EF is not clever enough just to get related entites IDs by FK, but it will get whole tables, and performance will be not acceptable. In that case we have to change whole structure to TPH or create view, map it to new model and at the end we will end up with two different models to describe exactly the same entity :/

Visualisation ;):
A (entity with all 3 digit numbers - like dictionary of all of them)
B (base model to keep numbers assigments - abstract)
--> C (derived) --> User (additional relation to entity with Name)
--> D (derived) --> Company (additional relation to entity with Name)
--> etc.

@KieranDevvs
Copy link

KieranDevvs commented Oct 3, 2017

I would like to see this as there are many query methods that use the same query but don't use the same included references therefore, if you have a method to return a query and add all references that are used in all instances of the methods usage, then you end up with a method that works but is really inefficient. This is how I imagine it should be done?

public IQueryable<Account> GetAccountsCreatedOnDate(DateTime time) { 
    context.Account.Where(x => x.CreatedDateTime == time);
}

public IEnumerable<Post> GetPostsByUsersCreatedOnDate(DateTime time) {
    var users = GetAccountsCreatedOnDate(time);
    context.Entries(users).Reference(x => x.Posts).Load();
    return users.SelectMany(x => x.Posts);
}

@smitpatel
Copy link
Member

@KieranDevlinSycous - Just try this.

public IEnumerable<Post> GetPostsByUsersCreatedOnDate(DateTime time) {
    return GetAccountsCreatedOnDate(time).SelectMany(x => x.Posts).AsEnumerable();
}

@starychfojtu
Copy link

I find this feature very useful. For example if you don't want to use eager loading too much, because it generates JOIN queries and if some other query already fetched the data, you are still stuck with lot of joins instead of simple selects. For example you have entity A with property C and entity B with property C and you eager load A with C and then you still have too eager load C with B instead of just selecting B. So you either have costly queries or you have many many methods on your repositories. So it would be much simpler to just load the necessary references in place. I would love this feature to be implemented. Now I use this instead

public Query<TEntity> LoadBy<TForeignEntity>(
            IEnumerable<TForeignEntity> foreignEntities,
            Func<TForeignEntity, TEntity> entitySelector,
            Func<TForeignEntity, Guid?> entityIdSelector,
            bool unrestricted = false)
        {
            var ids = foreignEntities.Where(e => entitySelector(e) == null).Select(e => entityIdSelector(e).ToOption());
            return Select(unrestricted).Where(e => e.Id, ids);
        }

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
@romfir
Copy link

romfir commented Mar 3, 2023

Our team would really need this feature, instead of it we must manually download entries by using id of a main entry and set its collection state to be loaded (Context.Entry(obj).Collection(c => c.Collections).IsLoaded = true)

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

No branches or pull requests

9 participants