LINQ group by and max clause usage - linq

I have below Employee class which has 3 important fields
public class Employee
{
public string Name
{
get;
set;
}
public string Department
{
get;
set;
}
public double Salary
{
get;
set;
}
}
I have a list of such employees. I want to find out name employee from each department
whose salary is maximum in his/her department.
I Wrote below query but its not working.
List<Employee> list = new List<Employee>();
list.Add(new Employee { Name="Hemant",Salary=10,Department="PTS"});
list.Add(new Employee { Name = "Gunjan", Salary = 11, Department = "PTS" });
list.Add(new Employee { Name = "Akshay", Salary = 8, Department = "PTS" });
list.Add(new Employee { Name = "Omkar", Salary = 10, Department = "EBG" });
list.Add(new Employee { Name = "Hemant1", Salary = 14, Department = "EBG" });
var query1 = from e in list
group e by e.Department into g
select new { Dept = g.Key,MaxSal = g.Max((e) => e.Salary )};
foreach (var item in query1)
{
Console.WriteLine("Department : " + item.Dept + " : " + " Salary is : " + item.MaxSal);
}
But above piece of code is not working, I am not able to select employee name. I think I must use ascending/descending clause and select first or last record. But I am not able to figure it out. Can someone help.
Regards,
Hemant Shelar

Yes, you should OrderByDescending and then get First:
var query1 = list.GroupBy(x => x.Department)
.Select(g => g.OrderByDescending(x => x.Salary).First());
Below is console code:
foreach (var item in query1)
{
string output = string.Format("Deparment: {0}, Name: {1}, Max Salaray: {2}",
item.Department, item.Name, item.Salary
);
Console.WriteLine(output);
}

Related

How do I use LINQ group by clause to return unique employee rows?

I'm pretty new to LINQ, and I can't for the life of me figure this out. I've seen lots of posts on how to use the group by in LINQ, but for some reason, I can't get it to work. This is so easy in ADO.NET, but I'm trying to use LINQ. Here's what I have that is relevant to the problem. I have marked the part that doesn't work.
public class JoinResult
{
public int LocationID;
public int EmployeeID;
public string LastName;
public string FirstName;
public string Position;
public bool Active;
}
private IQueryable<JoinResult> JoinResultIQueryable;
public IList<JoinResult> JoinResultIList;
JoinResultIQueryable = (
from e in IDDSContext.Employee
join p in IDDSContext.Position on e.PositionID equals p.PositionID
join el in IDDSContext.EmployeeLocation on e.EmployeeID equals el.EmployeeID
where e.PositionID != 1 // Do not display the super administrator's data.
orderby e.LastName, e.FirstName
// ***** Edit: I forgot to add this line of code, which applies a filter
// ***** to the IQueryable. It is this filter (or others like it that I
// ***** have omitted) that causes the query to return multiple rows.
// ***** The EmployeeLocationsList contains multiple LocationIDs, hence
// ***** the duplicates employees that I need to get rid of.
JoinResultIQueryable = JoinResultIQueryable
.Where(e => EmployeeLocationsList.Contains(e.LocationID);
// *****
// ***** The following line of code is what I want to do, but it doesn't work.
// ***** I just want the above join to bring back unique employees with all the data.
// ***** Select Distinct is way too cumbersome, so I'm using group by.
group el by e.EmployeeID
select new JoinResult
{
LocationID = el.LocationID,
EmployeeID = e.EmployeeID,
LastName = e.LastName,
FirstName = e.FirstName,
Position = p.Position1,
Active = e.Active
})
.AsNoTracking();
JoinResultIList = await JoinResultIQueryable
.ToListAsync();
How do I get from the IQueryable to the IList only returning the unique employee rows?
***** Edit:
Here is my current output:
[4][4][Anderson (OH)][Amanda][Dentist][True]
[5][4][Anderson (OH)][Amanda][Dentist][True]
[4][25][Stevens (OH)][Sally][Dental Assistant][True]
[4][30][Becon (OH)][Brenda][Administrative Assistant][False]
[5][30][Becon (OH)][Brenda][Administrative Assistant][False]
Actually you do not need grouping here, but Distinct. Ordering before Distinct or grouping is useless. Also AsNoTracking with custom projection is not needed.
var query =
from e in IDDSContext.Employee
join p in IDDSContext.Position on e.PositionID equals p.PositionID
join el in IDDSContext.EmployeeLocation on e.EmployeeID equals el.EmployeeID
where e.PositionID != 1 // Do not display the super administrator's data.
select new JoinResult
{
LocationID = el.LocationID,
EmployeeID = e.EmployeeID,
LastName = e.LastName,
FirstName = e.FirstName,
Position = p.Position1,
Active = e.Active
};
query = query.Distinct().OrderBy(e => e.LastName).ThenBy(e => e.FirstName);
JoinResultIList = await query.ToListAsync();
The problem is that few employees have more than one location is causing the results to be repeated.You can handle it in multiple ways. Im using Let clause to tackle the issue in the below example
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
public int PositionID { get; set; }
}
public class EmployeeLocation
{
public int EmployeeID { get; set; }
public int LocationID { get; set; }
}
public class Position
{
public int PositionID { get; set; }
public string Position1 { get; set; }
}
public class Location
{
public int LocationID { get; set; }
}
public class JoinResult
{
//Suggestion : Insetad of LocationID there should be a varibale that has all the locations of an employee
public IEnumerable<int> LocationIDs;
public int LocationID;
public int EmployeeID;
public string LastName;
public string FirstName;
public string Position;
public bool Active;
}
//Setting up mock data
List<Position> positions = new List<Position>();
positions.Add(new Position() { Position1 = "Dentist", PositionID = 2 });
positions.Add(new Position() { Position1 = "Dental Assistant", PositionID = 3 });
positions.Add(new Position() { Position1 = "Administrative Assistant", PositionID = 4 });
List<Employee> employees = new List<Employee>();
employees.Add(new Employee() { EmployeeID = 4, FirstName = "Amanda", LastName = "Anderson (OH)", PositionID = 2 });
employees.Add(new Employee() { EmployeeID = 25, FirstName = "Sally", LastName = "Stevens (OH)", PositionID = 3 });
employees.Add(new Employee() { EmployeeID = 30, FirstName = "Brenda", LastName = "Becon (OH)", PositionID = 4 });
List<Location> locations = new List<Location>();
locations.Add(new Location() { LocationID = 4 });
locations.Add(new Location() { LocationID = 5 });
List<EmployeeLocation> employeeLocation = new List<EmployeeLocation>();
employeeLocation.Add(new EmployeeLocation() { LocationID = 4, EmployeeID = 4 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 5, EmployeeID = 4 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 4, EmployeeID = 25 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 4, EmployeeID = 30 });
employeeLocation.Add(new EmployeeLocation() { LocationID = 5, EmployeeID = 30 });
var result = (from e in employees
join p in positions on e.PositionID equals p.PositionID
let employeeLocations = (from el in employeeLocation where el.EmployeeID == e.EmployeeID select new { LocationID = el.LocationID })
where e.PositionID != 1 // Do not display the super administrator's data.
orderby e.LastName, e.FirstName
select new JoinResult
{
LocationID = employeeLocations.Select(p=>p.LocationID).First()//Here its just selecting the first location,
LocationIDs = employeeLocations.Select(p=> p.LocationID),//This is my suggestion
EmployeeID = e.EmployeeID,
LastName = e.LastName,
FirstName = e.FirstName,
Position = p.Position1,
}).ToList();
Okay. So here is the solution I came up with. I installed the morelinq NuGet package, which contains a DistinctBy() method. Then I added that method to the last line of the code shown in my problem.
JoinResultIList = JoinResultIQueryable
.DistinctBy(jr => jr.EmployeeID)
.ToList();

Extending a Select.... to accommodate more fields as extension using EF

I have a class:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Salary { get; set; }
public string Address {get;set;}
}
And query using Entity Framework is:
var selectedEmployee = entities.Employees
.Where(e=>e.Salary>10000)
.Select(emp => new EmpDTO
{
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary
});
My question is:
I want to allow extending this query without rewriting the base query. It should allow adding a new field in the .Select(.....) by extending the above query.
Without rewriting the complete query:
var selectedEmployee = entities.Employees
.Where(e=>e.Salary>10000)
.Select(emp => new EmpDTO
{
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary,
Address = emp.Address
});
How can I do that?
Thanks
If I understand, you can try this:
public IQuerable<EmpDTO> GetEmployee(Func<Employee, EmpDTO> projection = null)
{
if(projection == null)
projection = emp => new EmpDTO {
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary,
};
return entities.Employees.Where(e => e.Salary > 10000).Select(projection);
}
Implementation:
var query = classInstance.GetEmployee();
//or
query = classInstance.GetEmployee(emp => new EmpDTO {
Id = emp.Id,
Name = emp.Name,
Salary = emp.Salary,
Address = emp.Address
});
If you always want to get some set of fields, like Id, Name and
Salary and sometimes take additional fields(and specify only their
as method arguments), you should to take all fields from DB and only
then filter them depends on your condition - it is bad practice to do
SELECT *, so you should get default set of fields or specify all desired fields mannualy.
Solution with SELECT *:
public List<EmpDTO> GetEmployee(Func<Employee, EmpDTO> projection)
{
var query = entities.Employees.Where(e => e.Salary > 10000).ToList().Select(x => {
var item = projection == null ? new EmpDTO() : projection(x);
item.Id = x.Id;
item.Name = x.Name;
item.Salary = x.Salary;
return item;
}).ToList();
}
At this case return value is List<T> not IQuerable<T>;
Implementation:
var items = classInstance.GetEmployee(emp => new EmpDTO { Address = emp.Address });
//items also will contain fields: Id, Name and Salary by default

Linq to objects, joining to collections and simplify select new

I need some help to simplify a linq query. I have 2 classes Invoice and Customer.
The Invoice have a property CustomerId and a property Customer.
I need to get all invoices and include the Customer object.
I don't like my query, as it needs to change if new properties are added to the Invoice object.
I can't join the invoice and customer earlier than this stage so that is not an alternative.
My query.
var customers = GetCustomers();
var invoices = GetInvoices();
var joinedList = (from x in invoices
join y in customers on x.CustomerId equals y.CustomerId
select new Invoice
{
Amount = x.Amount,
CustomerId = x.CustomerId,
Customer = y,
InvoiceId = x.InvoiceId
}).ToList();
The classes
public class Invoice
{
public int InvoiceId { get; set; }
public double Amount { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
}
private static IEnumerable<Invoice> GetInvoices()
{
yield return new Invoice
{
Amount = 34,
CustomerId = 1,
InvoiceId = 1
};
yield return new Invoice
{
Amount = 44.7,
CustomerId = 1,
InvoiceId = 2
};
yield return new Invoice
{
Amount = 67,
CustomerId = 2,
InvoiceId = 3
};
yield return new Invoice
{
Amount = 89,
CustomerId = 3,
InvoiceId = 4
};
}
private static IEnumerable<Customer> GetCustomers()
{
yield return new Customer
{
CustomerId = 1,
Name = "Bob"
};
yield return new Customer
{
CustomerId = 2,
Name = "Don"
};
yield return new Customer
{
CustomerId = 3,
Name = "Alice"
};
}
Why not just a simple foreach loop:
// Dictionary for efficient look-up
var customers = GetCustomers().ToDictionary(c => c.CustomerId);
var invoices = GetInvoices().ToList();
//TODO: error checking
foreach(var i in invoices)
i.Customer = customers[i.CustomerId];

LINQ with Group, Join and Where Easy in SQL not in LINQ?

I have trouble understand how to translate SQL into LINQ. I would like to do the following but can't figure out how to get the Group By to work
var query = from s in Supplier
join o in Offers on s.Supp_ID equals o.Supp_ID
join p in Product on o.Prod_ID equals p.Prod_ID
where s.City == "Chicago"
group s by s.City into Results
select new { Name = Results.Name };
I just need to do something simple like display the product name of this simple query, how does the group by work with joins and a where?
You haven't provided classes so I assumed that they are like below:
public class Supplier
{
public int SupplierID { get; set; }
public string SuppierName { get; set; }
public string City { get; set; }
}
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
}
public class Offer
{
public int SupplierID { get; set; }
public int ProductID { get; set; }
}
Then I added data for testing:
List<Supplier> supplierList = new List<Supplier>()
{
new Supplier() { SupplierID = 1, SuppierName = "FirstCompany", City = "Chicago"},
new Supplier() { SupplierID = 2, SuppierName = "SecondCompany", City = "Chicago"},
new Supplier() { SupplierID = 3, SuppierName = "ThirdCompany", City = "Chicago"},
};
List<Product> productList = new List<Product>()
{
new Product() { ProductID = 1, ProductName = "FirstProduct" },
new Product() { ProductID = 2, ProductName = "SecondProduct" },
new Product() { ProductID = 3, ProductName = "ThirdProduct" }
};
List<Offer> offerList = new List<Offer>()
{
new Offer() { SupplierID = 1, ProductID = 2},
new Offer() { SupplierID = 2, ProductID = 1},
new Offer() { SupplierID = 2, ProductID = 3}
};
If you want to show names of suppliers whiches products have been offered then your LINQ query should be as this:
IEnumerable<string> result = from supplier in supplierList
join offer in offerList on supplier.SupplierID equals offer.SupplierID
join product in productList on offer.ProductID equals product.ProductID
where supplier.City == "Chicago"
group supplier by supplier.SuppierName into g
select g.Key;
You can see if correct names have been selected:
foreach (string supplierName in result)
{
Console.WriteLine(supplierName);
}
It must give following result:
FirstCompany
SecondCompany
You could try this:
var query = from s in Supplier
join o in Offers on s.Supp_ID equals o.Supp_ID
join p in Product on o.Prod_ID equals p.Prod_ID
where s.City == "Chicago"
group s
by new {s.City, s.Name} //added this
into Results
select new { Name = Results.Key.Name };
You group s (Supplier) by s.City. The result of this is an IGrouping<City, Supplier>. I.e. only City and Supplier are within reach after the grouping: for each City you get an IEnumerable<Supplier> of its suppliers (which will be multiplied by the joins, by the way).
Since you also have the condition where s.City == "Chicago" grouping by city is of no use. There is only one city. So I think you may as well do something like this:
from s in Supplier
join o in Offers on s.Supp_ID equals o.Supp_ID
join p in Product on o.Prod_ID equals p.Prod_ID
where s.City == "Chicago"
select new {
City = s.City.Name,
Supplier = s.Name,
Product = p.Name,
...
};

How do I aggregate joins?

I have three related tables:
Employee(EmployeeId, EmployeeName)
Skill(SkillId, SkillName)
EmployeeSkill(EmployeSkillId, EmployeeId, SkillId)
EmployeSkillId is an identity.
The rows in the database are the following:
Employee Table:
EmployeeId | EmployeeNumber | EmployeeName
---------- | -------------- | ------------
1 | 10015 | John Doe
Skill Table:
SkillId | SkillName
------- | ---------
1 | .NET
2 | SQL
3 | OOD
4 | Leadership
EmployeeSkill Table:
EmployeeSkillId | EmployeeId | SkillId
--------------- | ---------- | -------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
If I have an employee who has three skills registered on EmployeSkill, I'd like to be able to have a result as the following:
John Doe, "Skill-1, Skill2, Skill-3"
That is, to concatenate the name of the skills for that employee into a single string.
I tried the following, but it isn't working.
var query = from emp in Employee.All()
from es in emp.EmployeeSkills
join sk in Skill.All() on es.SkillId equals sk.SkillId
group sk by new {emp.EmployeeName} into g
select new TestEntity
{
Name = g.Key.EmployeeName,
Skills = g.Aggregate(new StringBuilder(),
(sb, grp_row) => sb.Append(grp_row.SkillName))
.ToString()
};
The aggregated list of skill names is coming back empty. How can I do this?
It sounds like you could do the join as part of the select:
var query = from emp in Employee.All()
select new TestEntity {
Name = emp.EmployeeName,
Skills = string.Join(", ",
(from es in emp.EmployeeSkills
join sk in Skill.All() on es.SkillId equals sk.SkillId
select sk.SkillName)) };
Now that's going to do the join separately for each individually, which isn't terribly efficient. Another option is to build a mapping from skill ID to skill name first:
var skillMap = Skill.All().ToDictionary(sk => sk.SkillId,
sk => sk.SkillName);
then the main query is easy:
var query = from emp in Employee.All()
select new TestEntity {
Name = emp.EmployeeName,
Skills = string.Join(", ",
emp.EmployeeSkills.Select(sk => skillMap[sk.SkillId]))};
Ultimately there are lots of ways of skinning this cat - for example, if you wanted to stick to your original approach, that's still feasible. I would do it like this:
var query = from emp in Employee.All()
from es in emp.EmployeeSkills
join sk in Skill.All() on es.SkillId equals sk.SkillId
group sk.SkillName by emp into g
select new TestEntity
{
Name = g.Key.EmployeeName,
Skills = string.Join(", ", g)
};
At this point it's quite similar to your original query, just using string.Join instead of Aggregate, of course. If all these three approaches come back with an empty skills list, then I suspect there's something wrong with your data. It's not obvious to me why your first query would "succeed" but with an empty skill list.
EDIT: Okay, here's a short(-ish) but complete example of it working:
using System;
using System.Collections.Generic;
using System.Linq;
public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public static List<Employee> All { get; set; }
public IEnumerable<EmployeeSkill> EmployeeSkills
{
get
{
return EmployeeSkill.All
.Where(x => x.EmployeeId == EmployeeId);
}
}
}
public class Skill
{
public string SkillName { get; set; }
public int SkillId { get; set; }
public static List<Skill> All { get; set; }
}
public class EmployeeSkill
{
public int SkillId { get; set; }
public int EmployeeId { get; set; }
public static List<EmployeeSkill> All { get; set; }
}
class Test
{
static void Main()
{
Skill.All = new List<Skill>
{
new Skill { SkillName = "C#", SkillId = 1},
new Skill { SkillName = "Java", SkillId = 2},
new Skill { SkillName = "C++", SkillId = 3},
};
Employee.All = new List<Employee>
{
new Employee { EmployeeName = "Fred", EmployeeId = 1 },
new Employee { EmployeeName = "Ginger", EmployeeId = 2 },
};
EmployeeSkill.All = new List<EmployeeSkill>
{
new EmployeeSkill { SkillId = 1, EmployeeId = 1 },
new EmployeeSkill { SkillId = 2, EmployeeId = 1 },
new EmployeeSkill { SkillId = 2, EmployeeId = 2 },
new EmployeeSkill { SkillId = 3, EmployeeId = 2 },
};
var query = from emp in Employee.All
from es in emp.EmployeeSkills
join sk in Skill.All on es.SkillId equals sk.SkillId
group sk.SkillName by emp.EmployeeName into g
select new
{
Name = g.Key,
Skills = string.Join(", ", g)
};
foreach (var result in query)
{
Console.WriteLine(result);
}
}
}
Results:
{ Name = Fred, Skills = C#, Java }
{ Name = Ginger, Skills = Java, C++ }

Resources