Grouping hierarchical data with LINQ - c#

I have a search view that needs to return a grouping of all employees based on their level in the organization hierarchy. The user needs to have the ability to group by any level in this hierarchy. So for example, I have drop down check lists that allow 1 or more options to be selected for Division, Department, Section, Group.
The way that I have the organization structure represented in my data model is like this:
public class OrganizationEntity : IEntity
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int OrganizationEntityTypeId { get; set; }
[DataMember]
[ForeignKey("OrganizationEntityTypeId")]
public virtual OrganizationEntityType OrganizationEntityType { get; set; }
[DataMember]
public virtual OrganizationEntity Parent { get; set; }
public virtual ICollection<OrganizationEntity> Children { get; set; }
}
The OrganizationEntityType tells me where in the organization hierarchy I am.
Employees are only linked to one organization entity so employee model looks like:
public class Employee : IEntity
{
[Key]
[DataMember]
public int Id { get; set; }
[Required]
[DataMember]
public string Name { get; set; }
[DataMember]
public int CityId { get; set; }
[DataMember]
[ForeignKey("CityId")]
public virtual City City { get; set; }
[DataMember]
public int OrganizationEntityId { get; set; }
[DataMember]
[ForeignKey("OrganizationEntityId")]
public virtual OrganizationEntity OrganizationEntity { get; set; }
}
Now I'm trying to figure out the best way to group by a level in the org hierarchy using LINQ. So say the user selects Division 1, Division 2, All Sections, Group 1 and has their grouping set to Divisions I would need to see the data looks something like this:
Divisions Employee Count
division1 25
division2 3
And if the same parameters where the same except the grouping was set to Section the data would look like:
Sections Employee Count
Section1 15
Section2 3
Section3 4
And so on for the other levels.
Here is a sample dataset:
Any advice would be very much appreciated.
Update
Added a filter to the employees query to only include those in the Org level being group by and all of those below it and now works great! Code looks like this:
var employees = context.Employees.Where(o => orgEntityIds.Contains(o.OrganizationEntityId));

LINQ can do this with the GroupBy method, but you'll have to be a bit creative with the way you generate the grouping key. Here's a rough idea:
Func<Employee, string> selector = (n => FindOrganizationName(n.OrganizationEntity, orgType));
Dictionary<string, int> results = employees.GroupBy(selector)
.ToDictionary(n => n.Key, n => n.Count());
where FindOrganizationName is a recursive function that crawls up the organization hierarchy until it finds the requested organization type, like this:
string FindOrganizationName(OrganizationEntity entity, int entityType)
{
if (entity == null)
{
return string.Empty;
}
else if (entity.OrganizationEntityTypeId == entityType)
{
return entity.Name;
}
else
{
return FindOrganizationName(entity.Parent, entityType);
}
}

Related

Use LINQ to flatten tree getting deepest value

If I have a hierarchical structure of departments and employees, like this:
public class TestEmployee
{
public Guid ID { get; set; }
public Guid TestDepartmemntID { get; set; }
public string name { get; set; }
public string email { get; set; }
}
public class TestDepartmemnt
{
public Guid ID { get; set; }
public Guid ParentTestDepartmentID { get; set; }
public string TestDepartmentName { get; set; }
public string? ManagerName { get; set; }
public string phoneNumber { get; set; }
}
Noting that ManagerName is nullable...
Is there a more elegant way to get employee's direct managername other than iterating up ancestors to find the closest department manager name?
If Widgets Inc has CEO Bob at the root, and Bob manages several departments, one of which is Logistics.
Logistics has several departments, one is Warehouse. Warehouse has a manager of Sharon.
The Warehouse has multiple departments, including shipping and receiving.
Rick works in shipping.
Rick's manager is Sharon.
I can easily determine which department Rick works in because I can get the TestDepartmentID.
However, when getting Rick out of the tree, do I have to get do something like:
Get Ricks TestDepartmentID's closest ancestors where ManagerName is not null or empty?
Is there a more elegant way to do this than a recursive loop?
Linq operates on IEnumerables, so the first step would be to create one. For example by using an iterator block. You can then use .First() or any other linq methods to return the first non-null manager, or any other query you want.
public Dictionary<Guid, TestDepartmemnt> departments = new Dictionary<Guid, TestDepartmemnt>();
public IEnumerable<TestDepartmemnt> GetParents(Guid departmentId)
{
while(departments.TryGetValue(departmentId, out var department))
{
yield return department;
departmentId = department.ParentTestDepartmentID;
}
}
This assumes you have the departments in a dictionary. This can be replaced with whatever method you use to link ids to departments.

How should I order nested entities when I load parent entity?

I have entities similar to this:
public class Search
{
public int ID { get; set; }
public bool Complete { get; set; }
public int Processed { get; set; }
public virtual ICollection<PostCode> PostCodes { get; set; }
}
public class PostCode
{
public int ID { get; set; }
public string Value { get; set; }
}
I want to make sure that the PostCode collection is always ordered by its ID field when I load a Search entity.
Is there a way to set an order for nested collection when I load it?
There is many thousand postcodes so it is expensive process to iterate over whole list. In the below code am I iterating over every postcode twice or is this the right way to do it?
Search s = db.Searches.Include("PostCodes").FirstOrDefault(x => x.Complete == false);
s.PostCodes = s.PostCodes.OrderBy(x => x.ID).ToList();
I haven't profiled it but it feels like it's taking longer now.

Designing tables to avoid circular reference

Working in one project (Catering theme ) when I was designing the database I didn't take care about some thing , and now Is very hard to avoid some kine of errors(Circular error).
Suppose I have following scenario :
I have Meal object that should be composed from a list of semi-finished products (we will call it Product ) and list of simple Resources.
One Product is composed from a list of Resoruces and list of products.
So in real example this will look like this:
Meal: Pizza that contains list of Resoruces(cheese,dough) and list of Products : in our case will be just :Sauce.
Sauce will be composed from List of Resources(salt,Some cheese ,tomato Sauce) and a List of Products (in our case will be just one "Chopped tomatoes with salt")
So now I have following classes:
public class Resource
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductToProduct
{
public int Id { get; set; }
public Product MainProduct { get; set; }
public Product Component { get; set; }
public double Quantity { get; set; }
}
public class ProductToResource
{
public int Id { get; set; }
public Product Product { get; set; }
public Resource Resource { get; set; }
public double Quantityt { get; set; }
}
public class Meal
{
public int Id { get; set; }
public string Name { get; set; }
public IList<MealToProduct> MealToProducts { get; set; }
public IList<MealToResource> MealToResources { get; set; }
}
public class MealToResource
{
public int Id { get; set; }
public Meal Meal { get; set; }
public Resource Resource { get; set; }
public double Quantity { get; set; }
}
public class MealToProduct
{
public Meal Meal { get; set; }
public Product Product { get; set; }
public double Quantity { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public IList<ProductToResource> ProdcutToResources { get; set; }
public IList<ProductToResource> ProductToProducts { get; set; }
}
My problem is in relation between Product to Product.
Suppose I will have Product1, Product2 , Product3 , Product4.
Product 1 will be composed from something and Prodcut2, Product4.
Product2 will be composed from something and Prodcut3.
Prodcut 3 will be composed from something and Prodcut4.
Prodcut 4 will be composed from something and Prodcut1 , in this case when I will try to calcualte Cost for Product1 , or Product 4 I will get an Circular error.
So my problem is in ProductToProduct table.
My question is how I must to design tables to avoid this kind of errors .
I AM VERY SORRY FOR MY EXPLICATION BUT IT IS VERY HARD TO EXPLAIN THIS PROBLEM.
PLEASE ASK ME IF SOMETHING IS UNCLEAR.
THANKS FOR YOUR ATTENTION.
Note:This is not so important for this case but I am working in ASP.Net mvc , orm is Fluent Nhibernate.
Here's an example of a function you could use to detect whether a parent-child relationship exists. I have assumed that the product relationships are described in a table called ProductLink, which has two foreign keys to Product: ParentProductId and ChildProductId.
This function uses a recursive query to determine the complete list of products which are children of the product denoted by the argument #ParentProductId, then does a simple test to see whether #ChildProductId appears in that list.
create function dbo.ProductRelationshipExists
(
#ParentProductId int,
#ChildProductId int
)
returns bit
as
begin
declare #ChildExists bit = 0;
with ProductChildCTE as
(
-- Base case: Get the parent's direct children.
select ChildProductId from ProductLink where ParentProductId = #ParentProductId
-- Recursive case: Get the children's children.
union all
select
ProductLink.ChildProductId
from
ProductChildCTE
inner join ProductLink on ProductChildCTE.ChildProductId = ProductLink.ParentProductId
)
select #ChildExists = 1 from ProductChildCTE where ChildProductId = #ChildProductId;
return #ChildExists;
end
When someone tries to insert a record into ProductLink, you could use a test like this to determine whether the proposed parent and child are already in the table as child and parent, respectively, and disallow the insertion if so.
This was just a quick write-up to illustrate one possible approach; I should mention that I don't know how well the performance of this thing will scale as the table gets larger. Hopefully it will suffice for your case. If not, let me know how to improve it.

Entity Framework one-to-many relation

I have two simple classes:
class Student
{
public int StudentId { get; set; }
public int IndeksNo { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public virtual Group Group { get; set; }
}
And
class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public virtual List <Student > Students { get; set; }
}
Database is created correctly (using Code First). I have added couple items to both tables, but all the time Students list and Group property in Student are null. I have no idea why. I have searched solution for about an hour and i came up with something like this:
modelBuilder.Entity<Student>()
.HasRequired(st => st.Group)
.WithMany(gr => gr.Students)
.WillCascadeOnDelete(false);
But it doesn't help. I have no idea what may went wrong or why Group.Students and Student.Group are always null. List of groups and list of students are selected from db successfully - i mean all params except those connections.
In order to use the Lazy Loading feature of EntityFramework. Your navigation property must be virtual. In your Student class in the the case. The problem is with your Group class. The navigational property is a virtual List<Student> it must be a virtual ICollection<Student>
You can simply change your Group to
class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public virtual ICollection<Student > Students { get; set; }
}

Retrieve a list of items where an item exists in one of the items lists

Please excuse the slightly confusing title. I have a model (Project) which contains a list of items (Users).
I would like to retrieve all of the projects, where the current user is a member of the user list for that project.
I've tried:
List<Project> _MemberProjects =
_Db.Projects.Where(p =>
p.Users.Contains(_User)
).ToList();
This results in the following error:
Unable to create a constant value of type 'Nimble.Models.UserAccount'. Only primitive types or enumeration types are supported in this context.
User Model:
public class UserAccount
{
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public ICollection<Project> Projects{....}
}
Project Model
public class Project
{
public int ProjectID { get; set; }
public DateTime CreatedDate { get; set; }
public string ProjectName { get; set; }
public string Owner { get; set; }
public ICollection<UserAccount> Users{...}
public ICollection<ProjectGroup> Groups{...}
}
Haven't tried this, but it might work:
List<Project> _MemberProjects =
_Db.Projects.Where(p =>
p.Users.Any(u => u.UserID == _User.UserID )
).ToList();
The problem is that you are mixing together Linq (the WHERE clause) and a non-Linq Collection operation (Contains). Try using pure Linq. #JamesBond's answer might work.
Are you querying a database? Then a JOIN might be another solution, but the exact syntax depends on how you are storing the relationship between the two tables.

Categories