Ordering collection within group partitions using LINQ - c#

I have simple type Question:
public class Question
{
public string[] Tags { get; set; }
public DateTime Created { get; set; }
}
While I have a list of questions, I need to filter them along list of tags (called filters). The questions which have the most tags matched by the filters list, should be placed higher in the result collection. I wrote expression for that:
public IList<Question> GetSimiliar(IList<Questions> all, string[] filters)
{
var questions = all.Select(
x => new
{
MatchedTags = x.Tags
.Count(tag => filters.Contains(tag)),
Question = x
})
.Where(x => x.MatchedTags > 0)
.OrderByDescending(x => x.MatchedTags)
.Select(x => x.Question);
return questions.ToList();
}
Now I need a support for such situation, where I have more than one question with the same quantity of matched tags. Such questions should be further sorted by creation date (from newest to oldest).
Example of what I want:
filter: tags = [a,b,c]
collection of questions to be filtered:
q1 { tags = [a], created = 1939 }
q2 { tags = [b], created = 1945 }
q3 { tags = [a,b,c], created = 1800 }
q4 { tags = [a,b], created = 2012 }
q5 { tags = [z], created = 1999 }
result - the sorted collection:
q3
q4
q2
q1
How to do that using linq ?

Now I need a support for such situation, where I have more than one question with the same quantity of matched tags. Such questions should be further sorted by creation date (from newest to oldest).
Use ThenBy or ThenByDescending to further sort your query. Use these methods to break ties in prior ordering.
.OrderByDescending(x => x.MatchedTags)
.ThenByDescending(x => x.Question.Created)
.Select(x => x.Question);

The 101 Linq Samples page has a nested grouping example. This sample uses group by to partition a list of each customer's orders, first by year, and then by month:
public void Linq43()
{
List<Customer> customers = GetCustomerList();
var customerOrderGroups =
from c in customers
select
new
{
c.CompanyName,
YearGroups =
from o in c.Orders
group o by o.OrderDate.Year into yg
select
new
{
Year = yg.Key,
MonthGroups =
from o in yg
group o by o.OrderDate.Month into mg
select new { Month = mg.Key, Orders = mg }
}
};
ObjectDumper.Write(customerOrderGroups, 3);
}

Related

How can I get the best seller by product group using LINQ

I need to retrieve the name, product type and sum of the seller with the highest sales in each product type using linq and return the list to a view.
{
public ActionResult Query2()
{
// this part gets me everything into one model type
List<QueryViewModel> result = db.SaleProducts
.GroupBy(prod => prod.Type)
.SelectMany(sale => sale.Select(
row => new QueryViewModel
{
Seller = row.Sale.User.Name,
ProductType = row.Type,
Sales = (double)sale.Where(x => x.Sale.UserId == row.Sale.UserId && x.Type.Equals(row.Type)).Sum(price => price.Price)
}
)).Distinct().ToList<QueryViewModel>();
// this gives me the best per product type but i cant get the seller name
List<QueryViewModel> filter = (from res in result
group res by res.ProductType into prodGroup
select new QueryViewModel
{
ProductType = prodGroup.Key,
Sales = prodGroup.Max(x => x.Sales)
}).ToList<QueryViewModel>();
// this is really just to get the seller name at this point
List<QueryViewModel> something = (from res in result
join f in filter on res.Sales equals f.Sales
select new QueryViewModel
{
Seller = res.Seller,
ProductType = res.ProductType,
Sales = res.Sales
}).ToList<QueryViewModel>();
return View(something);
}
}
It should return a list of QueryViewModel(name, product type, total sales).
It does return that, but this seems horribly messy and I'm not understanding LINQ enough to clear this up.
Is there a better cleaner way to achieve my desired output?
The first grouping should be by type and Seller.
Something like this should get you going:
Quote_Masters.GroupBy(qm => new { qm.OrderTypeId, qm.SalesRepEmployeeId })
.Select(x => new { Name = x.Key.SalesRepEmployeeId, x.Key.OrderTypeId, Total = x.Sum(qm => qm.Total_Price) })
.OrderByDescending(x => x.Total).GroupBy(x => x.OrderTypeId).Select(x => x.FirstOrDefault())
Just change out your appropriate tables and fields.
Edit: Your select to QueryViewModel would be the new {Name = x.Key.SalesRepEmployeeId, x.Key.OrderTypeId, Total = x.Sum(qm => qm.Total_Price)}

Selecting unique values of different columns using LINQ

I have a table (orders for ex) which has Multiple Columns.
products categories subcategories
--------------------------------------
prod1 cat1 sub1
prod1 cat2 sub2
prod2 cat3 sub6
prod1 cat1 sub1
prod5 cat2 sub8
prod2 cat1 sub1
prod1 cat7 sub3
prod8 cat2 sub2
prod2 cat3 sub1
Now I can write three different queries to get distinct values
var prod = (from p in _context.orders select p.products).ToList().Distinct();
similarly I can write it for others.
Now I need to get the distinct values of each column in a single query for which the result needs to look like
products categories subcategories
--------------------------------------
prod1 cat1 sub1
prod2 cat2 sub2
prod5 cat3 sub6
prod8 cat7 sub8
sub3
My ClassType for unique fields looks like this
public class UniqueProductFields
{
public IEnumerable<string> Products { get; set; }
public IEnumerable<string> Categories { get; set; }
public IEnumerable<string> Subcategories { get; set; }
}
Not sure how to do this in an efficient manner so that I dont have to write three methods. The table is in the database (hence the need for optimization)
Thanks!
Is it an absolutely unchangeable requirement to use Linq? Why do you need it to be returned in a single query?
Suggestion: Use SQL. It can be done in a single query but you won't like the query. I'm assuming SQL Server (can be done differently for other DBMSes).
WITH V AS (
SELECT DISTINCT
V.*
FROM
Orders O
CROSS APPLY (
VALUES (1, O.Products), (2, O.Categories), (3, O.Subcategories)
) V (Which, Value)
),
Nums AS (
SELECT
Num = Row_Number() OVER (PARTITION BY V.Which ORDER BY V.Value),
V.Which,
V.Value
FROM
V
)
SELECT
Products = P.[1],
Categories = P.[2],
Subcategories = P.[3]
FROM
Nums N
PIVOT (Max(N.Value) FOR N.Which IN ([1], [2], [3])) P
;
See this working at db<>fiddle
Output:
Products Categories Subcategories
-------- ---------- -------------
prod1 cat1 sub1
prod2 cat2 sub2
prod5 cat3 sub3
prod8 cat7 sub6
null null sub8
If you are bound and determined to use Linq, well, I can't help you with the query-style syntax. I only know the C# code style syntax, but here's a stab at that. Unfortunately, I don't think this will do you any good, because I had to use some pretty funky stuff to make it work. It uses essentially the same technique as the SQL query above, only, there's no equivalent of PIVOT in Linq and there's no real natural row object other than a custom class.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program {
public static void Main() {
var data = new List<Order> {
new Order("prod1", "cat1", "sub1"),
new Order("prod1", "cat2", "sub2"),
new Order("prod2", "cat3", "sub6"),
new Order("prod1", "cat1", "sub1"),
new Order("prod5", "cat2", "sub8"),
new Order("prod2", "cat1", "sub1"),
new Order("prod1", "cat7", "sub3"),
new Order("prod8", "cat2", "sub2"),
new Order("prod2", "cat3", "sub1")
};
int max = 0;
var items = data
.SelectMany(o => new List<KeyValuePair<int, string>> {
new KeyValuePair<int, string>(1, o.Products),
new KeyValuePair<int, string>(2, o.Categories),
new KeyValuePair<int, string>(3, o.Subcategories)
})
.Distinct()
.GroupBy(d => d.Key)
.Select(g => {
var l = g.Select(d => d.Value).ToList();
max = Math.Max(max, l.Count);
return l;
})
.ToList();
Enumerable
.Range(0, max)
.Select(i => new {
p = items[0].ItemAtOrDefault(i, null),
c = items[1].ItemAtOrDefault(i, null),
s = items[2].ItemAtOrDefault(i, null)
})
.ToList()
.ForEach(row => Console.WriteLine($"p: {row.p}, c: {row.c}, s: {row.s}"));
}
}
public static class ListExtensions {
public static T ItemAtOrDefault<T>(this List<T> list, int index, T defaultValue)
=> index >= list.Count ? defaultValue : list[index];
}
public class Order {
public Order(string products, string categories, string subcategories) {
Products = products;
Categories = categories;
Subcategories = subcategories;
}
public string Products { get; set; }
public string Categories { get; set; }
public string Subcategories { get; set; }
}
I suppose that we could swap this
.Select(i => new {
p = items[0].ItemAtOrDefault(i, null),
c = items[1].ItemAtOrDefault(i, null),
s = items[2].ItemAtOrDefault(i, null)
})
for this:
.Select(i => new Order(
items[0].ItemAtOrDefault(i, null),
items[1].ItemAtOrDefault(i, null),
items[2].ItemAtOrDefault(i, null)
))
Then use that class's properties in the output section.
As far as i know, you won't be able to do it in a single query. Before thinking how would you do it with C# think how would you do it in SQL; I might be wrong but to me you'll be writing 3 querys anyway.
If you notice some performance issues and this is your actual code:
var prod = (from p in _context.orders select p.products).ToList().Distinct();
You may want to start by removing the .ToList() extension method beacuse that is retrieveng all records to memory and only after that the distinction is applied.
That's because your query expression (from p in ...) returns an IQueryable and calling .ToList() on it makes it IEnumerable. force the current formed SQL query to run and bring the results to memory.
The difference in this case is: Deferred execution
See: https://www.c-sharpcorner.com/UploadFile/rahul4_saxena/ienumerable-vs-iqueryable/

Entity Framework - GroupBy then OrderBy

I have a group of records that includes PatientID and Appointment Date.
I would like to group them by PatientID and order the groups by the oldest appointment in each PatientID group. Like this...
ID ApptDate
----------------------
3 2/5/2005 (oldest compared to all groups , so group "ID = 3" is sorted first)
3 5/10/2006
3 6/2/2010
1 8/5/2007
1 9/1/2015
2 6/15/2009
2 9/19/2009
I'm pretty sure I need to use grouping first to obtain the oldest date value for each ID and then order by that value but I am stuck understanding how the two functions will work together.
var query = from a in db.Appointments
group a by new { a.Id, a.ApptDate} into pa
select new {
ID = pa.Key.Id,
Date = pa.Min(x =>x.ApptDate) ...
... but I crash and burn at this point.
Any help appreciated.
I modified a bit Tanveer Badar's answer to return a row for each entity.
Create a class for your return data
public class ReturnType
{
public int ID { get; set; }
public DateTime AppDate { get; set; }
}
Get a group of IDs and enumerable of ordered dates
var result = db.Appointment
.GroupBy(a => a.Id)
.OrderBy(a => a.Min(n => n.Date))
.Select(a => new { ID = a.Key, AppDates = a.OrderBy(na =>
na.Date).Select(ne => ne.Date) })
.ToList();
Then flatten the returned list. I tried with SelectMany but with no success.
var results = new List<ReturnType>();
foreach (var a in result)
{
foreach (var date in a.AppDates)
{
var returnType = new ReturnType
{
ID = a.ID,
AppDate = date
};
results.Add(returnType);
}
}
You requirements are as follows (though the question does not mention one of them, but it is apparent from how data is laid out):
Group based on patient ID.
Sort groups based on oldest appointment date in each group.
Sort data within a group based on appointment date.
var query = records.GroupBy(r => r.PatientId) // Group patients
.OrderBy(g => g.Min(r => r.AppointmentDate)) // sort groups by oldest record in each
.Select(g =>
new
{
PatientId = g.Key,
Records = g.OrderBy(r => r.AppointmentDate) // sort records within a group by oldest
});
In my case it works fine...
var query = from a in db.Appointments
group a by new { a.Id, a.ApptDate} into pa
select new {
ID = pa.Key.Id,
Date = pa.Max(x =>x.ApptDate)
}).Select(x=>new {
x.ID,x.Date
}).ToList();
db.Appointments.GroupBy(new {ID = item.ID, ApptDate = item.ApptDate}).OrderByDescending(item => item.ApptDate)

Return elements of the input sequence of a linq sum

Let's say I have a simple list structured as the following 2 column table:
letter|number
a|1
a|7
b|2
b|5
I would like to have a Linq query that groups on the column 'letter', sums the grouped elements of column 'number', and additionally returns the summed elements in an array. Which would result in the following table:
a | 8 | {1,7}
b | 7 | {2,5}
What I have:
public class GroupedRow {
public int number { get; set; }
public string letter { get; set; }
public int[] elements { get; set; }
}
And in the program:
List<GroupedRow> listfromquery = numberletterlist.GroupBy(x => x.letter)
.Select(grp => new GroupedRow() {
number = grp.Sum(x => x.number)
letter = grp.key.letter
// elements=
};
Even though I see other ways of doing this, I would like to do this in 1 linq query (if possible), or another simple fast way.
grp is the group of elements and it implements IEnumerable<T>. Simply select number from it and then convert it into an array like this:
List<GroupedRow> listfromquery =
numberletterlist.GroupBy(x => x.letter)
.Select(grp => new GroupedRow()
{
number = grp.Sum(x => x.number),
letter = grp.Key,
elements = grp.Select(x => x.number).ToArray()
}).ToList();
What about this?
elements = grp.Select(x => x.number).ToArray()

Group By Query with Entity Framework

In my application I have Movements associated with a category.
I want a list of the most frequent category.
My objects are:
Category: catId, catName
Movement: Movid, movDate, movMount, catId
I think it would have to raise it with a "Group By" query (grouping by catId and getting those more)
(Im using Entity Framework 6 in c#)
From already thank you very much!
IMPORTANT: Entity Framework 7 (now renamed to Entity Framework Core 1.0) does not yet support GroupBy() for translation to GROUP BY in generated SQL. Any grouping logic will run on the client side, which could cause a lot of data to be loaded.
https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-entity-framework-core-rc2
group the movements by category and select catid and count.
join this result with category to get the name and then descending sort the results on count.
var groupedCategories = context.Movements.GroupBy(m=>m.catId).Select(g=>new {CatId = g.Key, Count = g.Count()});
var frequentCategories = groupedCategories.Join(context.Categories, g => g.CatId, c => c.catId, (g,c) => new { catId = c.catId, catName = c.catName, count = g.Count }).OrderByDescending(r => r.Count);
foreach (var category in frequentCategories)
{
// category.catId, category.catName and category.Count
}
i hope this help:
var query = dbContext.Category.Select(u => new
{
Cat = u,
MovementCount = u.Movement.Count()
})
.ToList()
.OrderByDescending(u => u.MovementCount)
.Select(u => u.Cat)
.ToList();
I resolved the problem!
I used the proposal by "Raja" solution (Thanks a lot!).
This return a collection composed of "Category" and "Count". I Change it a bit to return a list of Categories.
var groupedCategories = model.Movement.GroupBy(m => m.catId).Select(
g => new {catId= g.Key, Count = g.Count() });
var freqCategories= groupedCategories.Join(model.Category,
g => g.catId,
c => c.catId,
(g, c) => new {category = c, count = g.Count}).OrderByDescending(ca => ca.count).Select(fc => fc.category).ToList ();
you just need to use navigation property on category simply, you have a navigation property on category contains all related Movement, i call it Movements in following query. you can write your query like this, with minimum of connection with DB.
class Cat
{
public Guid catId { get; set; }
public string catName { get; set; }
public IEnumerable<Movement> Movements { get; set; }
public int MovementsCount { get { return Movements.Count(); } }
}
var Categories = category.Select(u => new Cat()
{
u.catId,
u.catName,
Movements = u.Movements.AsEnumerable()
}).ToList();
var CategoriesIncludeCount = Categories.OrderBy(u => u.MovementsCount).ToList();

Categories