Suppose I have two classes that have a many-to-one relationship
public class ParentObject{
public int Id { get; set; }
public List<ChildObject> Children { get; set; }
...
}
public class ChildObject{
public int Id { get; set; }
public ParentObject Parent { get; set; }
}
When I add the migration and update the database, the result is two tables:
ParentObject
Id : int
...
ChildObject
Id : int
ParentObjectId : int
...
Now I want to search for all of the children of a particular parent. In SQL, I can do this without having to join in the parent table simply by querying on the ChildObject.ParentObjectId column.
SELECT * FROM [ChildObject] c WHERE c.ParentObjectId = parentId
But in EF Core, the only way I've found to do this is to use .Include(c => c.Parent) and then .FirstOrDefault(c => c.Parent.Id == parentId). This creates a join to the ParentObject table.
Is there a way to query the ChildObject table without the .Include()?
Simply use the navigation property to access the parent PK:
var children = db.Children
.Where(c => c.Parent.Id == parentId)
.ToList();
If you include some other Parent entity property in Where, OrderBy, Select (or the Parent itself inside Select or Include), of course EF Core will create a join. But if you access only the PK of the related entity, then EF Core is smart enough the use the FK instead and avoid the join.
Please note that the returned Child objects won't have Parent property populated (will be null) except the context is not already tracking a Parent entity instance with that PK.
Related
Imagine a model of User that can have Parents and also can have Children.
How would you model such a case in EF Core?
I tried with something like that (pseudo-code)
public class User
{
public ICollection<Relation> Relations {get;set;}
public ICollection<User> Parents => Relation.Where(r => r.Relation == 'Parents')
public ICollection<User> Children => Relation.Where(r => r.Relation == 'Children')
}
public class Relaction
{
public User User1 {get;set;}
public Guid User1Id {get;set;}
public User User2 {get;set;}
public Guid User2Id {get;set;}
public Relation Relation {get;set;} //some enum or sth to indicate relation type
}
But in such modeling, I'm not able to force EF DbContext to fetch into User.Relations data where UserId is in User1Id and in User2Id.
Any idea?
What you are asking for is a classic many-to-many self relationship - (1) user as parent can have many users as children, and (2) user as child can have many users as parents.
Thus it is modelled with one main entity and one join (linking) entity similar to what you have shown. The linking entity does not need special indicator because the two FKs determine the role. i.e. lets change your example with more descriptive names:
public class User
{
public Guid Id { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
Now, in pseudo code, given User user, then
user.Parents = db.Users.Where(u => user == u.Child)
user.Children = db.Users.Where(u => user == u.Parent)
EF Core 5.0+ allows you to hide the join entity (it still exists, but is maintained implicitly) and model the relationship with the so called skip navigations, which are the natural OO way of representing such relationship, e.g. the model becomes simply
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
This is all needed to create such relationship.
However the name of the join table and its columns by convention won't be what normally you would do - in this case, they would be "UserUser" table with "ParentsId" and "ChildrenId" columns.
If you use migrations and don't care about the names, then you are done and can safely skip the rest.
If you do care though, luckily EF Core allows you to change the defaults with fluent configuration (even though in a not so intuitive way):
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<Dictionary<string, object>>("UserRelation",
je => je.HasOne<User>().WithMany()
.HasForeignKey("ParentId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.HasOne<User>().WithMany()
.HasForeignKey("ChildId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.ToTable("UserRelations")
.HasKey("ParentId", "ChildId")
);
Here Dictionary<string, object> is the shared type EF Core will use to maintain the join entity in memory (change tracker). And is the most annoying thing in the above configuration since in a future they might change their minds and use different type (there are actually plans to do that in EF Core 6.0), so you'll have to update your mappings. Note that this does not affect the database design, just the memory storage type in EF Core change tracker.
So, because of that and the fact that in some scenarios it is better to work directly with the join entity, you could actually combine both approaches (explicit join entity and skip navigations) and get the best of both worlds.
To do than, you add the explicit entity and (optionally) navigations from/to it. The next is w/o collection navigations from User to UserRelation (with fully defined navigation you would need two ICollection<UserRelation> properties there):
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
and required minimal fluent configuration
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<UserRelation>(
je => je.HasOne(e => e.Parent).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.HasOne(e => e.Child).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.ToTable("UserRelations")
);
The result is the same database model, but with different in-memory representation of the join entity and ability to query/manipulate it directly. Actually you can do the same with implicit entity, but in type unsafe way using sting names and object values which need to be cast to the appropriate type. This probably will improve in the future if they replace Dictionary<string, object> with some generic type, but for now explicit entity combined with skip navigations looks the best.
You can find (I guess better than mine) explanation of all this in the official EF Core documentation - Many-to-many and the whole Relationships section in general.
I have an issue I am unsure how to solve. I have three models, similar to as follows.
public class Parent : BaseEntity {
[Key]
public string Guid { get; set; }
[NotMapped]
public List<Child> Childs { get; set; }
}
public class Child : BaseEntity {
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public List<Detail> Details { get; set; }
}
public class Detail : BaseEntity {
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public Child Child { get; set }
}
I'm attempting to include both the children and the details. However, I am not guaranteed the Guid on the child exists in the parent table. That's why I went with NotMapped, but am willing and able to change that if need be. Right now I have this:
query.GroupJoin(context.Parents,
parent => parent.ChildGuid,
child => child.Guid,
(parent, childs) => new
{
Parent = parent,
Childs = childs
}
)
.AsEnumerable()
.Select(combos =>
{
combos.Parent.Childs = combos.Childs.ToList();
return combos.Parent;
})
.AsQueryable();
But that, of course, does not include the Details. Not sure if I'm heading the right direction here or not, but could use some direction if anyone has run into this before.
A typical parent-child relationship has the ParentID on the child table/entity. Your entity definitions seem to reflect that, but then your Linq expression refers to some parent.ChildGuid that isn't mentioned in your entity.
First let's correct your entity definition relationships. Childs shouldn't be excluded. A parent can have 0 or multiple children. Collections should be declared as virtual ICollection<T> rather than concrete List<T>. virtual enables lazy loading and helps ensure that EF proxies are fully functional for tracking changes of items in the collections. We should also initialize those collections with a new concrete list. This helps ensure that any new entity we create is ready to go accepting children.
public class Parent : BaseEntity
{
[Key]
public string Guid { get; set; }
public virtual ICollection<Child> Childs { get; set; } = new List<Child>();
}
public class Child : BaseEntity
{
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public virtual ICollection<Detail> Details { get; set; } = new List<Detail>();
}
public class Detail : BaseEntity
{
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public virtual Child Child { get; set }
}
EF can automatically map relationships provided you follow supported conventions in your naming. Chances are with the "Guid" syntax for your ID columns, EF won't work these out automatically so you will probably need to give it some help through configuration. For example in the OnModelCreating of the DbContext:
// EF Core
modelBuilder.Entity<Parent>()
.HasMany(x => x.Childs)
.WithOne()
.IsRequired() // If a Child must have a ParentGuid
.HasForeignKey(x => x.ParentGuid);
Since Child has a ParentGuid but no Parent reference declared, we use the WithOne() with no mapping, then use the HasForeignKey to set up the relationship. Similarly we set up the relationship between Child and Details:
// EF Core
modelBuilder.Entity<Child>()
.HasMany(x => x.Details)
.WithOne(x => x.Child)
.IsRequired() // If a Detail must have a Child
.HasForeignKey(x => x.ChildGuid);
In this case since we have a Child reference on Detail so we map to that.
Now regarding this statement:
However, I am not guaranteed the Guid on the child exists in the parent table.
From this it somewhat implies that a ParentGuid on the Child table might not exist in the Parents table. This would be a fairly serious concern from a database normalization and ensuring a valid data state. I would highly recommend to avoid trying to use a non-normalized schema such as attempting to "share" a Child table between multiple Parent tables. A classic example where this is tried is having an Address table (Child) referencing a ParentId that could point at a Customer (Parent) or Site (Parent) and/or other parent-like tables. Instead, you should consider a more normalized approach which would use a CustomerAddress and SiteAddress etc. linking table to ensure these relationships can be mapped out.
If a Child can exist without a Parent then you just need to remove the .IsRequired().
Now when querying, you don't need to worry specifically about joining and grouping, just query through the navigation properties and either eager-load the related data you want when working with the Entity graph, or project the data using Select when querying for details:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
To get the children with their associated Parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.ToList();
If you want to include children that have no parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.Union(context.Childs.Where(x => x.ParentGuid == null))
.ToList();
These are very rough guesses as the type of query you want to perform. Again, if the ParentGuid could be referring to a non-existent row in the DB or a different table I would really look at correcting that to ensure the data maintains referential integrity. I don't recommend trying to "break" EF behavior to work with an effectively broken schema. You may get it to have a semblance of working, but it could very easily lead to exceptions or unexpected behaviour.
I have two classes with a many-to-many relationship in a ASP.NET EF application. I'm trying to find all Listings that have any Categories which is posted from a view. The categories are checkboxes on the view form.
These are the classes with navigation properties simplified for example:
public class Listing
{
public int ID { get; set; }
public ICollection<Category> Categories { get; set; }
...
}
public class Category
{
public int ID { get; set; }
public ICollection<Listing> Listings { get; set; }
...
}
// this is the join table created by EF code first for reference
public class CategoryListings
{
public int Category_ID { get; set; }
public int Listing_ID { get; set; }
}
This is the query I am trying to use in my MVC Controller but it doesn't work and I don't really know what else to try:
if (model.Categories !=null && model.Categories.Any(d => d.Enabled))
{
List<Listing> itemsSelected = null;
foreach (var category in model.Categories.Where(d => d.Enabled))
{
var itemsTemp = items.Select(x => x.Categories.Where(d => d.ID == category.ID));
foreach (var item1 in itemsTemp)
{
itemsSelected.Add((Listing)item1); //casting error here
}
}
items = itemsSelected;
}
In SQL, I would write this using a subquery (the subquery represents the multiple categories that can be searched for):
select l.id, cl.Category_ID
from
listings as l inner join CategoryListings as cl
on l.id=cl.Listing_ID
inner join Categories as c on c.ID = cl.Category_ID
where c.id in (select id from Categories where id =1 or id=3)
How do I write that SQL query in EF using navigators or lambda? The subquery in the SQL will change each search and can be any id or IDs.
You forgot to tell us what objects are in your collection items. I think they are Listings. Your case doesn't work, because itemsTemp is a collection of Categories, and every item1 is a Category, which of course can't be cast to a Listing.
Advice: to debug casting problems, replace the word var
with the type you actually expect. The compiler will warn you about
incorrect types. Also use proper identifiers in your lambda expressions.
This makes them easier to read
IQueryable<???> items = ... // collection of Listings?
List<Listing> itemsSelected = null;
IQueryable<Category> enabledCategories = model.Categories.Where(category => category.Enabled));
foreach (Category category in enabledCategories)
{
IEnumerable<Category> itemsTemp = items
.Select(item => item.Categories
.Where(tmpCategory => tmpCategory.ID == category.ID));
foreach (Category item1 in itemsTemp)
{
// can't cast a Category to a Listing
We'll come back to this code later.
If I look at your SQL it seems that you want the following:
I have a DbContext with (at least) Listings and Categories.
I want all Listings with their Categories that have category Id 1 or 3
It's good to see that you followed the entity framework code-first conventions, however you forgot to declare your collections virtual:
In entity framework the columns in a table are represented by
non-virtual properties. The virtual properties represent the relations
between the table.
With a slight change your many-to-many relation can be detected automatically by entity framework. Note the virtual before the ICollection
class Listing
{
public int ID { get; set; }
// every Listing has zero or more categories (many-to-many)
public virtual ICollection<Category> Categories { get; set; }
...
}
class Category
{
public int ID { get; set; }
// every Category is used by zero or more Listings (many-to-many)
public ICollection<Listing> Listings { get; set; }
...
public bool Enabled {get; set;}
}
And the DbContext
public MyDbContext : DbContext
{
public DbSet<Listing> Listings {get; set;}
public DbSet<Category> Categories {get; set;}
}
Although a relational database implements a many-to-many relationship with a junction table, you don't need to declare it in your DbContext. Entity framework detects that you want to design a many-to-many and creates the junction table for you.
But how can I perform my joins without access to the junction table?
Answer: Don't do joins, use the ICollections!
Entity Framework knows which inner joins are needed and will do the joins for you.
Back to your SQL code:
Give me all (or some) properties of all Listings that have at least one Category with Id equal to 1 or 3
var result = myDbcontext.Listings
.Select(listing => new
{ // select only the properties you plan to use
Id = listing.Id,
Name = listing.Name,
...
Categories = listing.Categories
// you don't want all categories, you only want categories with id 1 or 3
.Where(category => category.Id == 1 || category.Id == 3)
.Select(category => new
{
// again select only the properties you plan to use
Id = category.Id,
Enabled = category.Enabled,
...
})
.ToList(),
})
// this will also give you the Listings without such Categories,
// you only want Listings that have any Categories left
.Where(listing => listing.Categories.Any());
One of the slower parts of database queries is the transfer of the selected data from the DBMS to your local process. Hence it is wise to only transfer the properties you actually plan to use. For example, you won't need the foreign keys of one-to-many relationships, you know it equals the Id value of the one part in the one-to-many.
Back to your code
It seems to me, that your items are Listings. In that case your code wants all Listings that have at least one enabled Category
var result = myDbContext.Listings
.Where(listing => ...) // only if you don't want all listings
.Select(listing => new
{
Id = listing.Id,
Name = list.Name,
Categories = listing.Categories
.Where(category => category.Enabled) // keep only the enabled categories
.Select(category => new
{
Id = category.Id,
Name = category.Name,
...
})
.ToList(),
})
// this will give you also the Listings that have only disabled categories,
// so listings that have any categories left. If you don't want them:
.Where(listing => listing.Categories.Any());
Do you have a relation between Listing/Category and CategoryListings?
Here is example for EF 6: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
If you have it the query will be simple, something like that:
CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId))
.Select(x => new {x.ListingRefId, x.CategoryRefId});
If you need all properties of Listing or Category, Include() extension will help.
I have a self referencing table and i need to bind the date from table to tree view. Parent-Child. My question is How to get tree view from that table using Entity Framework using anonymous type
some like this:
var tree = db.Categories.Select(g => new
{
id = g.CategoryId,
text = g.CategoryName,
children = g.children.Select(w => new
{
id = w.CategoryId,
parent = w.ParentCategoryId,
text = w.CategoryName,
}).ToList(),
}
).ToList();
Here is the code:
public partial class Category
{
public Category()
{
this.children = new HashSet<Category>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public Nullable<int> ParentCategoryId { get; set; }
public virtual ICollection<Category> children { get; set; }
public virtual Category Parent { get; set; }
}
I suppose to create a type for your purpose rather then using anonymous type and fill model via recursive method.
var three = BuildThree(db.Categories);
public IEnumerable<CategoryVm> BuildThree(IEnumerable<Categories> categories, int? parentCategoryId = null)
{
if (categories == null)
return null;
var result = categories.select(c => new CategoryVm()
{
id = c.CategoryId,
text = c.CategoryName,
parent = parentCategoryId,
children = BuildThree(c.children, c.CategoryId)
}
return result;
}
This solution there is on drawback - each time time when you call navigation property (children) you will make request to database. If you want to make it in one request and you have only one level of nested categories then .Include(c => c.Children) enough otherwise you have to make a choice one of the next options:
Write a common table expression (CTE) query, put it to view or stored procedure and map it by means of EF. The role of EF is not really big because the most tricky part is the SQL query
Especially for this kind of purpose, Microsoft SQL Server has hierarchyid but EF does not support it from the box. However there are some workarounds: Entity Framework HierarchyId Workarounds
You can add something like rootId to the Comment entity, when each child replay will has a link to root comment. After that you can load all hierarchy to memory in one sql query and map it manually. Given that database is bottleneck it will much faster then make new query for each level of hierarchy.
I need to setup a one-to-many relationship with entity framework.
I am trying to build a recipe, but a recipe CAN be composed of recipes.
How to achieve that with EF ?
public class Recipe
{
public Recipe()
{
Deleted = false;
Recipes = new List<Recipe>();
}
[Key]
public int RecipeId { get; set; }
public virtual List<Recipe> Recipes { get; set; }
}
I don't need the whole recursive thing(parent, child) only the child I'm interested in. Is it feasible using EF ? Can anyone point me to the right direction
Ex:
Recipe A
Recipe B => A
Recipe C
Recipe D => B
Recipe E => B, C
This will be a many-to-many relationship, because each recipe can have multiple and multiple parent recipes. When you create your child recipes, you will have to assign a parent to it, so you must define the parent relationship as well.
You could try the InverseProperty dataannotation, for more info check out this question:
Entity Framework 4.1 InverseProperty Attribute