Group records by id - linq

I am getting records like this returned from a stored procedure:
Id FullName Review Rating
---------------------------------------------
1 john sk 4.5
1 john hhh 3.5
1 john jhj 1.5
2 rig www 3.5
2 rig eee 1.5
This is my query which return records like above:
var empDetails = context.Database.SqlQuery<SearchWorkerDetail>("exec SearchWorkerDetail #param1", new SqlParameter("param1", searchKeyword) )
.Select( d => new
{
id = d.Id,
FullName = d.FullName,
Email = d.Email,
ServiceDescription = d.ServiceDescription,
Skills = d.Skills,
Name = d.Name,
r = d.Review,
averagerating = d.Rating
}).ToList();
Now I want to group the records by Id and want to select data.
Expected output:
SearchWorkerDetail=
[0]:{
Id:1
FullName:John
Email:john#yahoo.com
ReviewList:
{
[0]: Review=sk
rating=4.5
[1]: Review=hhh
rating=3.5
[2]: Review=jhj
rating=1.5
}
[1]:{
Id:2
FullName:rig
Email:rig#yahoo.com
ReviewList:
{
[0]: Review=www
rating=3.5
[1]: Review=eee
rating=1.5
}
}
For corresponding id I want list of reviews (1 to many relation between worker and review)
My class structure:
[DataContract]
public class SearchWorkerDetail
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string FullName { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string ServiceDescription { get; set; }
[DataMember]
public string Skills { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Review { get; set; }
[DataMember]
public Nullable<decimal> Rating { get; set; }
[DataMember]
public List<ReviewModel> RatingList { get; set; }
}
My query is like this:
var data = empDetails.GroupBy(m=>m.id)
.Select(g => new SearchWorkerDetail
{
id =g.Key,
FullName = g.FullName,
Reviewlist =
}
).ToList();
Here it is not allowing me to select FullName and other (eg: Email, skillDescription, Skills etc)
How do I do this?

There is no FullName property on g because it's a group not a single object. However you can get the FullName of first record in the group then create a ReviewList for each record in the group by using another Select:
empDetails.GroupBy(m => m.id)
.Select(g => new SearchWorkerDetail
{
id = g.Key,
FullName = g.First().FullName,
Reviewlist= g.Select(x => new ReviewModel
{
Review = x.Review,
rating = x.Rating
}.ToList()
}).ToList();
Or you can also group by based on FullName:
empDetails.GroupBy(m => new { m.id, m.FullName })
.Select(g => new SearchWorkerDetail
{
id = g.Key.id,
FullName = g.Key.FullName,
/* rest is the same */
}).ToList();

Related

product sale qty sum and name in linq group query

I have a product sales data and want to show the summary of sale grouped by product id.
Summary result should show product name and total sales. How can I select a field along with groupby result and that field is not the key field.
public partial class SaleOrderDetail
{
public int Id { get; set; }
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public decimal LineTotal { get; set; }
}
var query = from saleorder in _dbContext.SaleOrderDetail
group saleorder by saleorder.ProductId into salesummary
select new
{
productid = salesummary.Key,
prdouctname = salesummary.First().ProductName,
totalqty = salesummary.Sum(s => s.Quantity)
};
I got the error invalidoperationException because of First() for product name.
You have to include ProductName in grouping Key.
var query =
from saleorder in _dbContext.SaleOrderDetail
group saleorder by new {saleorder.ProductId, saleorder.ProductName} into salesummary
select new
{
productid = salesummary.Key.ProductId,
prdouctname = salesummary.Key.ProductName,
totalqty = salesummary.Sum(s => s.Quantity)
};
Making SaleOrderDetail as AsEnumerable() did the trick. For SQL Expression it will work if make it as AsEnumerable() or .ToList<> etc.
var query = from saleorder in _dbContext.SaleOrderDetail.AsEnumerable()
group saleorder by saleorder.ProductId into salesummary
select new
{
productid = salesummary.Key,
prdouctname = salesummary.First().ProductName,
totalqty = salesummary.Sum(s => s.Quantity)
};

LINQ Join Group By Select New

I have the following entities:
User is:
public String Id { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
Assessment is:
public int Id { get; set; }
public int SymptomId { get; set; }
public string UserId { get; set; }
public DateTime CreatedDate { get; set; }
A User can have 0, 1 or more Assessments.
I have written the following LINQ:
// Get Paged Users Recent Assessment List:
var _usersWithRecentAssessment =
from U in _context.Users
join A in _context.Assessments on U.Id equals A.UserId
group A by A.UserId into uaGroup
select uaGroup.OrderByDescending(a => a.CreatedDate).FirstOrDefault();
_usersWithRecentAssessment = _usersWithRecentAssessment.OrderByDescending(ua => ua.CreatedDate);
which returns the most recent symptom assessment for all Users that have completed an Assessment (and orders the assessment list in descending order of Assessment CreatedDate) as follows:
[
{
"id": 1052,
"symptomId": 44,
"userId": "b978d113-7da7-4b7f-a121-9dd71e158dd4",
"createdDate": "2019-11-16T12:50:05.2175621"
},
{
"id": 1051,
"symptomId": 44,
"userId": "5230f4b7-bf2a-46b0-88a0-6f13fa5caa91",
"createdDate": "2019-11-03T14:46:21.6598763"
}
]
I would like to return the following AssessmentDTO
where AssessmentDTO is:
public int Id { get; set; }
public int SymptomId { get; set; }
public string UserId { get; set; }
public string UserFirstName { get; set; }
public string UserLastName { get; set; }
public DateTime CreatedDate { get; set; }
which contains the additional attributes UserId, UserFirstName, and UserLastName from the User entity.
I have tried unsuccessfully to add 'select new AssessmentDTO() { }' at the end of the LINQ.
Can someone please help me?
A User has zero or more Assessments, every Assessment belongs to exactly one User. This is a standard one-to-many relation with a foreign key.
For your problem, you can use one of the overload of Queryable.GroupBy. First get all Users with their assessments and as a result keep only the most recent assessment:
var UsersWithMostRecentAssessment = dbContext.Users
.GroupBy(dbContext.Assessments, // GroupJoin Users and Assessments
user => user.Id, // From every User take the Id
assessment => assessment.UserId, // From every Assessment take the UserId
// ResultSelector: take each User with its zero or more Assessments to make one new:
(user, assessmentsOfThisUser) => new
{
User = user,
MostRecentAssessment = assessmentsOfThisUser
.OrderByDescending(assessment => assessment.CreatedDate)
.FirstOrDefault(),
// might be null if this User has not assessments at all
})
// Now get the User with its MostRecentAssessment (or null) to make one new:
.Select(userAssessMent => new
{
// Values from the most recent assessment:
Id = userAssessment.MostRecentAssessment.Id,
SymptomId = userAssessment.MostRecentAssessment.SymptomId,
...
// Values from the User:
UserId = userAssessment.User.Id,
FirstName = userAssessment.User.FirstName,
LastName = userAssessment.User.LastName,
...
})
Note: this will go wrong if there are users without assessments, because they won't have a most recent assessment. You can omit these users before the final Select:
.Where(userAssessment => userAssessment.MostRecentAssessment != null)
Or if you want these Users in your endresult:
// Values from the most recent assessment or default if null
Id = userAssessment.MostRecentAssessment.Id ?? 0,
SymptomId = userAssessment.MostRecentAssessment.SymptomId ?? 0,

How and where to use AddRange() method

I want to display related data from second table with each value in first table
i have tried this query
public ActionResult Index()
{
List<EmployeeAtt> empWithDate = new List<EmployeeAtt>();
var employeelist = _context.TblEmployee.ToList();
foreach (var employee in employeelist)
{
var employeeAtt = _context.AttendanceTable
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
Date = g.Key,
Emp_name = employee.EmployeeName,
InTime = g.Any(e => e.ScanType == "I") ? g.Where(e =>
e.ScanType == "I").Min(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent",
OutTime = g.Any(e => e.ScanType == "O") ? g.Where(e =>
e.ScanType == "O").Max(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent"
});
empWithDate.AddRange(employeeAtt);
}
return View(empWithDate);
}
Here is my attendance Table
AttendanceTable
Results
I want to display the shortest time with "I" Column value against each employee and last time with "O" Column value as out time. I think i am not using AddRange() at proper place. Where it should go then?
public partial class TblEmployee
{
public TblEmployee()
{
AttendanceTable = new HashSet<AttendanceTable>();
}
public int EmpId { get; set; }
public string EmployeeName { get; set; }
public virtual ICollection<AttendanceTable> AttendanceTable { get; set; }
}
public partial class AttendanceTable
{
public int Id { get; set; }
public int AttendanceId { get; set; }
public int EmployeeId { get; set; }
public string ScanType { get; set; }
public DateTime DateAndTime { get; set; }
public virtual TblEmployee Employee { get; set; }
}
The actual problem is not related to AddRange(), you need a where clause before GroupBy() to limit attendances (before grouping) to only records related to that specific employee, e.g.
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
...
Depended on your model, it is better to use some kind of ID instead of EmployeeName for comparison if possible.
Also you can use SelectMany() instead of for loop and AddRange() to combine the results into a single list. like this:
List<EmployeeAtt> empWithDate = _context.TblEmployee.ToList()
.SelectMany(employee =>
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
...
})
);
...

Get first object in child collection inside projection

I have the following entities:
public class Mark {
public Int32 Id { get; set; }
public DateTime Created { get; set; }
public virtual ICollection<MarkLocalized> MarksLocalized { get; set; }
} // Mark
public class MarkLocalized {
public Int32 Id { get; set; }
public String Culture { get; set; }
public String Name { get; set; }
public virtual Mark Mark { get; set; }
} // MarkLocalized
Given a culture, for example "en", I need to get all Marks, their localized name for the given culture and map them to a MarkModel:
public class MarkModel {
public Int32 Id { get; set; }
public String Name { get; set; }
} // MarkModel
I tried the following:
Context context = new Context();
context.Marks
.SelectMany(x => x.MarksLocalized, (y, z) =>
new MarkModel {
Id = y.Id,
Name = z.Name,
Slug = z.Slug
});
But I need only the MarksLocalized which culture is equal to the given culture.
How can I do this?
Thank you,
Miguel
You can add Where inside SelectMany:
Context context = new Context();
context.Marks
.SelectMany(x => x.MarksLocalized.Where(x => x.Culture == "en"), (y, z) =>
new MarkModel {
Id = y.Id,
Name = z.Name,
Slug = z.Slug
});
But should be more clear with syntax query:
from m in new Context.Marks
from l in m.MarksLocalized
where l.Culture == "en"
select new MarkModel { m.ID, m.Name, l.Slug }
which should be translated into following query by compiler:
context.Marks
.SelectMany(x => x.MarksLocalized, (m, l) => new { m, l })
.Where(x => x.l.Culture == "en")
.Select(x => new MarkModel { x.m.ID, x.m.Name, x.l.Slug })
which should produce exact same results as first one.

Linq to xml select new PurchaseOrder

I'm trying to get my head arround "Linq to xml" so let me jump right to it. I have this xml :
<?xml version="1.0"?>
<PurchaseOrders>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
</PurchaseOrders>
And then I created som simple objects :
public class PurchaseOrder
{
public string PurchaseOrderNumber { get; set; }
public string OrderDate { get; set; }
public Address BillingAddress { get; set; }
public Address ShippingAddress { get; set; }
public string DeliveryNotes { get; set; }
public List<Item> Items { get; set; }
}
public class Address
{
public string Type { get; set; }
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
}
public class Item
{
public string PartNumber { get; set; }
public string ProductName { get; set; }
public string Quantity { get; set; }
public string USPrice { get; set; }
public string Comment { get; set; }
}
Then i go ahead an make my linq :
var orders = (from order in XDocument.Load(Server.MapPath("/App_Data/Order.xml")).Descendants("PurchaseOrder")
select new PurchaseOrder
{
PurchaseOrderNumber = order.Attribute("PurchaseOrderNumber").Value,
OrderDate = order.Attribute("OrderDate").Value,
BillingAddress = new Address
{
City = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("City").Value,
Country = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Country").Value,
Name = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Name").Value,
State = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("State").Value,
Street = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Street").Value,
Type = "Billing",
Zip = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single().Element("Zip").Value
},
ShippingAddress = new Address
{
City = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("City").Value,
Country = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Country").Value,
Name = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Name").Value,
State = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("State").Value,
Street = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Street").Value,
Type = "Shipping",
Zip = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single().Element("Zip").Value
},
DeliveryNotes = order.Element("DeliveryNotes").Value,
Items = null
});
First of, why does the "order.Element("DeliveryNotes").Value" not exists in the output xml and throws "Object reference not set to an instance of an object" ?
Second, how do I get the "Type" (which is an Attribute) for the address?
Third, how do I make the list of items this one "Items = null" so that I have a fully functional object to work with ?
And finally if the rest of the linq needs som tuning then go ahead and guid me in the right direction ;o)
UPDATE
I've rewrited code so it do not using value property now. It also should populate your Items list.
I've found this topics very useful:
LINQ to XML optional element query
How do you guard for Null Reference exceptions in Linq To Xml?
var doc = XDocument.Load(#"C:\Temp\stackoverflow.xml");
var ordersTemp =
(from order in doc.Descendants("PurchaseOrder") select
new {
order,
BillingAddress = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Billing").Single(),
ShippingAddress = order.Descendants("Address").Where(a => a.Attribute("Type").Value == "Shipping").Single(),
Items = order.Descendants("Items").Descendants("Item")
});
var orders = (from order in ordersTemp select
new PurchaseOrder
{
PurchaseOrderNumber = (string)order.order.Attribute("PurchaseOrderNumber"),
OrderDate = (string)order.order.Attribute("OrderDate"),
BillingAddress = new Address
{
City = (string)order.BillingAddress.Element("City"),
Country = (string)order.BillingAddress.Element("Country"),
Name = (string)order.BillingAddress.Element("Name"),
State = (string)order.BillingAddress.Element("State"),
Street = (string)order.BillingAddress.Element("Street"),
Type = "Billing",
Zip = (string)order.BillingAddress.Element("Zip")
},
ShippingAddress = new Address
{
City = (string)order.ShippingAddress.Element("City"),
Country = (string)order.ShippingAddress.Element("Country"),
Name = (string)order.ShippingAddress.Element("Name"),
State = (string)order.ShippingAddress.Element("State"),
Street = (string)order.ShippingAddress.Element("Street"),
Type = "Shipping",
Zip = (string)order.ShippingAddress.Element("Zip")
},
DeliveryNotes = (string)order.order.Element("DeliveryNotes"),
Items = (from item in order.Items select new Item
{
PartNumber = (string)item.Attribute("PartNumber"),
ProductName = (string)item.Element("ProductName"),
Quantity = (string)item.Element("Quantity"),
USPrice = (string)item.Element("USPrice"),
Comment = (string)item.Element("Comment")
}).ToList()
});

Resources