Eagerly/explicitly load "child" entities with only navigation property to "parent" - entity-framework

I have an EF Core 2.1 Code First model with a "parent-child" type relationship between two classes:
class Parent
{
public int Id {get; set;}
public string Name {get; set;}
}
class Child
{
public int Id {get; set;}
public string Description { get; set; }
public Parent Parent { get; set; }
}
I want to load a certain Parent, and make sure that all its Child entities are loaded too. However, there is no navigation property to Child, and I cannot modify the classes, so I can't add one.
dbContext.Parents
.Include(p => p.???)
.Find(1);
I suppose I could do a second query where I look everything up in reverse:
dbContext.Children.Where(c => c.Parent.Id == loadedParent.Id)
but that does not seem very efficient, especially when you load multiple parents and do something horrible like this:
var parentIds = loadedParents.Select(p => p.Id);
var children = dbContext.Children.Where(c => parentIds.Contains(c.Parent.Id));
Is there a way to make sure entities are loaded when you only have a "child-to-parent" navigation property?

make sure that all its Child entities are loaded too
So load the Child entities:
var children = dbContext.Children.Include(c => c.Parent)
.Where(c => c.Parent.Id == 1).ToList();
Or use wider selection criteria than c.Parent.Id == 1 if you want to get multiple parents.
If necessary you can list the loaded parents by accessing the Local collection:
va parents = dbContext.Parents.Local;

Related

Entity Framework Core 5.0 - Many to many select query

I am trying to get a single User, with a list of Items, mapped with a many-to-many entity UserItems. However, I am unable to retrieve the mapped Items due to to an error that I'm unable to solve (error at bottom of question). Here is my code:
public class User
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class Item
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class UserItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
public int Quantity { get; set; }
}
The UserItem class configuration has the following relationships defined:
builder.HasOne(x => x.User)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.ClientCascade);
builder.HasOne(x => x.Item)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.ItemId)
.OnDelete(DeleteBehavior.ClientCascade);
I have the following generic repo with this method:
public class GenericRepository<T> : where T : class
{
private readonly DbContext _context;
public GenericRepository(DbContext context) => _context = context;
public T Get(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] navigationProperties)
{
IQueryable<T> query = _context.Set<T>();
query = navigationProperties.Aggregate(query, (current, property) => current.Include(property));
var entity = query.FirstOrDefault(where);
return entity;
}
}
However, when I try to run the code, I get an error on the Select(x => x.Item):
var user = repo.Get(x => x.Id == 1, x => x.UserItems.Select(y => y.Item));
Error:
System.InvalidOperationException: 'The expression 'x.UserItems.AsQueryable().Select(y => y.Item)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.'
What am I doing wrong, this seems to work for my other projects?
This error Occurs because you are not passing in a navigation property (x.UserItems would be a navigation property) but rather something you want to do with the navigation property. UserItems.Select(y => y.Item) is not a property of x because Select() is a function and therefore it cannot be included.
What you are trying to do (I assume it is including UserItems and also the corresponding Items) is not going to work with your current implementation of the repository. To include navigation properties of navigation properties .ThenInclude() must be used instead of .Include() which works only for navigation properties directly defined on the Entity the DbSet is created for.
But apart from your question I would suggest not to use such an generic implementation of Repository. The main benefit from using reposiories is to separarte code related to loading and storing of entities from the rest of your code. In your case if the consumer of repository knows that navigation properties must be included and that he has to provide them - then what is the point of having a repository at all? Then the consumer again cares about database specific code which makes having a repository unneccessary. I would recommend just making a conrete "UserRepository" which can only be used to retrieve users and explicitly includes the needed properties.

.Net Core EF re including means

Hi i just start working on a project. I saw a context query line. It includes entity then includes again.
I don't understand whats what mean. I created 2 diffrent var object. when removed the self include line then debugging. I saw same thing.
This is the code
using (var context = new DbContext())
{
var entity = context.Set<Book>()
..
...
.Include(x => x.Book)
.Include(x => x.BookAuthor).ThenInclude(x => x.Author)
..
... goes on
return entity.ToList()
}
I thought this is the same thing
using (var context = new DbContext())
{
var entity = context.Set<Book>()
..
...
//remove .Include(x => x.Book)
.Include(x => x.BookAuthor).ThenInclude(x => x.Author)
..
... goes on
return entity.ToList()
}
other example code is
.Include(x => x.CategoryBranches).ThenInclude(x => x.Category)
.Include(x => x.CategoryBranches).ThenInclude(x => x.Category).ThenInclude(x => x.BookCategories);
Can anyone explain the self include again. I saw it everywhere
Thank you
If the Book has a Navigation Property called Book (that doesnt make sense with that name semantically, but technically it does), you have to include it to load it.
You can have a reference to yourself, just like to any other table via a foreign key, and have a navigation property for that.
Consider this example that makes more sense semantically:
public class Person
{
public int Id { get; set; }
public int ParentId { get; set; }
public Person Parent { get; set; } // This is a self referencing navigation property
}
So now to use the parent you'd have to:
persons.Include(p => p.Parent). ... ;
to access the parent.
Lazy loading means related entities in a model class are loaded automatically.
The parent class provided by Mafii does not use lazy loading. That's why he had to use the Include method to load (or include) the parent entity (the related entity). If he didn't do it this way, any details under the Parent navigation will not be loaded. To load a related entity automatically, simply enable lazy loading by using the virtual keyword in the navigation property of the entity. This way, you don't have to use Include anymore.
public class Person
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Note: I think there is a performance downside to this. So, if you do this, you need to be sure you actually need those entities loaded.

Deleting association between one or zero to one entities with EntityFramework

I have entities set up something like this:
public class MyThing
{
public int Id { get; set; }
public virtual MyOtherThing { get;set; }
}
public class MyOtherThing
{
public int Id { get; set; }
public virtual MyThing MyThing { get; set; }
}
My intention is that 'MyThing' can have one or none of MyOtherThing, and I also want a navigation link from MyOtherThing to it's parent.
I have configured the following EntityBaseConfiguration for the 'MyOtherThing' entity:
this.HasOptional(x => x.MyThing)
.WithOptionalPrincipal(x => x.MyOtherThing);
I can assign and modify MyOtherThing to MyThing no problem, but when I want to unassign 'MyOtherThing' from 'MyThing', how do I do this?
I tried the following:
myThing.MyOtherThing = null;
and then editing the entity by setting the EntityState.Modified state, but this didn't remove the association between the entities.
I tried adding the following to my MyThing entity, but this resulted in an EF 'Multiplicity is not valid' error when updating my database model:
public int? MyOtherThingId{ get; set; }
Thanks in advance!
I tried the following:
myThing.MyOtherThing = null;
If you want to remove an optional dependent entity (here: MyOtherThing) from a principal entity (here MyThing) by setting it to null, you have to pull the entity from the database with the dependent entity included, for example:
var mything = context.MyThings.Include(m => m.MyOtherThing)
.Single(t => t.Id == idValue);
(It's also OK when the belonging MyOtherThing is loaded into the context later, for example by lazy loading).
Without Include, myThing.MyOtherThing already is null and EF doesn't detect any change. Note that the statement myThing.MyOtherThing = null; doesn't execute lazy loading, which is a bit confusing because with collections the behavior is different.
By the way, the dependent entity can also be removed from the database directly, which is more efficient.
var ot = context.Set<MyOtherThing>().Find(idValue);
context.Set<MyOtherThing>().Remove(ot);
context.SaveChanges();

How does Linq Where(p => p.Parent == null) work in self-referencing table?

Using EF 2.0 Core, code first, I have the following entity which defines a self-referencing table:
class EntityX
{
public int EntityXId { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
//navigation properties
public EntityX Parent { get; set; }
public ICollection<EntityX> Children { get; set; }
}
I want to retrieve all EntityX objects and their children in the form of a 'tree'
I can do that using:
var entities = context.EntityX
.Include(p => p.Parent)
.Include(p => p.Children)
.Where(p => p.Parent == null);
When I call entities.ToList() this gets me what I want: a list of parent entities with their children edit only 'first' generation children. When I omit the Where() clause, I get all entities and their children.
I do not understand why the Where() clause works. Objects that are part of the Children collection have a Parent. Why are they not omitted?
Edit: my question was answered but please be aware that I was wrong in my perception of how Include() works.
LINQ applies Where condition only to the objects in the collection being queried. Whatever else you choose to load with the Include method is not subject to the same condition. In fact, EF provides no direct way of restricting the associated entities (see this Q&A).
That is how the top-level query brings you what you expected. EF retrieves the children recursively via a separate RDBMS query using the parent ID to get all its children. The restriction from the Where method does not make it to the child query.

How to get grandparent of grandchild in Entity Framework

In Entity Framework, three entities have 1 to many relationships as grandparent, children and grandchildren.
How do you obtain an the grandparent object from a grandchild's primary key?
Thank you,
Newby to EF
You could do something like the following assuming the grandchildren have a reference (as a DbSet) to their parents, and those have a reference to their parents in return:
myGrandChildren.SelectMany(x => x.Parents).SelectMany(x => x.Parents);
What SelectMany does here is for each grandchildren select all its parents and return them as a single list (instead of as a list of lists - it concats them).
If you just have one grand child - you only need one SelectMany:
grandChild.Parents.SelectMany(x => x.Parents);
Query to context will be (grandchildId it is grandchild's primary key):
var grandparent = context
.Set<GrandParent>()
.SingleOrDefault(gp => gp.Children.Any(c => c.Children.Any(gc => gc.Id == grandchildId)));
if I understand, your classes looks like this:
class GrandParent
{
...
public List<Child> Children {get; set;}
}
class Child
{
...
public List<GrandChild> Children {get; set;}
}
class GrandChild
{
...
public int Id {get; set;}
}