Closed Remleo closed 2 years ago
To things you can try:
Include(b => b.Owner)
var ids = context.ChangeTracker
.Entries<Blog>()
.Select(e => e.Entity.BlogId)
.ToList();
context.Owner.Where(o => ids.Contains(o.BlogId)).Load();
@Remleo BTW, we would be interested in understanding your scenario better, e.g. why didn't you use eager loading in this case.
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 (
Reopening so we can visit this in triage.
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.
+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.
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);
}
@KieranDevlinSycous - Just try this.
public IEnumerable<Post> GetPostsByUsersCreatedOnDate(DateTime time) {
return GetAccountsCreatedOnDate(time).SelectMany(x => x.Posts).AsEnumerable();
}
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);
}
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
)
For explicit loading EF Core offers (from docs):
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():
which make a single SQL query like:
select .... where [BlogOwner].[BlogId] in (?, ?, ?, ?, ?, ?)
Sorry, but I have not found similar functionality. Thanks