Order GroupBy Asc based on Two more Properties using LINQ C# - c#

My GroupBy is performing well. I'm getting the Output
I need to Sort the Group Names
The Brown Color Block, represents the Group.
The Red Color Box within the Brown Color Block, represents the Manager
Peter Block (Brown Box) Should Come first
Raj Block (Brown Box) Should Come Second
Sunny Block (Brown Box) Should Come Third
Each Block Should Group By Boss(Manager) and Assistant (Boss don't have the
SID). After GroupBy the Name should be in Sorted Order, within the Group
the Assistant Names are also in the Sorted Order.
The Model Classes:
public class Person
{
public int ID { get; set; }
public int SID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public string Role { get; set; }
}
public class Boss
{
public int ID { get; set; }
public int SID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public string Role { get; set; }
public List<Person> Employees { get; set; }
}
The Main Functionality Source Code:
void Main()
{
List<Boss> BossList = new List<Boss>()
{
new Boss()
{
ID = 101,
Name = "Sunny",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 101, SID = 102, Name = "Peter", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 101, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
}
},
new Boss()
{
ID = 104,
Name = "Raj",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 104, SID = 105, Name = "Kaliya", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 104, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
},
},
new Boss()
{
ID = 102,
Name = "Peter",
Department = "Development",
Gender = "Male",
Role = "Manager",
Employees = new List<Person>()
{
new Person() {ID = 102, SID = 105, Name = "Kaliya", Department = "Development", Gender = "Male", Role = "Assistant"},
new Person() {ID = 102, SID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female", Role = "Assistant"},
}
}
};
List<Person> EmpList = BossList.SelectMany(i =>
new[] {
new Person()
{
ID = i.ID,
SID = i.SID,
Name = i.Name,
Gender = i.Gender,
Department = i.Department,
Role = i.Role
}
}.Concat(i.Employees)
).ToList().GroupBy(s => s.ID).SelectMany(h => h.GroupBy(g => g.SID).SelectMany(u => u.OrderBy(k=> k.Name))).ToList();
}

You can do by adding the ThenBy extension method after the Order by to apply the secondary sort. In fact, the ThenBy can be called multiple times for sorting on multiple property. I have modified the last line of your code to show how you can achieve this.
).ToList().GroupBy(s => s.ID).SelectMany(h => h.GroupBy(g => g.SID).SelectMany(u => u.OrderBy(k=> k.Name).ThenBy(l => l.<<secondproperty>>))).ToList();

The datastructure already establishes the groups. There is no need to re-group.
List<Person> result = (
from boss in BossList
order by boss.Name
let orderedEmployees = boss.Employees.OrderBy(emp => emp.Name)
let bossPerson = new Person(boss)
let people = new List<Person>() { bossPerson }.Concat(orderedEmployees)
from person in people
select person).ToList();
If you prefer the lambda syntax:
List<Person> result = BossList
.OrderBy(boss => boss.Name)
.SelectMany(boss => {
IEnumerable<Person> orderedEmployees = boss.Employees.OrderBy(emp => emp.Name);
Person bossPerson = new Person(boss);
return new List<Person>() { bossPerson }.Concat(orderedEmployees);
})
.ToList();

Related

How to seed a table with data from another table

I made a simple website in ASP.NET Core with Entity Framework where you can manage employees. You have and Employee controller, those Employees belong to a specific department, and there are groups who are assigned to tasks and every kind of employee can be on it from every department.
Here is my seeding:
modelBuilder.Entity<Employee>()
.HasData(
new Employee() { Id = -99, Name = "Mary", Email = "mary#gmail.com", DepartmentId = -99, GroupId = -1 },
new Employee() { Id = -98, Name = "Stan", Email = "stan#gmail.com", DepartmentId = -99, GroupId = -1 },
new Employee() { Id = -97, Name = "Mike", Email = "mike#gmail.com", DepartmentId = -99, GroupId = -1 });
modelBuilder.Entity<Department>()
.HasData(
new Department() { DepartmentId = -99, Field = "IT", Name = "Programming Department" },
new Department() { DepartmentId = -98, Field = "HR", Name = "Human Resorcues" },
new Department() { DepartmentId = -97, Field = "AD", Name = "Advertisement Department" });
modelBuilder.Entity<Group.Group>()
.HasData(
new Group.Group() { GroupId = -1, Name = "Cleaner", Task = "Clean the building"}
);
The thing is, I want that 1 group to have Employees in it, but I don't know how to seed those employees which are added before with Id -99,-98 and -97.
EDIT: Here I tried to make a list of my Employees and add them to the Employees field in Group:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var emp1 = new Employee() { Id = -99, Name = "Mary", Email = "mary#gmail.com", DepartmentId = -99, GroupId = -1 };
var emp2 = new Employee() { Id = -98, Name = "Stan", Email = "stan#gmail.com", DepartmentId = -99, GroupId = -1 };
var emp3 = new Employee() { Id = -97, Name = "Mike", Email = "mike#gmail.com", DepartmentId = -99, GroupId = -1 };
List<Employee> empList = new List<Employee>() { emp1, emp2, emp3 };
modelBuilder.Entity<Employee>()
.HasData(empList);
modelBuilder.Entity<Department>()
.HasData(
new Department() { DepartmentId = -99, Field = "IT", Name = "Programming Department" },
new Department() { DepartmentId = -98, Field = "HR", Name = "Human Resorcues" },
new Department() { DepartmentId = -97, Field = "AD", Name = "Advertisement Department" });
modelBuilder.Entity<Group.Group>()
.HasData(
new Group.Group() { GroupId = -1, Name = "Cleaner", Task = "Clean the building", Employees = empList }
);
}
But I get this error:
The seed entity for entity type 'Group' cannot be added because it has the navigation 'Employees' set. To seed relationships, you need to add the related entity seed to 'Employee' and specify the foreign key values {'GroupId'}. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the involved property values.
EDIT: Here is the seeding method:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasData(
new Employee() { Id = -99, Name = "Mary", Email = "mary#gmail.com", DepartmentId = -99, GroupId = -1 },
new Employee() { Id = -98, Name = "Stan", Email = "stan#gmail.com", DepartmentId = -99, GroupId = -1 },
new Employee() { Id = -97, Name = "Mike", Email = "mike#gmail.com", DepartmentId = -99, GroupId = -1 });
modelBuilder.Entity<Department>()
.HasData(
new Department() { DepartmentId = -99, Field = "IT", Name = "Programming Department" },
new Department() { DepartmentId = -98, Field = "HR", Name = "Human Resorcues" },
new Department() { DepartmentId = -97, Field = "AD", Name = "Advertisement Department" });
modelBuilder.Entity<Department>()
.HasMany(a => a.Employees);
modelBuilder.Entity<Group>()
.HasData(
new Group() { GroupId = -1, Name = "Cleaner", TodoId = -99 }
);
modelBuilder.Entity<Group>()
.HasMany(a => a.Employees);
modelBuilder.Entity<Todo>()
.HasData(
new Todo() { ProblemId = -99, Title = "Spilled drink in basement", Descrpition = "Someone spilled drink all over the place in the basement", IsDone = false, GroupId = -1 });
modelBuilder.Entity<Todo>()
.HasOne(a => a.Group);
}
Here is my Group.cs :
public class Group
{
[Key]
public int GroupId { get; set; }
[Required]
public string Name { get; set; }
public int TodoId { get; set; }
public Todo problem;
public List<Employee> Employees { get; set; } = new List<Employee>();
}
and my Employee.cs:
public class Employee
{
public int Id { get; set; }
[Required]
[MaxLength(50, ErrorMessage = "Name cannot exceed 50 characters")]
public string Name { get; set; }
[Required]
[RegularExpression(#"^[a-zA-Z0-9_0+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", ErrorMessage = "Invalid Email Format")]
[Display(Name = "Office Email")]
public string Email { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
The way I found out that nothing is in the Employees list is I logged out context.Employees.Count in the details function in the controller itself and it said 0

LINQ group list and combine collections

Given these classes:
public class Employee
{
public int EmployeeId { get; set; }
public int GroupId { get; set; }
public List<Plans> Plans { get; set; }
}
public class Plan
{
public int PlanYearId { get; set; }
public string Name { get; set; }
}
And given a setup like so:
var employees = new List<Employee> {
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
}}};
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 3"
}}}};
How can I use LINQ to group these employees by both EmployeeId and GroupId and then combine the two List<Plan> properties so that i would end up with something like this:
var employee = new Employee
{
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 3"
}
}
}
Just use combination of GroupBy and SelectMany:
var result = employees
.GroupBy(e => new { e.EmployeeId, e.GroupId })
.Select(g => new Employee
{
EmployeeId = g.Key.EmployeeId,
GroupId = g.Key.GroupId,
Plans = g.SelectMany(e => e.Plans).ToList()
}).ToList();

How to Use Effeciently Where Clause or Select in LINQ Parallel in Large Dataset

I'm having approx 250,000 records as marked as Boss, each Boss has 2 to 10 Staff. Daily I need to get the details of the Staff. Approx there are 1,000,000 staff. I'm using Linq to get the Unique list of Staff who are worked in daily basis. Consider the following C# LINQ and Models
void Main()
{
List<Boss> BossList = new List<Boss>()
{
new Boss()
{
EmpID = 101,
Name = "Harry",
Department = "Development",
Gender = "Male",
Employees = new List<Person>()
{
new Person() {EmpID = 102, Name = "Peter", Department = "Development",Gender = "Male"},
new Person() {EmpID = 103, Name = "Emma Watson", Department = "Development",Gender = "Female"},
}
},
new Boss()
{
EmpID = 104,
Name = "Raj",
Department = "Development",
Gender = "Male",
Employees = new List<Person>()
{
new Person() {EmpID = 105, Name = "Kaliya", Department = "Development",Gender = "Male"},
new Person() {EmpID = 103, Name = "Emma Watson", Department = "Development",Gender = "Female"},
}
},
..... ~ 250,000 Records ......
};
List<Person> staffList = BossList
.SelectMany(x =>
new[] { new Person { Name = x.Name, Department = x.Department, Gender = x.Gender, EmpID = x.EmpID } }
.Concat(x.Employees))
.GroupBy(x => x.EmpID) //Group by employee ID
.Select(g => g.First()) //And select a single instance for each unique employee
.ToList();
}
public class Person
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
}
public class Boss
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public List<Person> Employees { get; set; }
}
In the above LINQ I'm getting the List of Distinct Employees or Staff, the list contains more than 1,000,000 records. From the Obtained List I need to search "Raj"
staffList.Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant()));
For this operation, it took more than 3 to 5 minutes to get the result.
How could I make it more efficient. Kindly assist me...
If you change Boss to inherit from Person ( public class Boss : Person ) not only do you not need to duplicate your properties in Person and Boss, you don't have to create all new Person instances for each Boss, because a Boss is already a Person:
IEnumerable<Person> staff = BossList
.Concat(BossList
.SelectMany(x => x.Employees)
)
.DistinctBy(p => p.EmpId)
.ToList()
Where DistinctByis defined as
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
Also, in your comparison, you're converting every Name to lowercase and doing the comparison - that's a lot of string creation that you don't need. Instead, try something like
staffList.Where(m => m.Name.Equals("Raj", StringComparison.InvariantCultureIgnoreCase));
Also, be aware that your use of Contains would also match names like Rajamussenand mirajii - possibly not what you were expecting.
Would it work for you to change staffList to a Dictionary? A better search algorithm as those from Dictionary and SortedList would get you the most improvement.
I've tested the code below and it runs in just a few seconds.
private static void Main()
{
List<Boss> BossList = new List<Boss>();
var b1 = new Boss()
{
EmpID = 101,
Name = "Harry",
Department = "Development",
Gender = "Male",
Employees = new List<Person>()
{
new Person() {EmpID = 102, Name = "Peter", Department = "Development", Gender = "Male"},
new Person() {EmpID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female"},
}
};
var b2 = new Boss()
{
EmpID = 104,
Name = "Raj",
Department = "Development",
Gender = "Male",
Employees = new List<Person>()
{
new Person() {EmpID = 105, Name = "Kaliya", Department = "Development", Gender = "Male"},
new Person() {EmpID = 103, Name = "Emma Watson", Department = "Development", Gender = "Female"},
}
};
Random r = new Random();
var genders = new [] {"Male", "Female"};
for (int i = 0; i < 1500000; i++)
{
b1.Employees.Add(new Person { Name = "Name" + i, Department = "Department" + i, Gender = genders[r.Next(0, 1)], EmpID = 200 + i });
b2.Employees.Add(new Person { Name = "Nam" + i, Department = "Department" + i, Gender = genders[r.Next(0, 1)], EmpID = 1000201 + i });
}
BossList.Add(b1);
BossList.Add(b2);
Stopwatch sw = new Stopwatch();
sw.Start();
var emps = BossList
.SelectMany(x =>
new[] {new Person {Name = x.Name, Department = x.Department, Gender = x.Gender, EmpID = x.EmpID}}
.Concat(x.Employees))
.GroupBy(x => x.EmpID) //Group by employee ID
.Select(g => g.First());
var staffList = emps.ToList();
var staffDict = emps.ToDictionary(p => p.Name.ToLowerInvariant() + p.EmpID);
var staffSortedList = new SortedList<string, Person>(staffDict);
Console.WriteLine("Time to load staffList = " + sw.ElapsedMilliseconds + "ms");
var rajKeyText = "Raj".ToLowerInvariant();
sw.Reset();
sw.Start();
var rajs1 = staffList.AsParallel().Where(p => p.Name.ToLowerInvariant().Contains(rajKeyText)).ToList();
Console.WriteLine("Time to find Raj = " + sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
var rajs2 = staffDict.AsParallel().Where(kvp => kvp.Key.Contains(rajKeyText)).ToList();
Console.WriteLine("Time to find Raj = " + sw.ElapsedMilliseconds + "ms");
sw.Reset();
sw.Start();
var rajs3 = staffSortedList.AsParallel().Where(kvp => kvp.Key.Contains(rajKeyText)).ToList();
Console.WriteLine("Time to find Raj = " + sw.ElapsedMilliseconds + "ms");
Console.ReadLine();
}
public class Person
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
}
public class Boss
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
public List<Person> Employees { get; set; }
}
}
Output1:
Output2 (using .AsParallel() on searches):
In other words, if you can't use some faster data structure, up can speed your search up just by changing form
staffList.Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant()));
to
staffList.AsParallel().Where(m => m.Name.ToLowerInvariant().Contains("Raj".ToLowerInvariant()));

GroupBy in Enumerable method

I want to make a list of Report class from persons list with group by
class Report
{
public string Country { get; set; }
public double SumAge { get; set; }
}
class Person
{
public int Age { get; set; }
public string Name { get; set; }
public string Country { get; set; }
}
var list = new List<Person>
{
new Person {Age = 35, Country = "Spain", Name = "John"},
new Person {Age = 45, Country = "Spain", Name = "Alex"},
new Person {Age = 55, Country = "Hungary", Name = "Bob"},
new Person {Age = 23, Country = "Spain", Name = "Eve"},
new Person {Age = 39, Country = "India", Name = "Rose"},
new Person {Age = 25, Country = "India", Name = "Peter"},
new Person {Age = 51, Country = "Hungary", Name = "Rob"},
new Person {Age = 23, Country = "India", Name = "Jeff"},
};
list.GroupBy(p => p.Country, p => p)
.Select(p => new Report
{
Country = p.Key,
SumAge = p.Sum() //????
});
There is something wrong in select statement, how can I count a sum of ages?
You need to specify which property to sum:
var res = list.GroupBy(p => p.Country, p => p)
.Select(p => new Report
{
Country = p.Key,
SumAge = p.Sum(x => x.Age)
});

LINQ for a list of list

public class Employee
{
public string Name{get;set;}
public List<Department> Department { get; set; }
public string Company{get;set;}
}
public class Department
{
public string Name { get; set; }
public string Location { get; set; }
}
List<Employee> employees = new List<Employee>();
employees.Add(new Employee() { Company = "Dell", Name = "ABC" });
employees.Add(new Employee() { Company = "Dell", Name = "Aakash" });
employees.Add(new Employee() { Company = "CSC", Name = "Vaibhav" });
employees.Add(new Employee() { Company = "TCS", Name = "Sambhav" });
employees[0].Department = new List<Department>();
employees[0].Department.Add(new Department() { Location = "Delhi", Name = "HR" });
employees[0].Department.Add(new Department() { Location = "Delhi", Name = "Recruitment" });
employees[1].Department = new List<Department>();
employees[1].Department.Add(new Department() { Location = "Noida", Name = "EAO" });
employees[1].Department.Add(new Department() { Location = "Delhi", Name = "Arch" });
employees[2].Department = new List<Department>();
employees[2].Department.Add(new Department() { Location = "Denmark", Name = "Scandi" });
employees[2].Department.Add(new Department() { Location = "Noida", Name = "SAG" });
employees[3].Department = new List<Department>();
employees[3].Department.Add(new Department() { Location = "Mumbai", Name = "NSE" });
I need to write a lambda expression to select all employees where Departement Location is Noida
Use Any extension method:
var results = employees.Where(e => e.Department.Any(d => d.Location == "Noida"))
.ToList();
Actually you even don't need LINQ for that. List<T> has methods FindAll and Exsists which can do the job:
employees.FindAll(e => e.Department.Exists(d => d.Location == "Noida"))

Categories