Entityframework select all childs related data when call parent - c#

I have categories table which contains :
public partial class C_Categories
{
public int CatId { get; set; }
public Nullable<int> ParentId { get; set; }
public string ImageUrl { get; set; }
public virtual ICollection<C_Node> C_Node { get; set; }
}
And i have node table which contains :
public partial class C_Node
{
public int NodeId{ get; set; }
public Nullable<int> CatId { get; set; }
public System.DateTime PostDate { get; set; }
public virtual C_Categories C_Categories { get; set; }
}
And my controller :
public ActionResult Index(int? catId)
{
IQueryable<C_Node> moduleItems = db.C_Node;
if (catId != null)
{
//here i want to check if category is parent , get all node related to his child categories
moduleItems = moduleItems.Where(x => x.CatId == catId);
}
return View(moduleItems.ToList());
}
At my controller i want to check if category is parent , get all node table related to his child categories,
I tried to use any , but it failed .
to explain my question : i have category : electronics and electronics have childs computers, mobiles . i have products on node table under computers and mobiles , if catId is electronics i want all products under its childs computers, mobiles

You first need to find all the categories under the parent; if there are only 2 levels this is simple:
...
if (catId != null)
{
// Find the child categories for which this is the parent
var childCatIds = db.C_Categories
.Where(cat => cat.ParentId == catId)
.Select(cat => cat.CatId)
.ToList();
if (childCatIds.Count == 0)
// Not a parent category: Just find the items for the category as before
moduleItems = moduleItems.Where(x => x.CatId == catId);
else
// Parent category: Find the items for the child categories
moduleItems = moduleItems.Where(x => childCatIds.Contains(x.CatId));
}
If there are more than 2 levels, you will need to find the child ids recursively.
private List<int> GetChildCatIds(List<int> parentCatIds)
{
var childCatIds = db.C_Categories
.Where(cat => cat.ParentId.HasValue && parentCatIds.Contains(cat.ParentId.Value))
.Select(cat => cat.CatId)
.ToList();
if (childCatIds.Count == 0)
// Reached the end of the tree: no more children
return parentCatIds;
else
// Recursive call to find the next child level:
return GetChildCatIds(childCatIds);
}
...
if (catId != null)
{
var childCatIds = GetChildCatIds(new List<int>{catId.Value});
moduleItems = moduleItems.Where(x => childCatIds.Contains(x.CatId));
}

How about this:
moduleItems = dbcontext.C_Nodes.Where(n => n.CatId == catId);

Related

Building a tree in C# and fill it with recursion

I have to build a tree, similar as filesystem structure of files and folders in the hard drive.
Instead of files and folders I have folders, and equipments.
I have created this class
public class TreeModel
{
public int Id { get; set; }
public string Name { get; set; }
public int? IdParent { get; set; }
public int? Level { get; set; }
public bool IsEquipment { get; set; }
public List<TreeModel> Children { get; set; }
}
I have a list of N root which can have childrens of TreeModel elements.
I have tryed this recursive function which makes the program crash
private TreeModel GetChild(TreeModel tree, ref List<ShipSparePartsCategory> folders, List<ShipSparePartsCategory> equipments)
{
if (tree == null)
{
return tree;
}
var combined = folders.Concat(equipments);
var nodes = combined
.Where(x => x.IdParent == tree.Id && x.IsActive == true)
.Select(y => new TreeModel()
{
Id = y.Id,
IdParent = y.IdParent,
Name = y.Name,
Level = y.Level,
IsEquipment = equipments.Any(z => z.Id == y.Id) ? true : false,
Children = equipments.Any(z => z.Id == y.Id) ? new List<TreeModel>() : new List<TreeModel>(),
})
.ToList();
tree.Children.AddRange(nodes);
return GetChild(tree, ref folders, equipments);
}
I have no idea how to fix it and call it in the correct way.

LINQ query to find related data [duplicate]

This question already has answers here:
Projecting self referencing multi level Entities In Entity Framework 6
(2 answers)
Closed 5 years ago.
I got some help with my recursive product category tree view here on Stack Overflow before, and this is working:
Entity model:
public class ProductCategory
{
public int Id { get; set; }
public int SortOrder { get; set; }
public string Title { get; set; }
[ForeignKey(nameof(ParentCategory))]
public int? ParentId { get; set; }
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } //nav. prop to children
public List<ProductInCategory> ProductInCategory { get; set; }
}
Controller:
var categories = _context.ProductCategories.Include(e => e.Children).ToList();
var topLevelCategories = categories.Where(e => e.ParentId == null);
return View(topLevelCategories);
View:
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title
<ul>
#Html.Partial("_CategoryRecursive.cshtml", item.Children)
</ul>
</li>
}
}
But when I tried to translate this setup to my viewmodel (and adding a property for counting products in each category, as well as a list of products without any category connection) ...:
Viewmodel:
public class ViewModelProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public string ProductCountInfo
{
get
{
return Products != null && Products.Any() ? Products.Count().ToString() : "0";
}
}
public ViewModelProductCategory ParentCategory { get; set; } // Nav.prop. to parent
public IEnumerable<ViewModelProductCategory> Children { get; set; } // Nav.prop. to children
public List<ViewModelProduct> Products { get; set; } // Products in this category
public List<ViewModelProduct> OrphanProducts { get; set; } // Products with no reference in ProductInCategory
}
Controller:
var VMCategories = _context.ProductCategories
.Include(e => e.Children)
.OrderBy(s => s.SortOrder)
.Where(r => r.ParentId == null) // only need root level categories in the View
.Select(v => new ViewModelProductCategory
{
Id = v.Id,
ParentId = v.ParentId,
Title = v.Title,
SortOrder = v.SortOrder,
// get products without a category:
OrphanProducts = v.ProductInCategory
.Where(o => !_context.ProductsInCategories.Any(pc => o.Id == pc.ProductId))
.Select(orph => new ViewModelProduct
{
Id = orph.Product.Id,
Title = orph.Product.Title,
Price = orph.Product.Price,
Info = orph.Product.Info,
SortOrder = orph.SortOrder
})
.OrderBy(s => s.SortOrder)
.ToList()
})
.ToList();
return View(VMCategories);
View:
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title (#item.ProductCountInfo)
<ul>
#Html.Partial("_CategoryRecursive.cshtml", item.Children)
</ul>
</li>
}
}
... it won't work any more. The view does render, but it is just showing the root categories. It seems that my modified query won't get any of the children categories. When I inspect the query result, the Children property is null.
EDIT
I'm going with #Rainman's solution, and have changed my query .Select to include Children = v.Children,, and changing my viewmodel navigational properties thusly:
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } //nav. prop to children
I have also created the new viewmodel CategoryRecursiveModel and changed my view to this:
#model IEnumerable<MyStore.Models.ViewModels.ViewModelProductCategory>
<ul>
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title (#item.ProductCountInfo)
<ul>
#Html.Partial("_CategoryRecursive.cshtml", new CategoryRecursiveModel
{
Children = item.Children.ToList();
})
</ul>
</li>
}
}
</ul>
Now I'm faced with InvalidOperationException, as the view is expecting an IEnumerable of ViewModelProductCategory, but receives CategoryRecursiveModel.
Because you are not selecting the Children for second query;
var VMCategories = _context.ProductCategories
.Include(e => e.Children)
.OrderBy(s => s.SortOrder)
.Where(r => r.ParentId == null) // only need root level categories in the View
.Select(v => new ViewModelProductCategory
{
Id = v.Id,
Children = v.Children, // Select it
ParentId = v.ParentId,
Title = v.Title,
SortOrder = v.SortOrder,
// get products without a category:
OrphanProducts = v.ProductInCategory
.Where(o => !_context.ProductsInCategories.Any(pc => o.Id == pc.ProductId))
.Select(orph => new ViewModelProduct
{
Id = orph.Product.Id,
Title = orph.Product.Title,
Price = orph.Product.Price,
Info = orph.Product.Info,
SortOrder = orph.SortOrder
})
.OrderBy(s => s.SortOrder)
.ToList()
})
.ToList();
public ProductCategory ParentCategory { get; set; } //nav.prop to parent
public ICollection<ProductCategory> Children { get; set; } //nav. prop to children
Also, navigation properties exist only for EF entities not ViewModelProductCategory model class or other classes.
EDIT
Create a model class for _CategoryRecursive view;
public class CategoryRecursiveModel
{
public List<ProductCategory> Children { get; set; }
}
And the change the main view;
#if (Model != null)
{
foreach (var item in Model)
{
<li>
#item.Title (#item.ProductCountInfo)
<ul>
#Html.Partial("_CategoryRecursive.cshtml", new CategoryRecursiveModel
{
Children = item.Children.ToList();
})
</ul>
</li>
}
}

How to do a grouped distinct in linq

I have something working in plain simpel loops but I want to know how to do it with LINQ, if possible.
I don't even know how to describe what i want to do. But stack overflow wants me to do so in words instead of with an example, so this is me tricking it...
The classes (stripped down)
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public List<CategoryGroup> CategoryGroups { get; set; }
}
public class CategoryGroup
{
public int CategoryGroupId { get; set; }
public string Name { get; set; }
public List<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
}
What I want to do
From
Product 1
CategoryGroup 1
Category 11
Category 12
Category 13
CategoryGroup 2
Category 21
Category 22
Product 2
Category Group 1
Category 11
Category 14
Category Group 2
Category 21
Category Group 3
Category 31
To
Category Group 1
Category 11
Category 12
Category 13
Category 14
Category Group 2
Category 21
Category 22
Category Group 3
Category 31
The working code
var categoryGroups = new List<CategoryGroup>();
foreach (var product in products)
{
foreach (var categoryGroup in product.CategoryGroups)
{
var group = categoryGroups.SingleOrDefault(cg => cg.CategoryGroupId == categoryGroup.CategoryGroupId);
if (group == null)
{
group = new CategoryGroup {
Categories = new List<Category>(),
CategoryGroupId = categoryGroup.CategoryGroupId,
Name = categoryGroup.Name
};
categoryGroups.Add(group);
}
foreach (var category in categoryGroup.Categories)
{
if (group.Categories.Any(c => c.CategoryId == category.CategoryId))
{
continue;
}
group.Categories.Add(category);
}
}
}
You can do many subselections through SelectMany:
var result = products
.SelectMany(x => x.CategoryGroups)
.GroupBy(x => x.CategoryGroupId)
.Select(x => new CategoryGroup
{
Categories = x.SelectMany(y => y.Categories)
.Distinct(y => y.CategoryId)
.OrderBy(y => y.CategoryId)
.ToList(),
CategoryGroupId = x.Key,
Name = x.Values.First().Name
})
.OrderBy(x => x.CategoryGroupId)
.ToList();
Here is the query to get all the different category groups
var categoryGroups = products.SelectMany(g => g.CategoryGroups)
.GroupBy(x => x.CategoryGroupId)
.Select(y => new { CategoryGroupId = y.Key,
Categories = y.SelectMany(category => category.Categories).Distinct().ToList() });
To eliminate duplicate categories you will have to modify your Category object to implement the IEquatable interface as follows
public class Category : IEquatable<Category>
{
public int CategoryId { get; set; }
public string Name { get; set; }
public bool Equals(Category other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return CategoryId.Equals(other.CategoryId) && Name.Equals(other.Name);
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
int hashProductName = Name == null ? 0 : Name.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = CategoryId.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
}
Implementing the interface allows the Distinct() call in LINQ to compare the list of objects and eliminate duplicates.
Hope that helps.

Convert list of items to list of tree nodes that have children

I have the following class
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Text { get; set; }
}
And the following class for tree view
public class TreeViewModel
{
public TreeViewModel()
{
this.Children = new List<TreeViewModel>();
}
public int Id { get; set; }
public int NodeId { get; set; }
public string Text { get; set; }
public bool Expanded { get; set; }
public bool Checked { get; set; }
public bool HasChildren
{
get { return Children.Any(); }
}
public IList<TreeViewModel> Children { get; private set; }
}
I will receive list of items and I would to convert it to tree.
the item that not have parent id it will the main node.
example: if i have the following items
item[0] = Id:0 Text:User ParentId:3
item[1] = Id:1 Text:Role ParentId:3
item[2] = Id:2 Text:SubUser ParentId:0
item[3] = Id:3 Text:Admin ParentId:null
item[4] = Id:4 Text:SuperAdmin ParentId:null
item[5] = Id:5 Text:Doha ParentId:4
the following item it will list of tree
I tried to make recursive function to do that , but i have no result
You don't need a recursive function to do this:
var models = items.Select(i => new TreeViewModel
{
Id = i.Id,
...
}).ToList();
foreach (var model in models){
model.Children.AddRange(models.Where(m => m.ParentId == model.Id));
}
If you then want to get the roots of your tree, you can use:
var roots = models.Where(m => !m.ParentId.HasValue);
Here is a fast O(N) time complexity method of doing that:
List<Item> list = ...;
// Pre create all nodes and build map by Id for fast lookup
var nodeById = list
.Select(item => new TreeViewModel { Id = item.Id, Text = item.Text })
.ToDictionary(item => item.Id);
// Build hierarchy
var tree = new List<TreeViewModel>();
foreach (var item in list)
{
var nodeList = item.ParentId == null ? tree, nodeById[item.ParentId.Value].Children;
nodeList.Add(nodeById[item.Id]);
}

Issue Related to SelectMany function in LINQ

I have two tables in Database:
PostCalculationLine
PostCaluclationLineProduct
PostCalculationLineProduct(table2) contains Foriegn key of PostCalucationLineId(table1)
In C# code I have two different Models for these two tables as follows:
public class PostCalculationLine : BaseModel
{
public long Id{ get; set; }
public string Position { get; set; }
public virtual Order Order { get; set; }
public virtual Task Task { get; set; }
//some other properties go here
public virtual IList<PostCalculationLineProduct> PostCalculationLineProducts { get; set; }
}
and
public class PostCalculationLineProduct : BaseModel
{
public long Id {get;set;}
public string Description { get; set; }
//some other properties go here
}
Now in Entityframework code, I fetch data from PostCalculationLineProduct as follows:
PostCalculationLineRepository pclr = new PostCalculationLineRepository();
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts)
.Where(c => c.Product.ProductType.Id == 1 && c.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.Id,
Date = c.From,
EmployeeName = c.Employee != null ?c.Employee.Name:string.Empty,
Description= c.Description,
ProductName = c.Product != null?c.Product.Name :string.Empty,
From = c.From,
To = c.Till,
Quantity = c.Amount,
LinkedTo = "OrderName",
Customer ="Customer"
PostCalculationLineId = ____________
})
.ToDataSourceResult(request);
In the above query I want to get PostCalculationLineId(from Table1) marked with underLine. How can I achieve this?
Thanks
You can use this overload of SelectMany to achieve this:-
DataSourceResult dsrResult = pclr.Get()
.SelectMany(p => p.PostCalculationLineProducts,
(PostCalculationLineProductObj,PostCalculationLineObj) =>
new { PostCalculationLineProductObj,PostCalculationLineObj })
.Where(c => c.PostCalculationLineProductObj.Product.ProductType.Id == 1
&& c.PostCalculationLineProductObj.DeletedOn == null)
.Select(c => new HourGridViewModel()
{
Id = c.PostCalculationLineProductObj.Id,
Date = c.PostCalculationLineProductObj.From,
//Other Columns here
PostCalculationLineId = c.PostCalculationLineObj.Id
};
This will flatten the PostCalculationLineProducts list and returns the flattened list combined with each PostCalculationLine element.

Categories

Resources