Is this by design? will be implemented later? something else?
I couldn't find an issue about it so I making this one to track this.
context.Entities.Include(e=> e.OtherEntity).FindAsync(id)
I was expecting to work on IncludableQueryable
.
Without the Include
, Find
works as expected.
'IIncludableQueryable<Entity, ICollection<OtherEntity>>' does not contain a definition
for 'FindAsync' and no extension method 'FindAsync' accepting a first argument of type
'IIncludableQueryable<Entity, ICollection<OtherEntity>>' could be found
(are you missing a using directive or an assembly reference?)
EF Core version: 1.1.0-preview1-final
Operating system: Windows
Visual Studio version: 2015
Other details about my project setup:
EF Triage: Find
is not a query operator, but a convenient way to retrieve a single entity by key and save a database roundtrip if the entity you are looking for is already loaded in the context. Include
only works in queries. For this case you can write:
C#
var entity = await context.Entities
.Include(e=> e.OtherEntity)
.FirstOrDefaultAsync(e => e.Id == id);
@divega and in case you don't know the primary key field/s at compile time?
@wc-matteo
Assuming you know the type and that the key isn't composite:
``` C#
var keyProperty = context.Model.FindEntityType(typeof(Blog)).FindPrimaryKey().Properties[0];
var entity = context.Blogs
.Include(e => e.Posts)
.FirstOrDefault(e => EF.Property
```
Similar code can be written if the problem is less constrained.
@ajcvickers Nice! I forgot about EF.Property. I was looking at this for an extension to load all the navigation properties of an entity. There is nothing like that in the framework, right?
@wc-matteo
C#
foreach (var navigation in context.Entry(entity).Navigations)
{
navigation.Load();
}
@ajcvickers thanks! That's... what I need ;)
@ajcvickers ah! Just one doubt... that pulls only the data related to the entity in question, right?
(And not all the data, of all the navigation properties)
@wc-matteo It will bring in all entities related to the given entity. If you want all entities related to all entities of a given type you are probably better off using Include. But you could do something like this:
``` C#
var setMethod = typeof(DbContext).GetMethod("Set");
var entityType = context.Model.FindEntityType(typeof(Blog));
foreach (var navigation in entityType.GetNavigations())
{
((IQueryable)setMethod.MakeGenericMethod(navigation.GetTargetType().ClrType)
.Invoke(context, null))
.OfType
Thank you again @ajcvickers! All these snippets will come very handy! 馃憤
@divega Curios to know what is the reason behind not supporting Include
with Find
by-design.
I think it would be great if EF intelligently could:
Say you want to query X and include Y with it:
I can't think of a flaw in that, can you?
Thanks for your time Diego!
p.s. Not (at all) saying it's urgent or even important to support it specially in these early stages of EF Core, I'm just curious why you decided it shouldn't be supported by-design.
@gdoron The answer to your question has a few parts:
Find()
is a simple method defined on DbSet
by design, as opposed to a general query method that composes with things like Include()
Find()
(or create something new) to be just a DRY sugar query method for FirstOrDefault()
that implicit knows about the key properties.Thanks for the input @divega!
Is it called Identity Map in EF too or am I using the wrong term for EF?
@gdoron yep, identity map is ubiquitous language for that concept within the EF team too :smile:
@divega in my scenario case, I cannot use the predicate as e => e.Id == id
.
I have a generic class , where T : class
and I have a TId
, which can be long
, string
, or even composite, that is received as parameter. I've noticed that Find
gets a param object[] keys
and as it says "Finds an entity with the given primary key values." so it works with composite keys too, meaning that finding an element with the given Id is as straightforward as
public virtual T FindById(TId id)
{
return Context.Set<T>().Find(id);
}
That being said, since my generic parameter T
is a class, cannot use the predicate of FirstOrDefault
, meaning I cannot achieve something like this
public virtual T FindById(TId id, params Expression<Func<T, object>>[] includeProperties)
{
return Context.Set<T>().Include(includeProperties).FirstOrDefault(x => x.Id = id);
}
My question is: why/how does it work with Find
but does not with Include
?
@ajcvickers's solution could work , but he states "and that the key isn't composite".
Is the only way to change from where T : Class
to my specific implementation?
@DanielSSilva #7391 is tracking adding APIs to make it easier to use arbitrary key types/values.
@ajcvickers . If i have a structure like that : Candidate with a list of educations and a education with a list of schools. Does your snippet allow me to load the educations relatives to Candidate and all relative schools to that education ? I mean something like that :
var setMethod = typeof(DbContext).GetMethod("Set");
```C#
var setMethod = typeof(DbContext).GetMethod("Set");
var entityType = context.Model.FindEntityType(typeof(Candidate));
foreach (var navigation in entityType.GetNavigations())
{
((IQueryable)setMethod.MakeGenericMethod(navigation.GetTargetType().ClrType)
.Invoke(context, null))
.OfType
Or must I use Include() and ThenInclude() ?
@FloMedja Include would be the way to do that, unless there is some really good reason why Include doesn't work.
@ajcvickers Thanks you. I use this approch with your previous snippet. It work for load all include and relatives include to these include for an entity. I think this approch only work for 2 level of include but that is ok with my current requirements . Your code help me a lot. Thanks you :)
public async Task<TEntity> GetByIdWithChildrenAsync(TKey id)
{
var entity = await _context.FindAsync<TEntity>(id);
foreach (var navigation in _context.Entry(entity).Navigations)
{
await navigation.LoadAsync();
await LoadRelativeChildrenAsync(navigation.Metadata.GetTargetType().ClrType);
}
return entity;
}
private async Task LoadRelativeChildrenAsync(Type entityTypeValue)
{
var setMethod = typeof(DbContext).GetMethod("Set");
var entityType = _context.Model.FindEntityType(entityTypeValue);
foreach (var navigation in entityType.GetNavigations())
{
await ((IQueryable)setMethod.MakeGenericMethod(navigation.GetTargetType().ClrType)
.Invoke(_context, null))
.OfType<object>()
.LoadAsync();
}
}
Most helpful comment
@wc-matteo
Assuming you know the type and that the key isn't composite:
``` C#
var keyProperty = context.Model.FindEntityType(typeof(Blog)).FindPrimaryKey().Properties[0];
var entity = context.Blogs(e, keyProperty.Name) == id);
.Include(e => e.Posts)
.FirstOrDefault(e => EF.Property
```
Similar code can be written if the problem is less constrained.