How to merge two lists of objects based on a property? - c#

I have the two lists below where one comes from the database and the other from a JSON. The one from the database has it's Id associated.
List from database:
EmployeeId FirstName LastName EmployeeNumber
1234 Tom Cruise 98372829
5555 James Bond 93932228
The employeeId is a GUID Saved in the database.
Now I retrieve a list of Employees again to detect changes - James Bond Lastname changed. And I used automapper to map in the same format as my database entity.
List from JSON:
EmployeeId FirstName LastName EmployeeNumber
000-0000... Tom Cruise 98372829
000-0000... James Carter 93932228
Now I want to update the first list with the FirstName and LastName based on the EmployeeNumber.
// Employees retrieved in JSON
var retrievedEmployees = JsonSerializer.Deserialize<List<EmployeeDto>>(methodToRetrieveEmployees()))!.ToList();
var mappedEmployees = _mapper.Map<IEnumerable<Employee>>(retrievedEmployees);
var existingEmployeeFromDatabase = await GetExistingEmployees();
var employeesWithLatestUpdates = mappedEmployees
.Where(y => existingEmployeeFromDatabase.Any(z => z.Number == y.Number)).ToList();
So What I need to do is to update employeeswithLatestChanges (Id,FirstName and LastName) with the values from existingEmployees from the database. Since they don't have Id, this should be mapped by the EmployeeNumber.
I have tried to use Union/joins but no luck.

Updating by linq in c# 6
var updatedEmployee = employeeswithLatestChanges.Select(x => new Employee
{
FirstName = existingEmployees.FirstOrDefault(y => y.EmployeeId == x.EmployeeNumber)?.FirstName?? x.FirstName,
LastName = existingEmployees.FirstOrDefault(y => y.LastName == x.code)?.LastName ?? x.LastName ,
});
Can Use Loop also
foreach (var dbEmp in existingEmployees)
{
foreach(var emp in (employeeswithLatestChanges.Where(t => t.EmployeeNumber == dbEmp.EmployeeId)))
{
emp.FirstName= dbEmp.FirstName;
emp.LastName= dbEmp.LastName;
}
}

To fix the idea we can assume the following class to represent an employee:
public sealed class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid Id { get; set; }
public int Number { get; set; }
}
Suppose you have a collection of employees named employees that you want to update by using another collection of employees named updatedEmployees:
IEnumerable<Employee> employees = ....;
IEnumerable<Employee> updatedEmployees = ....;
The simplest way I can think of to solve your problem is the following:
public static void Main(string[] args)
{
// previous code omitted for brevity
Dictionary<int, Employee> employeeNumberToUpdatedEmployee = updatedEmployees.ToDictionary(x => x.Number);
foreach (var employee in employees)
{
if (employeeNumberToUpdatedEmployee.TryGetValue(employee.Number, out var updatedEmployee)
{
employee.FirstName = updatedEmployee.FirstName;
employee.LastName = updatedEmployee.LastName;
}
}
// subsequent code omitted for brevity
}
An alternative way to solve this problem is to perform a join operation by using LINQ to objects, as in the following code:
var employees = new List<Employee>
{
new Employee{ Id = Guid.NewGuid(), Number = 11, FirstName = "Bob", LastName = "Red" },
new Employee{ Id = Guid.NewGuid(), Number = 13, FirstName = "Alice", LastName = "Smith" },
new Employee{ Id = Guid.NewGuid(), Number = 5, FirstName = "Max", LastName = "Brown" },
};
var updatedEmployees = new List<Employee>
{
new Employee{ Id = Guid.NewGuid(), Number = 11, FirstName = "Bob", LastName = "Verdi" },
new Employee{ Id = Guid.NewGuid(), Number = 13, FirstName = "Alice", LastName = "Rossi" },
new Employee{ Id = Guid.NewGuid(), Number = 78, FirstName = "Sam", LastName = "Smith" },
};
// here we are using the fact that we can have, at most, one match
var query = from employee in employees
join updatedEmployee in updatedEmployees on employee.Number equals updatedEmployee.Number into matches
from match in matches.DefaultIfEmpty()
select new Employee
{
Id = employee.Id,
Number = employee.Number,
FirstName = match?.FirstName ?? employee.FirstName,
LastName = match?.LastName ?? employee.LastName,
};
foreach (var item in query)
{
Console.WriteLine($"{item.FirstName} {item.LastName}");
}

Related

Is there an easy way to flatten an array of objects while making an array of the one column that would be different?

I have an array of objects similar to this:
class StateVisitor
{
string FirstName { get; set; }
string LastName { get; set; }
string StateViseted { get; set; }
}
StateVisitor[] StateVisitors = {
new() { FirstName = "Bob", LastName = "Smith", StateViseted = "AL" },
new() { FirstName = "Bob", LastName = "Smith", StateViseted = "AK" },
new() { FirstName = "Bob", LastName = "Jones", StateViseted = "AL" },
new() { FirstName = "Sam", LastName = "Smith", StateViseted = "UT" }
}
And I want to do something like this:
class VisitorsWithCombinedStates {
string FirstName { get; set; }
string LastName { get; set; }
string[] StatesVisetedArray { get; set; }
}
VisitorsWithCombinedStates[] visitorsWithCombinedStates = StateVisitors... /* Linq magic? */
visitorsWithCombinedStates.ForEach(v
=> Console.WriteLine($"{v.FirstName} {v.LastName} visited {string.Join(", ",v.StatesVisitedArray)}"));
// "Bob Smith visited AL, AK"
// "Bob Jones visited AL"
// "Sam Smith visited UT"
Is there an easy way, in C# (probably with LINQ) to flatten that first array into the second array, where it makes an array of the states visited?
I think you are looking for GroupBy then projecting the groups into your new class:
var visitorsWithCombinedStates = StateVisitors.GroupBy(sv => new { sv.FirstName, sv.LastName }, sv => sv.StateVisited)
.Select(svg => new VisitorsWithCombinedStates {
FirstName = svg.Key.FirstName,
LastName = svg.Key.LastName,
StatesVisitedArray = svg.ToArray()
})
.ToArray();
NOTE: Corrected spelling of visited
You want to group your items by a combination of first and last name and project the result of the grouping as just the state visited
var groups = stateVisitors.GroupBy(sv => new
{
sv.FirstName,
sv.LastName,
},
sv => sv.StateVisited );
Output:
foreach(var g in groups)
{
// g.Key.FirstName
// g.Key.LastName
// g is IEnumerable<string> of visited states
}
An example, to fill your VisitorsWithCombinedStates class:
VisitorsWithCombinedStates[] visitorsWithCombinedStates = StateVisitors.GroupBy(x => new { x.FirstName, x.LastName},x => x.StateViseted)
.Select(x => new VisitorsWithCombinedStates { FirstName = x.Key.FirstName, LastName = x.Key.LastName, StatesVisetedArray = x.ToArray() }).ToArray();

Sort List of C# object by STRING parameter [duplicate]

This question already has answers here:
C# - code to order by a property using the property name as a string [duplicate]
(10 answers)
Closed 4 years ago.
I have a list of ListUser class objects. I need to be able to pass in a String value and order by that column in ascending or descending order using text expression. Everything I have seen that uses Lambda expressions, has the object property as a strongly typed value, How can I achieve this by adding in "firstname descending" as a parameter ?
The code is as follows
namespace SortLists
{
class ListUser
{
public int id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string company { get; set; }
public string phonenumber { get; set; }
}
class Program
{
static void Main(string[] args)
{
var user1 = new ListUser() { id = 1, firstname = "James", lastname = "Smith", company = "Code Logic", phonenumber = "01235 566 456" };
var user2 = new ListUser() { id = 1, firstname = "Chris", lastname = "Andrews", company = "Adobe", phonenumber = "01235 566 456" };
var user3 = new ListUser() { id = 1, firstname = "Paul", lastname = "Jones", company = "Microsoft", phonenumber = "01235 566 456" };
var user4 = new ListUser() { id = 1, firstname = "Peter", lastname = "Williams", company = "Apple", phonenumber = "01235 566 456" };
List<ListUser> users = new List<ListUser>()
{
user1, user2, user3, user4
};
}
}
Add reference to nuget package:
https://www.nuget.org/packages/System.Linq.Dynamic/
Add using System.Linq.Dynamic; at the top.
Use var usersSorted = users.AsQueryable().OrderBy("firstname ASC").ToList();
It's easy with a dictionary. Just start with this:
var sortBy = new Dictionary<string, Func<IEnumerable<ListUser>, IEnumerable<ListUser>>>()
{
{ "firstname", lus => lus.OrderBy(lu => lu.firstname) },
{ "lastname", lus => lus.OrderBy(lu => lu.lastname) },
{ "company", lus => lus.OrderBy(lu => lu.company) },
{ "phonenumber", lus => lus.OrderBy(lu => lu.phonenumber) },
};
Then you can easily sort like this:
List<ListUser> sorted = sortBy["firstname"](users).ToList();
If you want it descending just do this:
List<ListUser> sorted = sortBy["firstname"](users).Reverse().ToList();
Just structure your sort method like so:
if(stringPassed == "firstname")
{
List<ListUser> sortedListUser = listUser.OrderBy(p=>p.firstName).ToList();
}
else if(...) // and so on
if you want to order them by desc order just use LINQ's .OrderByDescending method.
The other cool approach may be that you set your properties to be objects with
string value;
string name;
and loop your input string with reflection towards the properties in your class and get the one you want and order it. It's a fancy way to impress your teacher xaxa.

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()));

Order GroupBy Asc based on Two more Properties using LINQ 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();

Pulling objects from a collection that satisfies a condition

I was wondering whether you know using LINQ in C# to pull a list of objects from a collection that satisfies a condition?
I am trying to pull a list of Person objects from a list whose IDs match in an integer list. Please see the code below (I am trying to pull all person objects whose IDs are in the integer list).
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
...
var intList = new List<int>() { 1, 2, 3, 4 };
var perList = new List<Person>
{
new Person {Id = 1, FirstName = "Thomas", LastName = "Joseph"},
new Person {Id = 2, FirstName = "Joseph", LastName = "Austin"},
new Person {Id = 3, FirstName = "Lee", LastName = "Hentry"},
new Person {Id = 4, FirstName = "Abraham", LastName = "Tony"}
};
You can use the Where and Contains methods:
var people = perList
.Where(person => intList.Contains(person.Id))
.ToList();
You can use the Where and Any methods:
var filteredPeople = perList.Where(person => intList.Any(person.Id)).ToList();

Categories