ASP.NET MVC LINQ Entity Framework recursive - c#

I'm not sure how to write LINQ query. I have these models:
class Category
{
ICollection<Thread> Threads {get;set;}
ICollection<Category> SubCategories {get;set;}
}
class Thread
{
Category Category {get;set;}
//Some Stuff
}
So, there could be categories linked like -
Category1
Category2
Category3
Category4
Category5
Category6
I want find all threads linked to Category2 and it SubCategories(3, 4, 5).
I thought about just take Category1 form db, and using C# recursive function build List of threads i need, but i feel it's bad idea.
Any ideas or links would be great. Thank you!
There code, but there is Topics(in Threads), i didnt mention it couse it's not rly matter(at least i think so)
public ActionResult ShowCategoryTopics(int id)
{
var category = db.Categories.Where(x => x.Id == id).FirstOrDefault();
var topics = GetTopics(category);
return View();
}
public List<Topic> GetTopics(Category category)
{
List<Topic> topics = new List<Topic>();
if (!category.IsDeleted && !category.IsHidden)
return null;
foreach (Thread thread in category.Threads)
{
topics.AddRange(thread.Topics.Where(x => !x.IsDeleted).ToList());
}
foreach(Category childCategory in category.SubCategories)
{
topics.AddRange(GetTopics(childCategory));
}
return topics;
}

While EF can load joined records lazily and transparently, it can't load recursive joined records cause it's too complicate.
So, first of all, remove the Category.Threads navigation property:
public class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
// you can remove the attribute
[ForeignKey(nameof(ParentId))]
public virtual Category Parent { get; set; }
public string Title { get; set; }
public virtual ICollection<Category> SubCategories { get; set; } = new HashSet<Category>();
}
public class Thread
{
public int Id { get; set; }
public int CategoryId { get; set; }
// you can remove the attribute
[ForeignKey(nameof(Category))]
public Category Category { get; set; }
public string Title { get; set; }
}
Now you can use Common Table Expressions to recursive query and Database.SqlQuery<TElement> method to load the result of the query.
This is the SQL query to get all Threads corresponded to the specified #CategoryId and all its subcategories:
WITH RecursiveCategories(Id, ParentId, Title)
AS
(
SELECT Id, ParentId
FROM dbo.Categories AS c1
WHERE Id = #CategoryId
UNION ALL
SELECT Id, ParentId
FROM dbo.Categories AS c2
INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)
The method to load threads of specified category recursively:
public IEnumerable<Thread> GetAllRecursivelyByCategoryId(int categoryId)
{
var query = #"WITH RecursiveCategories(Id, ParentId, Title)
AS
(
SELECT Id, ParentId
FROM dbo.Categories AS c1
WHERE Id = #CategoryId
UNION ALL
SELECT Id, ParentId
FROM dbo.Categories AS c2
INNER JOIN c1 ON c2.ParentId = c1.Id
)
SELECT th.*
FROM dbo.Threads AS th
WHERE th.CategoryId IN (SELECT Id FROM RecursiveCategories)";
var parameter = new SqlParameter("CategoryId", categoryId);
return _dbContext.Database
.SqlQuery<Thread>(query, parameter)
.AsEnumerable();
}
This method runs the recursive query and maps the result to enumerable of threads. Here is only one request to the SQL server, and the response contains only necessary threads.

The way to do this all in database would be to use a recursive Common Table Expression (CTE) to extract all the category hierarchy. However this is a bit difficult to implement using Linq without resorting to direct SQL.
As you state there will only be about 100 or so categories it may me simpler to do the category extraction in the code rather than database.
I'm assuming you have the foreign key columns as wells as the navigation properties.
First a Helper function, converts a list of categories to an enumerable of nested ids;
static IEnumerable<int> GetCategoryIds(IList<Category> categories, int? targetId) {
if (!targetId.HasValue) {
yield break;
}
yield return targetId;
foreach (var id in categories.Where(x => x.ParentId==targetId).SelectMany(x => GetCategoryIds(x.Id))) {
yield return id;
}
}
Now your query
var ids = GetCategoryIds(db.Categories.ToList(), 2).ToList();
var threads = db.Threads.Where(x => ids.Contains(x.CategoryId));

Related

conversion - SQL to LINQ

How can the following be accomplished using LINQ
SELECT r.BrandID
FROM dbo.Items AS r
JOIN Brands AS d ON r.BrandID = d.BrandID
WHERE CategoryID IN (SELECT CategoryID
FROM dbo.Categories
WHERE Name = 'Bread - Bakery')
Code for Brand class:
public class Brand
{
public int BrandID { get; set; }
[DisplayName("Brand Name")]
public string Name { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Item> Items { get; set; }
}
Code for Item class:
public class Item
{
[Key]
public int ItemID { get; set; }
public virtual Category Category { get; set; }
public virtual Brand Brand { get; set; }
public int CategoryID { get; set; }
public int BrandID { get; set; }
}
code for Category class:
public class Category
{
[Key]
public int CategoryID { get; set; }
[DisplayName("Category Name")]
public virtual string Name { get; set; }
public virtual List<Brand> Brands { get; set; }
public virtual List<Item> Items { get; set; }
}
dbContext.Items
.Where(x => x.Category.Name.Equals("Bread - Bakery"))
.Select(x => x.BrandID);
I am not sure why you need to use below join. It seems that it is not needed (unless intentionally inner joined with brands to remove non-matching records from items)
JOIN Brands AS d ON r.BrandID = d.BrandID
Hm, pity you didn't write your requirements, now I have to guess what they are from your SQL code.
So you have a database with Brands, Items and Categories. Every Item has a Category, every Category can be used by zero or more Items: a one-to-many relation
Every Item is of a certain Brand, Every Brand can have zero or more items: also a straightforward one-to-many relation.
Finally every Brand has zero or more Categories, every Category has zero or more Brands: many-to-many
Now you take your collection of Items, you only want to keep those Items that have a Category with a Name that equals Bread - Bakery. From the remaining items you want all their BrandIds.
The requirement would be: "Give me the BrandIds of all Items that have a Category with a Name that equals 'Bread - Bakery`.
If you use entity framework, it is usually easier if you use the virtual ICollection instead of doing the join yourself. Entity framework knows the relations between the tables and will compose the proper joins for it.
var result = myDbContext.Items // from the collection of Items
.Where(item => item.Category.Name == "Bread - Bakery") // keep only those with a Category
// that has a Name equal to ...
.Select(item.BrandId); // from the remaining items select the BrandId
If you really want, and you can convince your project leader that entity framework can't be trusted to do the proper joins you can do the join yourself:
// get the sequence of categories that I'm interested in:
var breadBakeryCategories = myDbContext.Categories
.Where(category => category.Name == "Bread - Bakery");
// join the items with these categories
// and select the BrandIds
var requestedBrandIds= myDbContext.Items
.Join(breadBakeryCategories,
item => item.CategoryId, // from every Item take the CategoryId,
category => category.CategoryId, // from every Category take the CategoryId
(item, category) => item.BrandId); // when they match, select the BrandId
TODO: consider concatenating this into one big ugly LINQ statement.
Remark 1
You do realize that your result might have the same BrandIds several times, don't you?
If you don't want that, start with the Brands:
var result = myDbContext.Brands
.Where(brand => brand.Items.Select(item => item.Category.Name)
.Where(name => name == "Bread - Bakery")
.Any())
.Select(brand => brand.brandId);
In words: from the collection of Brands, keep only those Brands that have at least one Category with a name equal to "Bread - Bakery". From the remaining Brands select the BrandId.
** Remark 2 **
Why are your one-to-many Lists instead of ICollections? Are you sure that brand.Categories[4] has a proper meaning?
var result = myDbContext.Brands
.Where(brand => brand.Category[4].Name == "Bread - Bakeries");
Your compiler won't complain, but you'll get runtime errors.
Consider using virtual ICollection<...> for your one-to-many and many-to-many relations. This way you'll have exactly the functionality you expect with a database table, and your compiler will complain if you try to use functionality that can't be translated into SQL

Select categories with subcategories [duplicate]

I have the following entity:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public Item Parent { get; set; }
public List<Item> Children { get; set; }
public double PropertyA { get; set; }
public double PropertyB { get; set; }
...
}
Now I want to query the database and retrieve data of all the nested children.
I could achieve this by using Eager Loading with Include():
var allItems = dbContext.Items
.Include(x => Children)
.ToList();
But instead of Eager Loading, I want to do the following projection:
public class Projection
{
public int Id { get; set; }
public List<Projection> Children { get; set; }
public double PropertyA { get; set; }
}
Is it possible to retrieve only the desired data with a single select?
We are using Entity Framework 6.1.3.
Edit:
This is what I have tried so far.
I really don't know how to tell EF to map all child Projection the same way than their parents.
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: The type 'Projection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
var allItems = dbContext.Items
.Select(x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection()
{
Id = c.Id,
PropertyA = c.PropertyA,
Children = ???
})
})
.ToList();
Generally speaking, you can't load a recursive structure of unknown unlimited depth in a single SQL query, unless you bulk-load all potentially relevant data irregardless whether they belong to the requested structure.
So if you just want to limit the loaded columns (exclude PropertyB) but its ok to load all rows, the result could look something like the following:
var parentGroups = dbContext.Items.ToLookup(x => x.ParentId, x => new Projection
{
Id = x.Id,
PropertyA = x.PropertyA
});
// fix up children
foreach (var item in parentGroups.SelectMany(x => x))
{
item.Children = parentGroups[item.Id].ToList();
}
If you want to limit the number of loaded rows, you have to accept multiple db queries in order to load child entries. Loading a single child collection could look like this for example
entry.Children = dbContext.Items
.Where(x => x.ParentId == entry.Id)
.Select(... /* projection*/)
.ToList()
I see only a way with first mapping to anonymous type, like this:
var allItems = dbContext.Items
.Select(x => new {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new {
Id = c.Id,
PropertyA = c.PropertyA,
})
})
.AsEnumerable()
.Select(x => new Projection() {
Id = x.Id,
PropertyA = x.PropertyA,
Children = x.Children.Select(c => new Projection {
Id = c.Id,
PropertyA = c.PropertyA
}).ToList()
}).ToList();
A bit more code but will get the desired result (in one database query).
Let's say we have the following self-referencing table:
public class Person
{
public Person()
{
Childern= new HashSet<Person>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
[StringLength(50)]
public string Name{ get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
And for some point of time you need to get all grandsons for specific persons.
So, first of all I will create stored procedure(using code-first migration) to get all persons in the hierarchy for those specific persons:
public override void Up()
{
Sql(#"CREATE TYPE IdsList AS TABLE
(
Id Int
)
GO
Create Procedure getChildIds(
#IdsList dbo.IdsList ReadOnly
)
As
Begin
WITH RecursiveCTE AS
(
SELECT Id
FROM dbo.Persons
WHERE ParentId in (Select * from #IdsList)
UNION ALL
SELECT t.Id
FROM dbo.Persons t
INNER JOIN RecursiveCTE cte ON t.ParentId = cte.Id
)
SELECT Id From RecursiveCTE
End");
}
public override void Down()
{
Sql(#" Drop Procedure getChildIds
Go
Drop Type IdsList
");
}
After that you can use Entity Framework to load the ids(you could modify stored procedure to return persons instead of only returning ids) of persons under the passed persons(ex grandfather) :
var dataTable = new DataTable();
dataTable.TableName = "idsList";
dataTable.Columns.Add("Id", typeof(int));
//here you add the ids of root persons you would like to get all persons under them
dataTable.Rows.Add(1);
dataTable.Rows.Add(2);
//here we are creating the input parameter(which is array of ids)
SqlParameter idsList = new SqlParameter("idsList", SqlDbType.Structured);
idsList.TypeName = dataTable.TableName;
idsList.Value = dataTable;
//executing stored procedure
var ids= dbContext.Database.SqlQuery<int>("exec getChildIds #idsList", idsList).ToList();
I hope my answer will help others to load hierarchical data for specific entities using entity framework.

get parent name and child count from model using LINQ

I have the following model:
public Class Category{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<SubCategory> SubCategories {get;set;}
}
public Class SubCategory{
public int Id {get;set;}
public string Name {get;set;}
public int CategoryId { get; set; }
public virtual Category Category{ get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
public class Ticket {
public Ticket();
public int Id { get; set; }
public virtual SubCategory SubCategory{ get; set; }
public int SubCategoryId{ get; set; }
}
I want to get data groupBy Category and get the count of tickets in each subcategory using this query:
Entities
.Include(h => h.SubCategories )
.ThenInclude(s => s.Tickets)
.GroupBy(s => s.Id)
.Select(t => new Cata {
Name = t.FirstOrDefault().Name,
Children = GetChildern(t.FirstOrDefault().SubCategories )
});
public List<SubCat> GetChildern(IEnumerable<SubCategories> subs)
{
var output = new List<SubCat>();
foreach (var sub in subs) {
var subcat = new SubCat();
subcat.Name = sub.Name;
if (sub.Tickets != null) {
subcat.Size = sub.Tickets.Count;
}
output.Add(subcat);
}
return output;
}
With the Query above the ticket is always zero for all, but tickets exists.
I don't see why you need to do a group by if you start your query in Categories
var result= Entities
.Include(h => h.TicketSubCategories)
.ThenInclude(s => s.Tickets)
.Select(t => new Cata {
Name = t.Name,
Children= t.TicketSubCategories
.Select(ts=>new SubCat{
Name=ts.Name,
Count=ts.Tickets.Count()})
};
Agreed with #Ivan about he commented above, here you don't need to use a custom method, using it you will force the projection of your query to be executed on the client side and not on the server (your DB)
So each Category has zero or more SubCategories, and each SubCategory has zero or more Tickets. Each Ticket belongs to exactly one SubCategory and each SubCategory belongs to exactly one Category
And you want a query, that results in groups of SubCategories that have the same Category. You want some (or all) properties of each SubCategory, but above all, you want the number of Tickets each SubCategory has.
All elements in every group of SubCategories belong to the same Category. You also want some (if not all) properties of this Category.
The solution is to group all SubCategories into groups of same Category (for efficiency use CategoryId). Then use a Select to get the properties you want.
var result = SubCategories
// group them into groups with same CategoryId
.GroupBy(subCategory => subCategory.CategoryId
// from every group take the properties you want:
.Select(group => new
{
// All SubCategories in one group belong to the same Category.
// For efficiency, take only the Category properties you plan to use,
CommonCategory = group.Key.Select(category => new
{
// take the category properties you want to use
}
// The group has a lot of SubCategories.
// For each subcategory select only the properties you want to use
SubCategories = group.Select(subCategory => new
{
// one of the properties you want is the number of Tickets of this SubCategory:
TicketCount = subCategory.Tickets.Count(),
// for efficiency: select only SubCategory properties you plan to use:
Property1 = subCategory.Property1,
Property2 = subCategory.Property2,
...
}),
});
So the result is a sequence of objects. Each object has two Properties:
SubCategories: a sequence of some properties of all SubCategories that belong to the same Category.
CommonCategory. Several properties of the Category that all SubCategories belong to.
The SubCategories is a sequence. Each element of the sequence is an object with several properties:
TicketCount: the number of tickets in the SubCategory
other properties: several other properties of the SubCategory
From this it is easy to construct the code to GetChildren

Loading Parent Child data using Entity Framework stored procedure

I have two stored procedures, one to load parent data and other to load child data. Is there anyway to do this in a better way? Please see code below.
public partial class Product
{
public long Id { get; set; }
public string ProductId { get; set; }
public string Name { get; set; }
public string ProductionNo { get; set; }
public string UniqueIdentificationNo { get; set; }
public virtual ICollection<Process> Processes { get; set; }
}
public List<GetProductsBetween_Result> GetProductsBetween(DateTime? startDate, DateTime? endDate)
{
var products = DbContext.GetProductsBetween(startDate, endDate).ToList();
foreach(var product in products)
{
product.Processes = DbContext.GetWaitingTime(product.Id).ToList();
}
return products;
}
This entire scenario can be rewritten using Linq(Language Intergrated Query).
You can apply eager loading to fetch the Processes of a product. This is similar
to sql joins which Linq provides, such a state that code is optimised and foreachs
and hits to the server can be reduced
var products = from u in dbContext.Product.Include(u => u.Processes)
select new ProductView
{
item1 = XXX,
item2 = yyy
};
var newdata = user.ToList().where("some lambda expressions")
with conditions to filter date
I had the same issue with a really complex search that had to return the full tree of an object.
What you need to do is:
Make a stored procedure that returns in only one call multiple result sets for the root object and for all its dependent objects:
i.e. (add your own conditions)
SELECT p.* FROM Products p WHERE p.Name LIKE '%test%';
SELECT DISTINCT r.* FROM Processes r INNER JOIN Products p ON p.id = r.idProduct WHERE p.Name LIKE '%test%';
After the return, re-attach the parent objects with its childs as described in this article:
https://blogs.infosupport.com/ado-net-entity-framework-advanced-scenarios-working-with-stored-procedures-that-return-multiple-resultsets/
You will need to iterate each Product from the first result set and attach its Processes.
This should be much faster since you only make one round-trip to the database and less queries.

EF eagerly load filtered child objects / filter only childs

my EF Poco classes structure as below and what I try to achieve is to get all CategoryProducts Including Products and ProductName but only ProductNames having languageid=1
I dont want to filter Root objects. I need not all productnames are loaded but only productname with languageid=1
I dont know how to achieve this. for example, I tried query below
var products = db.CategoryProduct.Include("Product.ProductName").
Where(p=>p.Product.ProductName.Any(a=>a.LanguageId==1)).ToList();
But this one filters all categoryProducts which have ProductName with languageid=1. this is not what I want because all products have names in 5 different languages. I just dont want to load eagerly for each product 5 times but only 1 time for languageid=1
public partial class CategoryProduct
{
[Key]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public virtual ICollection<ProductName> ProductName { get; set; }
}
public partial class ProductName
{
public int ProductId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
public virtual Product Product { get; set; }
}
I'm afraid that using eager loading you can't filter the related entities unless you project your query in an anonymous type or a DTO:
var products = db.CategoryProduct.Include(c=>c.Product.ProductName)
.Select(c=> new CategoryProductDTO()
{
//...
ProductNames= c.Product.ProductName.Where(a=>a.LanguageId==1)
})
.ToList();
If you don't want to project your query and you want to load specific related entities, then I suggest you to use Explicit Loading:
var catproduct = db.CategoryProduct.Include(c=>c.Product).FirstOrDefault();// This is just an example, select the category product that you need to load the related entities
context.Entry(catproduct.Product)
.Collection(b => b.ProductName)
.Query()
.Where(pn => pn.LanguageId==1)
.Load();
But IMHO the first variant is the way to go
this is not easily doable, but something like the following may do it:
from cp in db.CategoryProduct.Include(x => x.Product)
from pn in cp.Product.ProductName.Where(x => x.LanguageId == 1).DefaultIfEmpty()
select new {
Cat = cp,
Name1 = pn.Name
}
then you have you product in Cat.Product, and the name in Name1.
The basic idea is to set a LEFT JOIN on ProductName.

Categories

Resources