Linq Query For Self Referencing Object to Get Childless Elements - c#

I have a category class as shown.
/// <summary>
/// A category / matrix item
/// </summary>
public class Category : BaseItem
{
public Category()
{
}
[Key]
public int CategoryId { get; set; }
[MaxLength(256, ErrorMessage = "Text cannot be longer than 256 characters")]
[Required(ErrorMessage = "Item text is required")]
public string Text { get; set; }
public int? ParentCategoryId { get; set; }
public virtual Category Parent { get; set; }
}
I am attempting to write a function that allows me to get only the elements who have no children. I am struggling to wrap my head around the recursion logic. I am not opposed to using a loop and just building a list, however I am hoping to be able to do something like joining the data back to itself, DefaultIfEmpty()
then join again where joined record is null.
IE:
ID| Text | ParentId
1 | Parent | null
2 | Child | 1
3 | asdf | 2
4 | asdf | 1
I would only want to retrieve records 3 and 4 as they have no children.
I have referenced this post for the full tree functions, but need a bit more to get only the childless elements.

Thanks to the comments on this question, i was able to accomplish this using the query below.
public IEnumerable<Category> GetChildlessCategories()
{
return WowContext.Categories
.GroupJoin(
WowContext.Categories.Where(category => category.ParentCategoryId.HasValue),
(category) => category.CategoryId,
(child) => child.ParentCategoryId.Value,
(category, children) => new { Children = children, Category = category })
.Where(a => !a.Children.Any())
.Select(a => a.Category).ToList();
}

Related

Filtering parent collection by grandchildren properties in EF Core

Using EF core 5 and ASP.NET Core 3.1, I am trying to get a filtered collection based on a condition on its grandchildren collection.
I have the following entities:
public class Organisation
{
public int Id { get; set; }
public int? OrganisationId { get; set; }
public IEnumerable<Customer> Customers { get; set; }
}
public partial class Customer
{
[Key]
public uint Id { get; set; }
public int? EmployerId { get; set; }
public int? OrganisationId { get; set; }
public List<TimecardProperties> TimecardsProperties { get; set; }
}
public partial class TimecardProperties
{
[Key]
public int Id { get; set; }
public int? EmployerId { get; set; }
public int? Week { get; set; }
public short? Year { get; set; }
}
The goal is to get all Organisations that have at least one customer and the customer has at least 1 timecard property that is in week=34 and year=2021.
So far I have tried the following:
////necessary join to get Organisations for user id
IQueryable<Organisation> ouQuery = (from cou in _dbContext.Organisations
join uou in _dbContext.table2 on cou.OrganisationId equals uou.OrganisationId
where uou.UsersId == int.Parse(userId)
select cou)
.Where(cou => cou.Customers.Where(c => c.TimecardsProperties.Count > 0).Any())
.Include(cou => cou.Customers.Where(c => c.TimecardsProperties.Count > 0))
.ThenInclude(c => c.TimecardsProperties.Where(tc => tc.tWeek == 34 && tc.Year > 2020))
;
This returns a organisation list that each have a customers list but some customers have a count of timecards 0. I don't want to have organisation in the returned list that does not have at least one item in the timecards collection.
Also, it is too slow, and if I try to filter the produced list its even
slower (over 15 seconds)
I have also tried a raw sql query on the organisation db context but it is again very slow:
select distinct count(id) from organisation a where organisation_id in (
select organisation_id from customers where employer_id in (select distinct employer_id from timecards a
inner join timecard_components b on a.id=b.timecards_id
where week IN(
34) and year in (2021,2021) and invoice !=0 and type = 'time'
group by employer_id, week)
);
In general, I want to know the the total
count of the returned organisation collection for pagination (so I don't need to include all attributes of each entity)
as well as return only a part of the correct results, which satisfy the conditions,
an organisation list that has at least 1 timecards in
their customers by executing the query in the end like so:
ouQuery.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
I have also tried the EntityFramework.Plus and projection with no results.
How could I write this to achieve getting the total count of the organisation list and a part of these results (first 10) to display to the user?
Use navigation properties. This is the query you want:
var orgsQuery = dbContext.Organizations
.Where( o => o.Customers.Any( c =>
c.TimecardProperties.Any( tp =>
tp.Year = 2021
&& tp.Week = 34 ) ) );
Add includes and other predicates as needed

Joining 2 lists of objects based on object id

In my web application I am fetching 2 lists of objects from database.
First list of objects Employee
1
Name: Tom
Week1: 1
Week2: 3
Week3: 7
2
Name: Mike
Week1: 2
Week2: 1
Week3: 7
Second list of objects listOfId
1
id: 1
color: green
symbol: AT
2
id: 2
color: red
symbol: TB
3
id: 3
color: blue
symbol: TD
I would like to be able now to display it in a form of table, where for each of the weeks, I display this weeks color and symbol (each week matched on Employee.Week# = listOfId.id)
Something like this
Name | Week1 | Week1 Color | Week1 Symbol | Week2 | Week 2 Color etc...
Tom 1 green AT 3 blue
Mike 2 red TB 1 green
In total I will have constant 20 weeks for each employee.
I considered writing a SQL query which would take in week id, and return color and symbol. But for 50 people * 20 weeks... I would need to run this query 1000 times.
I am looking for better approach to solving this issue
my Models:
public class WeekViewModel
{
public int Id{ get; set; }
public string ShortNAme { get; set; }
public string Description { get; set; }
public int Color { get; set; }
}
}
public class EmployeeWeekViewModel
{
public string full_name { get; set; }
public string location { get; set; }
public int week1 { get; set; }
public int week2 { get; set; }
public int week3 { get; set; }
}
If I'm getting your correct, you have two lists coming from DB:
employeesList which represents the employees with their corresponding weeks ID
weeksList which represents the weeks for an employee.
Now, you want to join these two lists to display the information in a simple table format. I would do something like that:
public class EmployeeWeekViewModel{
public string EmployeeName{get;set;}
public WeekViewModel Week1 {get;set;}
public WeekViewModel Week2 {get;set;}
...
public WeekViewModel Week20 {get;set;}
}
public class WeekViewModel{
public int Id{get;set;}
public string Color{get;set;}
public string Symbol{get;set;}
}
employeesList.Select(t=> new EmployeeWeekViewModel(){
EmployeeName = t.Name,
Week1 = weeksList.FirstOrDefault(w => w.id == t.Week1).Select(w => new WeekViewModel(){
Id = w.id,
Color = w.color,
Symbol = w.symbol
}),
Week2 = weeksList.FirstOrDefault(w => w.id == t.Week2).Select(w => new WeekViewModel(){
Id = w.id,
Color = w.color,
Symbol = w.symbol
}),
...
});
In order to not make a request for each week, I would suggest send to you query a list of week Ids to get them once (checkout this example).

Fluent NHibernate Many-to-Many Mapping Self Referencing with associative table

Just for record : I use C#, Fluent NHibernate, and MySQL 5.
I used to use NHibernate for this project, but recently I decided to use Fluent NHibernate instead. And I got problem in mapping the many to many relationship that references to self with additional column.
I have table Item. Item can has many component which is also from table Item. These components can be used by other item as well.
For example, Item A needs two components, which are Item B and Item C. Item B is also used for Item D and Item E, and so on.
I used associative table and I need additional column for their relationship. Because of that, there will be 2 One - Many relations. This is my database structure.
Item
ID
Name
Prerequisite [ Associative table ]
ID
Item_ID [ FK_Item1 ] // item to be made.
Component_ID [ FK_Item2 ] // component for this item.
Weight // additional column.
This is my mapping for Prerequisite Table :
public PrerequisiteMap()
{
Id(x => x.ID).GeneratedBy.Native();
References(x => x.Item).Column("Item_ID");
References(x => x.Component).Column("Component_ID");
Map(x => x.Need);
}
This is my mapping for Item table :
public ItemMap()
{
Id(x => x.ID).GeneratedBy.Native();
HasMany(x => x.PrerequisitesParent).KeyColumn("Item_ID").Cascade.All();
HasMany(x => x.PrerequisitesComponent).KeyColumn("Component_ID").Cascade.All ;
Map(x => x.Name);
}
This is my prerequisite class :
public virtual UInt64 ID { get; set; }
// item.
public virtual UInt64 Item_ID { get; set; }
public virtual Item Item { get; set; }
// Component.
public virtual UInt64 Component_ID { get; set; }
public virtual Item Component { get; set; }
// prerequisite properties.
public virtual float Need { get; set; }
And this is my item class :
// Item properties.
public virtual UInt64 ID { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<Prerequisite> PrerequisitesComponent { get; set; }
public virtual IList<Prerequisite> PrerequisitesParent { get; set; }
The problem I have is, whenever I try to save/update Item with prerequisite, the Component_ID and Item_ID always have the same value. I create new Item X, with components Item Y and Item Z, in prerequisite table, I got these :
ID|Item_ID|Component_ID|Need
1 | X | X | 10
2 | X | X | 20
instead of, the expected result
1 | X | Y | 10
2 | X | Z | 20
This is my piece of code when saving :
using (var session = SessionFactoryProvider.OpenSession())
{
using (var trans = session.BeginTransaction())
{
var item = session.Get<Item>((UInt64)1); // Item to be updated.
var item2 = session.Get<Item>((UInt64)2); // Component 1
var item3 = session.Get<Item>((UInt64)3); // Component 2
item.PrerequisitesComponent.Add(new Prerequisite() { Item = item, Component = item2, Need = 100f}); // adding new prerequisite from component 1 (item2)
item.PrerequisitesComponent.Add(new Prerequisite() { Item = item, Component = item3, Need = 100f }); // adding new prerequisite from component 2 (item3)
session.SaveOrUpdate(item);
try
{
trans.Commit();
}
catch(GenericADOException ex)
{
MessageBox.Show(ex.InnerException.ToString());
}
}
}
What could be the problem here? Is it because I reference the same table? or maybe I mapped it wrongly?
I read other similar questions many to many from product to product and many to many self referencing but it seems like they didn't use additional column in their associative table?
Any help is very appreciated, Thank You.
Silly Me.. I used the wrong reference for item.
I changed item.PrerequisiteComponent to item.PrerequisiteParent and it all works well.

How to list Items by ItemOrder defined in a separate table? MVC

I am trying to order Items by Item Order defined by user entry on a different page.
I have a table of Items and a separate table Item Orders linked to an Item.
I also have a separate table for ItemCategories.
These three tables are all related appropriately.
Different surveys have a specific project than can have multiple items that need to be displayed in the user defined order.
Example:
Project 1 | Project 2
ItemOrder Item ItemCategory |ItemOrder Item ItemCategory
1 Item a CatA |8 Item a CatA
3 Item b CatB |7 Item b CatB
4 Item c CatC |5 Item c CatC
2 Item d CatD |4 Item d CatD
5 Item e CatE |6 Item e CatD
8 Item f CatF |1 Item f CatF
6 Item g CatG |3 Item g CatG
7 Item h CatH |2 Item h CatH
I am not sure how to display everything as a list of Item and Category by Project based on the ItemOrder.
Controller:
public ActionResult Survey(int SurveyId) {
var model = new SurveyViewModel(SurveyId);
return View(model);
}
Model:
public class SurveyViewModel {
public Survey Survey {get; set;}
public int SurveyId {get; set;}
public List<Project> Projects {get; set;}
public List<OrderedItem> OrderedItems {get; set;}
}
public class OrderedItem {
public int Id {get; set;}
public int Order {get; set;}
public string Category {get; set;}
public string ItemText {get; set;}
}
public SurveyViewModel(int SurveyId) {
using (var db = new SurveyContext()) {
Projects = db.Projects.Include("Items")
.Include("Items.Category")
.Where(s => s.SurveyId == SurveyId).ToList();
var Items = from s in Surveys
from i in s.Project.Items
from io in i.ItemOrders
where s.Id == 1
orderby io.ItemOrder
select new OrderedItem {
Id = i.Id,
Category = i.Category.Category,
Order = io.Order,
ItemText = i.Text.Text
}).Distinct();
//NOT SURE WHAT TO DO HERE TO POPULATE THE LIST TO PASS TO MY VIEW...
//THIS IS NULL...
OrderedItems = OrderedItems.ToList(); //?????
}
}
View:
#model xxxx.Models.SurveyViewModel
//Not sure what to do here either...
#foreach (var item in Model.Survey.Project.Items) {
#item.Text.Text //???
}
Please help, I think I am mostly there but I am having trouble. Any help is appreciated.
It is not a good idea to mix your data access code inside your viewmodels. Keep them lean and flat.
public class SurveyViewModel {
public Survey Survey {get; set;}
public int SurveyId {get; set;}
public List<Project> Projects {get; set;}
public List<OrderedItem> OrderedItems {get; set;}
}
public class OrderedItem {
public int Id {get; set;}
public int Order {get; set;}
public string Category {get; set;}
public string ItemText {get; set;}
}
And in your action method, load data to it
public ActionResult Survey(int SurveyId)
{
var vm = new SurveyViewModel();
using (var db = new SurveyContext())
{
vm.Projects = db.Projects.Include("Items")
.Include("Items.Category")
.Where(s => s.SurveyId == SurveyId).ToList();
vm.OrderedItems = from s in Surveys
from i in s.Project.Items
from io in i.ItemOrders
where s.Id == SurveyId
orderby io.ItemOrder
select new OrderedItem {
Id = i.Id,
Category = i.Category.Category,
Order = io.Order,
ItemText = i.Text.Text
}).Distinct().ToList();
}
return View(vm);
}
You may also move your db access code to another layer if you want to make your controllers lean.
Your razor view will be
#model SurveyViewModel
<h2>Ordered Items </h2>
#foreach(var p in Model.OrderedItems )
{
<p>#p.Category</p>
<p>#p.Order</p>
}
<h2>Projects</h2>
#foreach(var proj in Model.Projects)
{
<p>#proj.Name</p>
}

Convert nested relational model to a string

I have a data model in MVC5 entity framework in which a post has a category. This category can be nested such as.
Top Level: 0
-> Lower Level: 1
-> Lowest Level: 2
This is represented in my model as:
public class CategoryModel
{
public int Id { get; set; }
public CategoryModel ParentCategory { get; set; }
public string Title { get; set; }
}
Now when I display my post which has (from the above example) category "Lowest Level 2", I would like to display
"Top level: 0 > Lower Level: 1 > Lowest Level: 2"
somewhere on that page to inform the user where they are.
Problem is I dont have any idea of how to do this.
Propably really simple (as with all things in lambda) but I don't really know how and my googling skills are really off.
Edit as per comment question:
The post is defined as this:
public class PostModel
{
public int Id { get; set; }
public CategoryModel Category { get; set; } // a post can only have one category
public string Text { get; set; }
}
What I want to do is follow the CategoryModel relation, and then keep following the Categories ParentCategory untill it is null. This is always a 1 to 1 relation.
More Edit:
I was fairly simply able to do this with a TSQL-CTE expression but still no idea how to convert this to lambda.
SQL:
;WITH Hierarchy(Title, CatId, LEVEL, CategoryPath)
AS
(
Select c.Title, c.Id, 0, c.Title
FROM Categories c
WHERE c.[ParentCategory_Id] IS NULL
UNION ALL
SELECT c.Title, c.Id, H.LEVEL+1, H.CategoryPath+' > '+c.Title
FROM Categories c
INNER JOIN Hierarchy H ON H.CatId = c.[ParentCategory_Id]
)
SELECT SPACE(LEVEL*4) + H.Title, *
FROM Hierarchy H
ORDER BY H.CategoryPath
Result:
Assuming you have an instance of CategoryModel you could write a function that will build a string list with the chain of all titles:
private void FormatCategories(CategoryModel model, List<string> result)
{
result.Add(model.Title);
if (model.ParentCategory != null)
{
FormatCategories(model.ParentCategory, result);
}
}
and then:
CategoryModel model = ... // fetch your model from wherever you are fetching it
var result = new List<string>();
FormatCategories(model, result);
Now all that's left is to reverse the order of elements in the list and join them to retrieve the final result:
result.Reverse();
string formattedCategories = string.Join(" -> ", result);
// At this stage formattedCategories should contain the desired result

Categories