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

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

Related

linq distinct with object selection

I have the following linq statement:
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floornr = x.floor,
appnr = x.roomnr
})
.Distinct()
.ToList();
Somehow this does not return distinct datasets. I assume it has something to do with the selection of the object? The distinct function is therefore not comparing the attributes directly but rather the objects? I am not understanding it fully unfortunately but in ms sql this statement works fine.
I also tried the following but it does not return a List object and I would need to use var or something else and I need a List of Consumer() objects.
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floornr = x.floor,
appnr = x.roomnr
})
.GroupBy(x => new { x.firstname, x.lastname, x.haus, x.etage, x.appnr })
.ToList();
You can do/try this:
public class Consumer
{
public string firstname { get; set; }
public string lastname { get; set; }
public string floor { get; set; }
public string house { get; set; }
}
List<Consumer> objConsumer = new List<Consumer>()
{
new Consumer(){ firstname="Govind", lastname="Sahu", house="298",floor="1st Floor"},
new Consumer(){ firstname="Govind", lastname="Sahu", house="298",floor="1st Floor"},
new Consumer(){ firstname="Govind1", lastname="Sahoo", house="297",floor="1st Floor"}
};
1st approach:
var obj = objConsumer.GroupBy(s => new {
s.firstname,
s.lastname,
s.house,
s.floor,
}).Select(o=>o.FirstOrDefault()).ToList();
2nd approach:
var obj1 = objConsumer.Select(s => new { firstname = s.firstname, lastname = s.lastname, house = s.house, floor = s.floor }).Distinct().ToList();
I found another solution by using DistinctBy from MoreLinq. It returns the right result when I use it like this
consumers = data.Select(x => new Consumer()
{
firstname = x.firstname,
lastname = x.lastname,
house = x.sublocationid,
floor = x.floor,
appnr = x.roomnr
})
.DistinctBy(x=> new
{
x.firstname,
x.lastname,
x.floor,
x.appnr,
x.house
})
.ToList();

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,
...
};

Get Count from entity framework

Quite new to EF, basically i want to convert this SQL query:
SELECT
PSKU.ProductSKUID,
PSKU.ProductSKUName,
W.WarehouseID,
W.WarehouseName,
SA.SystemAreaName,
COUNT(SLI.ProductSKUID) AS QTY
FROM dbo.StockLineItem AS SLI INNER JOIN
dbo.ProductSKU AS PSKU ON PSKU.ProductSKUID = SLI.ProductSKUID INNER JOIN
dbo.Warehouse AS W ON W.WarehouseID = SLI.WarehouseID INNER JOIN
dbo.SystemArea AS SA ON SA.SystemAreaID = SLI.SystemAreaID
WHERE (SA.SystemAreaID = 1)
AND W.WarehouseID = #WarehouseID
GROUP BY PSKU.ProductSKUID, PSKU.ProductSKUName, W.WarehouseName, SA.SystemAreaName, W.WarehouseID
To an effective EF statement. This is what i Have so far, my Model class and the method:
[Serializable]
public class StockReturnMethod
{
public int ProductSKUID { get; set; }
public int WarehouseID { get; set; }
public int LotID { get; set; }
public string LotName { get; set; }
public int AreaID { get; set; }
public string AreaName { get; set; }
public int BinID { get; set; }
public string BinName { get; set; }
}
public class DALStockMovement
{
scmEntitiesPrimaryCon entities = new scmEntitiesPrimaryCon();
public List<AvailibleStock> AvailibleStockQty(int warehouseID)
{
var rows = (from PLA in entities.ProductLocationAssignments
from W in entities.Warehouses
from SLI in entities.StockLineItems
from SA in entities.SystemAreas
from PSKU in entities.ProductSKUs
where W.WarehouseID == warehouseID
select new AvailibleStock() { WarehouseID = W.WarehouseID, ProductSKUID = PSKU.ProductSKUID, ProductSKUName = PSKU.ProductSKUName, WarehouseName = W.WarehouseName, Status = SA.SystemAreaName, QtyUnassigned = SLI.ProductSKUID }).ToList();
return rows;
}
Any Advice to get this to an Effective EF Statement would be appreciated
I actually used this tool called Linqer, since I had the SQL
I just popped it into that tool and it generated the Linq for me.
Here is what came out:
var SKUStock = (from sli in entities.StockLineItems
where
sli.SystemArea.SystemAreaID == 1 &&
sli.WarehouseID == warehouseID
group new { sli.ProductSKU, sli.Warehouse, sli.SystemArea, sli } by new
{
ProductSKUID = (System.Int32?)sli.ProductSKU.ProductSKUID,
sli.ProductSKU.ProductSKUName,
sli.Warehouse.WarehouseName,
sli.SystemArea.SystemAreaName,
WarehouseID = (System.Int32?)sli.Warehouse.WarehouseID
} into g
select new AvailibleStock()
{
ProductSKUID = (int)(System.Int32?)g.Key.ProductSKUID,
ProductSKUName = g.Key.ProductSKUName,
WarehouseID = (int)(System.Int32?)g.Key.WarehouseID,
WarehouseName = g.Key.WarehouseName,
Status = g.Key.SystemAreaName,
QtyUnassigned = (int)(Int64?)g.Count(p => p.sli.ProductSKUID != null)
}).ToList();
return SKUStock;
It returns exactly what i need :).

How To: Joining three lists using Linq to objects

Problem is with the addresses not being outputted
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LinqToObjects
{
class Program
{
static void Main(string[] args)
{
var customers = Customer.GetAllCustomers();
var addresses = Address.GetAllAddresses();
var addressRelations = AddressRelation.GetAllAddressRelations();
var results = customers
.Join(addressRelations,
c => c.CustomerID,
ar => ar.CustomerID,
(c, ar) => new
{
CustomerName = c.FirstName + " " + c.LastName,
CustomerID = c.CustomerID,
AddressRelID = ar.AddressID
});
var resultsJoined = results
.GroupJoin(addresses,
ar => ar.AddressRelID,
a => a.AddressID,
(ar, a) => new
{
CustomerName = ar.CustomerName,
AddressLine = addresses.Select(b => b.StreetAddress).FirstOrDefault()
});
foreach(var item in resultsJoined)
{
Console.WriteLine(item.CustomerName);
Console.WriteLine(item.AddressLine);
Console.WriteLine("-----------------");
}
}
}
public class AddressRelation
{
public int AddressRelationID { get; set; }
public int CustomerID { get; set; }
public int AddressID { get; set; }
public AddressRelation(int id, int customerId, int addressId)
{
AddressRelationID = id; CustomerID = customerId; AddressID = addressId;
}
public static List<AddressRelation> GetAllAddressRelations()
{
var AllAddressRelations = new List<AddressRelation>();//simulate data returned from db
var addressRelation1 = new AddressRelation(1, 1, 1);
var addressRelation2 = new AddressRelation(2, 3, 3);
var addressRelation3 = new AddressRelation(3, 2, 2);
AllAddressRelations.Add(addressRelation1);
AllAddressRelations.Add(addressRelation2);
AllAddressRelations.Add(addressRelation3);
return AllAddressRelations;
}
}
public class Address
{
public int AddressID { get; set; }
public string StreetAddress { get; set; }
public Address(int id, string streetAddress)
{
AddressID = id; StreetAddress = streetAddress;
}
public static List<Address> GetAllAddresses()
{
var AllAddresses = new List<Address>();
Address customer1Address = new Address(1, "Elm St");
Address customer2Address = new Address(2, "Willow Way");
Address customer3Address = new Address(3, "Linq Ln");
AllAddresses.Add(customer1Address);
AllAddresses.Add(customer2Address);
AllAddresses.Add(customer3Address);
return AllAddresses;
}
}
public class Customer
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Customer(int id,string firstName, string lastName)
{
CustomerID = id; FirstName = firstName; LastName = lastName;
}
public static List<Customer> GetAllCustomers()
{
var AllCustomers = new List<Customer>();
var customer1 = new Customer(1, "James", "T");
var customer2 = new Customer(2, "Donnie", "H");
var customer3 = new Customer(3, "Sarah", "H");
AllCustomers.Add(customer1);
AllCustomers.Add(customer2);
AllCustomers.Add(customer3);
return AllCustomers;
}
}
}
The query isn't very expressive. If I was going to join three lists using LinqToObjects, I'd do this:
var query =
from c in customers
join xr in addressRelations on c.CustomerId equals xr.CustomerId
join a in addresses on xr.AddressId equals a.AddressId
select new {Customer = c, Address = a};
Looks like another mistake. I bet that AddressRelId is the key to the AddressRelation table, and not what you want use to connect to the Address table.
.GroupJoin(addresses,
ar => ar.Address**Rel**ID,
a => a.AddressID,
In response to comment:
var query = customers
.Join(addressRelations,
c => c.CustomerId,
xr => xr.CustomerId,
(c, xr) => new {c, xr})
.Join(addresses,
x => x.xr.AddressId,
a => a.AddressId,
(x, a) => new {c = x.c, xr = x.xr, a = a})
.Select(x => new {Customer = x.c, Address = x.a});
It's returning the first customer address because you've told it to:
AddressLine = addresses.Select(b => b.AddressLine1).FirstOrDefault()
Here, addresses is all addresses. I suspect you just want:
AddressLine = a.Select(b => b.AddressLine1).FirstOrDefault()

Resources