Entity Framework self referencing - Parent-Child - c#

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.

Related

Load child entity on the fetch of the Parent entity EFCore

I have the below model. What is the better way to load the parent entity with child entity at the time of fetching from the DB with find method?
Parent Entity:
public class Client
{
public int Id { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public DateTime DateOfBirth { get; set; }
public Address Address { get; set; }
}
Child Entity:
public class Address
{
public int Id { get; set; }
public string FirstLine { get; set; }
public string SecondLine { get; set; }
public string Province { get; set; }
}
Now when I try to fetch the data using the Find method I got the address entity null, but when I check in the DB data exist for that ID in Child table too.
referenceContext.Clients.Find(client.Id);
Is there a way to overcome this? When I fetch the parent object and at the same time the value of the child entity is also loaded with the parent.
Notes: As of now, if I used the Include(i => i.Address) then, and then, only I am able to load the child entity.
I already use the Include but is there any other option exist to load child entity if I get the parent entity.
referenceContext.Clients.Where(c => c.IsActive.Equals(true))
.Include(i => i.Address).ToList();
As you said:
Notes: As of now, if I used the Include(i => i.Address) then, and then, only I am able to load the child entity.
Yes! this is the best way to load related data in EF Core.
You further said:
I already use the Include but is there any other option exist to load child entity if I get the parent entity.
Yes! There is! That is called Lazy loading. To enable lazy loading you have to make the navigation property virtual as follows:
public class Client
{
public int Id { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public DateTime DateOfBirth { get; set; }
public virtual Address Address { get; set; } // <-- Here it is
}
And you have to register your DbConext as follows:
services.AddDbContext<BloggingContext>(
b => b.UseLazyLoadingProxies() // <-- Here is it is
.UseSqlServer(myConnectionString));
UseLazyLoadingProxies() method is available in the Microsoft.EntityFrameworkCore.Proxies nuget package.
Note: You cannot disable lazy loading for a certain query. So using Eager loading is the best way to load related data in EF Core.
In EF, there is a concept called Eager Loading using .Include.
MS Docs - Loading Related Data - EF Core
.NET Fiddle
using MyContext context = new MyContext();
IList<Client> clients =
context.Clients
.Include(c => c.Address)
.Where(c => c.LastName == "patel")
.ToList();
You can use Include()
Linq query
using (var context = new DBEntities())
{
var result = (from c in context.Client.Include("Address")
where c.IsActive
select c).ToList();
}
Lambda Expression
using (var context = new DBEntities())
{
var result = context.Client.Include(p => p.Address).Where(c => c.IsActive).ToList();
}

Projecting an IEnumerable inside an Projected IQueryable making N Requests to the Database

I'm making an Asp.net Core Api and one of the Actions of the Controller i need to return an IQueryable of a DTO, but one of the properties is an IEnumerable of another DTO in a relationship one to many in the database model of EF.
For example:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int OrderNumber { get; set; }
public Customer Customer { get; set; }
}
And the DTO
public class CustomerDTO
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<OrderDTO> Orders { get; set; }
}
public class OrderDTO
{
public int OrderNumber { get; set; }
}
This is just a simple example because in my application there is alot more fields on each table and i cannot expose everything to the frontend application, that's why i'm using DTOs.
I'm using the Select to Project each element to the DTO, there is no problem there because i can see on the ASP.NET Core Web Server output that the system is only making one request to the database (to get the Customers), but the problem comes when i try to project the OrdersDTO inside the CustomerDTO. What's happening is for example if i have 100 customers the EF will make 101 requests to the database. (1 to get the Customers and 100 to get the Orders for each customer)
[HttpGet]
[EnableQuery]
public IEnumerable<CustomerDTO> Get()
{
return context.Customer
.Select(s => new CustomerDTO
{
Id = s.Id,
Name = s.Name,
Orders = s.Orders.Select(so => new OrderDTO
{
OrderNumber = so.OrderNumber
})
});
}
If i call ToList() before i project the elements using Select it will make only one request to the Database (as intended), but i need to return an IQueryable because i'm using OData, so that the frontend application can execute queries directly to the database even if is just a DTO
I already tried putting like this
Orders = s.Orders.Any() ? s.Orders.Select(so => new OrderDTO
{
OrderNumber = so.OrderNumber
}) : new List<OrderDTO>()
It solved the problem partially because if out of the 100 customers theres is only 50 that have orders the EF will only make 50 requests to the Database.
I would like to know if there is a solution to this problem because i don't want the application doing hundreds of queries to the database each time some user calls this endpoint of the API.
You need to add ToList() when projecting the inner collection.
Orders = s.Orders.Select(so => new OrderDTO
{
OrderNumber = so.OrderNumber
}).ToList() // <--
First because CustomerDTO.Orders property type is List<OrderDTO>, so the code does not compile w/o that.
But even it wasn't (let say it's IEnumerable<OrderDTO>), you still need ToList in order to get EF Core 2.1 introduced Optimization of correlated subqueries
:
We have improved our query translation to avoid executing "N + 1" SQL queries in many common scenarios in which the usage of a navigation property in the projection leads to joining data from the root query with data from a correlated subquery. The optimization requires buffering the results from the subquery, and we require that you modify the query to opt-in the new behavior.
Note the last sentence - "we require that you modify the query to opt-in the new behavior". Then documentation contains an example and continues with:
By including ToList() in the right place, you indicate that buffering is appropriate for the Orders, which enable the optimization

How to write T-SQL many-to-many with subquery in EF

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.

Linq to Entities filter navigation collection properties

I have an order class that has two navigation properties of type collections; OrderDetails and Categories. There is a one to many relationship between Order and both OrderDetail and Category. An Order may or may not have a Category associated to it. An OrderDetail record has a CustomerID field.
I am trying to retrieve a list of Orders that have categories associated to them and their corresponding OrderDetail records for a specific customer. I want to achieve this using linq to entities if possible.
public class order
{
public order()
{
OrderDetails = new list<OrderDetail>();
Categories = new list<Category>();
}
public int OrderID { get; set; }
public DateTime OrderDate { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
public virtual List<Category> Categories{ get; set; }
}
public class OrderDetail
{
public int OrderDetailID { get; set; }
public int CustomerID { get; set; }
public virtual Order Order { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public virtual Order Order { get; set; }
}
I can get it to work if I start with the OrderDetail entity first as shown below but how would I write this if I want to start with the Order entity first?
var query = from od in _dbCtx.OrderDetails
.Include("Order")
.Include("Order.Categories")
where od.CustomerID == custID && od.Order.Categories.Count > 0
select od;
You can try this:
var query =_dbCtx.Orders.Include("OrderDetails")
.Include("Categories")
.Where(o=>o.Categories.Count>0)
.SelectMany(o=>o.OrderDetails.Where(od=>od.CustomerID == custID));
The key in this query is the SelectMany extension method, which is used to flatten the Where's result into one single collection.
Edit 1
Due to you have disabled lazy loading, the Order navigation property in the OrderDetails that you get when you execute my query are null. One option could be using the Load method when you use the result:
foreach(var od in query)
{
// Load the order related to a given OrderDetail
context.Entry(od).Reference(p => p.Order).Load();
// Load the Categories related to the order
context.Entry(blog).Collection(p => p.Order.Categories).Load();
}
Another option could be returning an anonymous type:
var query =_dbCtx.Orders.Include("OrderDetails")
.Include("Categories")
.Where(o=>o.Categories.Count>0)
.SelectMany(o=>o.OrderDetails.Where(od=>od.CustomerID == custID).Select(od=>new {Order=o,OrderDetail=od}));
But I don't like anyone of these solutions.The most direct way is the query that you already had from the beginning.
The default setting for Entity Framework is to allow lazy loading and dynamic proxies.
And in this case when you are using the virtual keyword on the relational properties these 'should' (in case you have not disabled it in EF) load with Lazy Loading.
Lazy Loading Loads the relational properties when you need it. Example:
var load = data.Orders.OrderDetails.Tolist() // Would load all OrderDetails to a list.
//Below would load all OrderDetails that has a OrderId smaller than 5
var loadSpecific = data.Orders.Where(x=> x.OrderId < 5).OrderDetails.ToList()
The case you are describing is Eager Loading('Include' statements), Nothing wrong with it. But if you are planning on using it I would consider using below syntax instead. This would give compilation error if you decide to change the name of the relational property.
var load = data.Orders
.Include(x => x.OrderDetails)
.Include(x => x.Categories)
I suggest you take 10-15 minutes of time and read up on it in this article:
https://msdn.microsoft.com/en-us/data/jj574232.aspx

How to explicitly load relations for multiple models at once in Entity Framework?

I have a model similar to the following:
class Parent {
public int Id { get; set; }
public string Name { get;set; }
public ICollection<Child> Children { get; set; }
public GrandChildren SpecialGrandChild {
get {
return Children.SelectMany(c => c.Children).Where(...).Single();
}
}
}
class Child {
public int Id { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public ICollection<GrandChild> Children { get; set; }
}
class GrandChild {
public int Id { get; set; }
public string Name { get;set; }
public int ParentId { get; set; }
public Child Parent { get; set; }
}
I also have a fairly complex query involving all three tables. From that query I want to extract all the Parent objects, and I will be displaying a property of the SpecialGrandChild for each one.
The problem is that if I do:
query.Include(p => p.Children.Select(c => c.Children));
EF will generate an ungodly sql query, and take a ton of time to build the query (on some cases over 10 seconds!). The query is cached so further calls are much faster. If I drop the Include call, I do not get such a bad first-call performance, but of course I get a worse performance as I will be doing M*N+1 queries (for each Parent, fetch the Children, and for each Child fetch the GrandChildren).
So the question is: can I explicitly load all the Children and GrandChildren for all the loaded Parents in a single call? If so, how can I do so?
I tried querying all the Childs for the currently loaded Parents as follows:
var ids = parents.Select(p => p.Id);
(from c in Childs where ids.Contains(c.ParentId) select c).Include("Children").Load();
But that call does not tell EF that all the associated Childs are loaded so it still goes to the DB when I access the association properties.
Load your data in two steps:
var dbParent = ...; // query all Parent's
var dbChild = ...; // query all Child's
var parents = dbParent.Include(p => p.Children).ToList();
dbChild.Include(p => p.Children).toList();
That should make parents have a list of all parents, and because of tracking, each parent will have each of its children.
If you have to apply a filter condition on parent, you should make it on children too.
Since I don't know enough about your parent variable and a context your parent objects are probably attached on (or maybe not) I can't give you a precise answer.
But what you're looking for should look like this where ctx is an instance of your context and Parents a DbSet Property:
IQueryable<Parent> query = ctx.Parents.Include("Children.Children");
List<Parent> myTree = query.toList();

Categories