LINQ - Left Join And Group By - linq

I have a problem. This is my products class
public int ProductID { get; set; }
public string ProductName { get; set; }
public int CategoryID { get; set; }
public decimal Price { get; set; }
public int CompanyID { get; set; }
public string QuantityPerUnit { get; set;
And this is OrderDetail Class
public int DetailID { get; set; }
public int OrderID { get; set; }
public int ProductID { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public bool Discount { get; set; }
so ı wrote this query in mssql. So, how can I write this query on linq ?
select Products.ProductID, Sum(OrderDetails.Quantity) as 'NumberOfOrdered' from Products
left join OrderDetails on Products.ProductID = OrderDetails.ProductID
group by Products.ProductID
order by Products.ProductID
I Wrote this query but it doesn't work.
from p in _context.Products
join d in _context.OrderDetails on p.ProductsID equals d.ProductsID into t1
from d1 in t1.DefaultIfEmpty()
group new { p, d } by new { p.ProductsID, d.ProductsID } into g
orderby g.Key.ProductsID
select new ProductsOrderDetails
{
ProductsID = g.Key.ProductsID,
QuantityToplam = g.(x=>x.d.Quantity)
}).ToList();

So you have Products, and OrderDetails. Every Product has zero or more OrderDetails, every OrderDetail is the detail of exactly one Product, namely the Product that OrderDetail.ProductId belongs to.
Requirement: From every Product, give me the Id and the sum of the Quantities of all its OrderDetails
Whenever you need the sequence of items, every item with its sub-items, like Schools with their Students, Authors with their Books, Orders with their OrderDetails, consider using one of the overloads of Enumerable.GroupJoin
In this case, I don't want a simple Product with its OrderDetails, I want to compose a special result. For every Product I want the Id and the sum of Quantities of all its OrderDetauls. Therefore I use the Overload with a parameter resultSelector.
var result = dbContext.Products.GroupJoin( // GroupJoin Products
dbContext.OrderDetails, // with Orderdetails
product => product.Id, // From every Product take the Id
orderDetail => orderDetail.ProductId, // From every OrderDetail that the foreign key
// resultSelector: for every Product and all its zero or more OrderDetails
// create one new object
(product, orderDetailsOfThisProduct) => new
{
Id = product.Id,
QuantitiesToPlam = orderDetailsOfThisProduct
.Select(orderDetail => orderDetail.Quantity)
.Sum(),
});
For property QuantitiesToPlam: for every OrderDetail that belongs to this Product: take the value of property Quantity, and Sum them.

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,

EF Core - many queries sent to database for subquery

Using EF Core 2.2.2, I have a table in my database which is used to store notes for many other tables. In other words, it's sortof like a detail table in a master-detail relationship, but with multiple master tables. Consider this simplified EF Model:
public class Person
{
public Guid PersonID { get; set; }
public string Name { set; set; }
}
public class InvoiceItem
{
public Guid InvoiceItemID { get; set; }
public Guid InvoiceID { get; set; }
public string Description { get; set; }
}
public class Invoice
{
public Guid InvoiceID { get; set; }
public int InvoiceNumber { get; set; }
public List<Item> Items { get; set; }
}
public class Notes
{
public Guid NoteID { get; set; }
public Guid NoteParentID { get; set; }
public DateTime NoteDate { get; set; }
public string Note { get; set; }
}
In this case, Notes can store Person notes or Invoice notes (or InvoiceItem notes, though let's just say that the UI doesn't support that).
I have query methods set up like this:
public IQueryable<PersonDTO> GetPersonQuery()
{
return from p in Context.People
select new PersonDTO
{
PersonID = p.PersonID,
Name = p.Name
};
}
public List<PersonDTO> GetPeople()
{
return (from p in GetPersonQuery()
return p).ToList();
}
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber
};
}
public List<InvoiceDTO> GetInvoices()
{
return (from i in GetInvoiceQuery()
return i).ToList();
}
These all work as expected. Now, let's say I add InvoiceItems to the Invoice query, like this:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList()
};
}
That also works great, and issues just a couple queries. However, the following:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in Context.Notes
where i.InvoiceID = n.NoteParentID
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};
}
sends a separate query to the Note table for each Invoice row in the Invoice table. So, if there are 1,000 invoices in the Invoice table, this is sending something like 1,001 queries to the database.
It appears that the Items subquery does not have the same issue because there is an explicit relationship between Invoices and Items, whereas there isn't a specific relationship between Invoices and Notes (because not all notes are related to invoices).
Is there a way to rewrite that final query, such that it will not send a separate note query for every invoice in the table?
The problem is indeed the correlated subquery versus collection navigation property. EF Core query translator still has issues processing such subqueries, which are in fact logical collection navigation properties and should have been processed in a similar fashion.
Interestingly, simulating collection navigation property with intermediate projection (let operator in LINQ query syntax) seems to fix the issue:
var query =
from i in Context.Invoices
let i_Notes = Context.Notes.Where(n => i.InvoiceID == n.NoteParentID) // <--
select new InvoiceDTO
{
InvoiceID = i.InvoiceID,
InvoiceNumber = i.InvoiceNumber,
Items = (from ii in i.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in i_Notes // <--
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};

Selecting single element from each group that cointains maximum DateTime value

model :
public class ReferenceParameterHistory
{
[Key]
public int IDReferenceParameterHistory { get; set; }
public double Value { get; set; }
public string Value_S { get; set; }
public DateTimeOffset CreatedAt { get; set; }
[Required]
public bool IsStable { get; set; }
public int? IDReference { get; set; }
public Reference Reference { get; set; }
[Required]
public int IDParameterTemplate { get; set; }
public ParameterTemplate ParameterTemplate { get; set; }
}
My code in ASP.NET core controller :
[HttpGet]
public async Task<IActionResult> GetReferenceParameterHistory(int? IDparameterTemplate,
int? IDreference,
DateTimeOffset? startDate,
DateTimeOffset? endDate,
bool latestOnly)
{
try
{
IQueryable<ReferenceParameterHistory> query = _context.ReferenceParameterHistory.OrderByDescending(rph => rph.IDReferenceParameterHistory);
if (IDparameterTemplate != null && IDparameterTemplate > 0)
query = query.Where(rph => rph.IDParameterTemplate == IDparameterTemplate);
if (IDreference != null && IDreference > 0)
query = query.Where(rph => rph.IDReference == IDreference);
if (startDate != null)
query = query.Where(rph=> rph.CreatedAt >= startDate);
if (endDate != null)
query = query.Where(rph => rph.CreatedAt <= endDate);
if (latestOnly)
{
// I tried this but it doesnt compile and I don't have idea how to solve this ....
//query = (from rph in query
// group rph by rph.IDParameterTemplate
// into groups
// where groups.Max(rph => rph.CreatedAt)
// select groups.Key);
}
var referenceParameterHistory = await query.AsNoTracking().ToListAsync();
if (referenceParameterHistory.Any())
return new ObjectResult(referenceParameterHistory);
return new NotFoundResult();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new StatusCodeResult(500);
}
}
I have a database table based on that ReferenceParameterHistory model class. I want to group records exctracted from that table by IDParameterTemplate and from each group I need to extract records that have the highest value in CreatedAt column (latest records). So each group contains many recods but I need to get only these with max value in CreatedAt column. The result should be IEnumerable of ReferenceParameterHistory since I store that query in an IQueryable variable and then send it to SQL Server to process the query. Commented code in my example is just what I tried but I don't know how to do that.
How can I solve that problem ?
You reused the variable rph inside the lambda.
How to select only the records with the highest date in LINQ
query = (from rph in query
group rph by rph.IDParameterTemplate into g
select g.OrderByDescending(t=>t.CreatedAt).FirstOrDefault());

Linq sort by products but display cateogires

So I have an issue where the code is Selecting a Category
and I need to change sort order to sort by product.Name Then By category.name.
But the problem is I still want to select a category, but how do I
sort by product.name first without adding an extra join or select.
from category in categories
select category.name
orderby category.Name //orderby category name
Later on in view I loop foreach(category.products) and pass in category.product[i] to view to display
But sort order is wrong, order is always by Category.Name
How do I sort by Product.Name first and then by Category.Name?
Will SelectMany help? Again I do not want to disrupt the select
part of my query, just the Order by stuff.
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}
class Category
{
public string Name { get; set; }
public int ID { get; set; }
}
// Specify the first data source.
static List categories = new List()
{
new Category(){Name="Beverages", ID=001},
new Category(){ Name="Condiments", ID=002},
new Category(){ Name="Vegetables", ID=003},
new Category() { Name="Grains", ID=004},
new Category() { Name="Fruit", ID=005}
};
// Specify the second data source.
static List products = new List()
{
new Product{Name="Cola", CategoryID=001},
new Product{Name="Tea", CategoryID=001},
new Product{Name="Mustard", CategoryID=002},
new Product{Name="Pickles", CategoryID=002},
new Product{Name="Carrots", CategoryID=003},
new Product{Name="Bok Choy", CategoryID=003},
new Product{Name="Peaches", CategoryID=005},
new Product{Name="Melons", CategoryID=005},
};
Oh, I saw you query, it was badly formatted.
You need to order by Product.Name group by Category.Name
//LinqPad code
class Category
{
public string Name { get; set; }
public int ID { get; set; }
public List<Product> products { get; set;}
}
class Product
{
public string Name { get; set; }
public int ID { get; set; }
}
void Main()
{
// Specify the second data source.
List<Product> BevProducts = new List<Product>()
{
new Product{Name="ZCola"},
};
// Specify the third data source.
List<Product> CondProducts = new List<Product>()
{
new Product{Name="Sugar"},
};
// Specify the first data source.
List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001, products=BevProducts},
new Category(){ Name="Condiments", ID=002, products=CondProducts},
};
var sortedCats = categories.OrderBy(c => c.ID).ToList();
foreach (var category in sortedCats)
{
//display category
System.Console.Out.WriteLine(category.Name);
//Assuming each category contains exactly one product in the list ie 1 to 1 relationship
// how can I sort by product.Name, so if ZCola comes before Sugar, ZCola's Category (Beverages) sorts before Condiments
// so in this Case Beverages, Condiments is the right order, because ZCola comes after Sugar.
var sortedProductsPerCategory = category.products.OrderBy(p => p.Name).ToList();
foreach (var product in sortedProductsPerCategory)
{
//display product
System.Console.Out.WriteLine(" " + product.Name);
}
}
}
class Category
{
public string Name { get; set; }
public int ID { get; set; }
public List<Product> products { get; set;}
}
class Product
{
public string Name { get; set; }
public int ID { get; set; }
}
void Main()
{
// Specify the second data source.
List<Product> BevProducts = new List<Product>()
{
new Product{Name="ZCola"},
};
// Specify the third data source.
List<Product> CondProducts = new List<Product>()
{
new Product{Name="Sugar"},
};
// Specify the first data source.
List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001, products=BevProducts},
new Category(){ Name="Condiments", ID=002, products=CondProducts},
};
var sortedCats = categories.OrderBy(c => c.products.Min(p => p.Name)).ToList();
foreach (var category in sortedCats)
{
//display category
System.Console.Out.WriteLine(category.Name);
//Assuming each category contains exactly one product in the list ie 1 to 1 relationship
// how can I sort by product.Name, so if ZCola comes before Sugar, ZCola's Category (Beverages) sorts before Condiments
// so in this Case Beverages, Condiments is the right order, because ZCola comes after Sugar.
var sortedProductsPerCategory = category.products.OrderBy(p => p.Name).ToList();
foreach (var product in sortedProductsPerCategory)
{
//display product
System.Console.Out.WriteLine(" " + product.Name);
}
}
}

Resources