Linq query to group by with a limit - c#

I have a simple table in the following structure.
I want to write a LINQ expression to fetch only 5 records always. This 5 should be "Gold" if available. Otherwise add "Bronze" to make it 5. If it still not 5 then add "Silver" to the list. But total results returned should be 5. It should be good in terms of performance.
I tried basic linq but no luck. Any help is highly appreciated.
Class :
public class Option {
public int Id {get;set;
public string Name {get;set;}
public string Priority {get;set;}
}
dbContext.Options would create a connection to database table through ORM and we can apply linq expressions there.
Attempt : dbContext.Options.OrderByDescending(o => o.Priority).GroupBy(a => a.Priority)
this returns grouped result by priority. But i want to include the logic i needed inside this expression.

You want to assign a sort value to each string so that they are ordered. You can do this by assigning the integer 0 to Gold, 1 to Bronze, and 2 for Silver (other).
You then use Take to just get the first 5 records.
// ordered by gold, bronze, silver
var result = dbContext.Options
.OrderBy(o => o.Priority == "Gold" ? 0 : o.Priority == "Bronze" ? 1 : 2)
.Take(5)
.ToList();

It should be good in terms of performance.
Then you could consider using raw SQL to filter the records in the original query that is executed against the database, e.g.:
dbContext.Options.SqlQuery("SELECT TOP 5 * FROM [Option] ORDER BY CASE WHEN [Priority] = 'Gold' THEN 1 WHEN [Priority] = 'Bronze' THEN 2 WHEN [Priority] = 'Silver' THEN 3 ELSE 4 END").ToArray();
Maximal performance and LINQ seldom go hand in hand when it comes to querying databases.

Let Priority be an enum, orderby it and take 5.
class Option
{
public int Id { get; set; }
public string Name { get; set; }
public Priority Priority { get; set; }
}
enum Priority
{
Gold = 0,
Silver,
Bronze
}
static void Main(string[] args)
{
var list = new List<Option>()
{
new Option { Id = 1, Name = "Bob", Priority = Priority.Gold },
new Option { Id = 2, Name = "Rob", Priority = Priority.Gold },
new Option { Id = 2, Name = "David", Priority = Priority.Bronze },
new Option { Id = 2, Name = "Adam", Priority = Priority.Bronze },
new Option { Id = 2, Name = "Jack", Priority = Priority.Silver },
new Option { Id = 2, Name = "Josh", Priority = Priority.Silver },
new Option { Id = 2, Name = "Peter", Priority = Priority.Silver },
new Option { Id = 2, Name = "Max", Priority = Priority.Silver },
new Option { Id = 2, Name = "Steve", Priority = Priority.Silver },
};
var newList = list.OrderBy(l => l.Priority).Take(5);
}

List<Option> top5 = participants.OrderBy(part => {
switch(part.Priority) {
case "Gold": return 1;
case "Bronze": return 2;
case "Silver": return 3;
default: return 4;
}
}).Take(5).ToList();
If the list is shorter than 5, just order them, if that is needed.

See code below :
List<string> rank = new List<string>() { "Gold", "Bronze","Silver" };
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("Priority", typeof(string));
dt.Rows.Add(new object[] { 9, "Steve", "Silver" });
dt.Rows.Add(new object[] { 8, "Max", "Silver" });
dt.Rows.Add(new object[] { 7, "Peter", "Silver" });
dt.Rows.Add(new object[] { 6, "Josh", "Silver" });
dt.Rows.Add(new object[] { 5, "Jack", "Bronze" });
dt.Rows.Add(new object[] { 4, "Adam", "Bronze" });
dt.Rows.Add(new object[] { 3, "David", "Gold" });
dt.Rows.Add(new object[] { 1, "Bob", "Gold" });
dt.Rows.Add(new object[] { 2, "Rob", "Gold" });
DataRow[] results = dt.AsEnumerable().OrderBy(x => rank.IndexOf(x.Field<string>("Priority"))).Take(5).ToArray();

Related

I want to create multiple objects in list<object> and remove them with the desired id

I want to delete it with the desired id.
Can you tell me if there is another way to do it with Linq?
I think I can use Select() and Where(), but it doesn't work.
var list = new List<object>() { };
list.Add(new { id = 3, const = "22"});
list.Add(new { id = 4, const = "22"});
list.Add(new { id = 6, const = "22"});
list.Add(new { id = 2, const = "22"});
list.Add(new { id = 1, const = "22"});
//example
list.Remove(new { id = 2, const = "22" });
first of all const is a keyword in C#, so either avoid using it as member name, or escape it by decorating with #.
since your list item type is object, you need to cast them as dynamic to allow access the 'unknown' id -> this a very vague approach, but it works.
= in C# means: set the value of... so if you want to compare equality, you need ==
putting this altogether:
var list = new List<object>();
list.Add(new { id = 4, #const = "22" });
list.Add(new { id = 4, #const = "22" });
list.Add(new { id = 6, #const = "22" });
list.Add(new { id = 2, #const = "22" });
list.Add(new { id = 1, #const = "22" });
list.RemoveAll(i => ((dynamic)i).id == 2);
will work.
Consider using an own Type for your list items, anonymous types should only be used 'locally' in Queries like GroupBy.
you could try:
list.RemoveAt(list.FindIndex(item => item.id == 1));

Filter hierarchical list retaining parent using LINQ

I have a model class which looks something like this:
public class Employee
{
public int Id {get;set;}
public int ParentId {get;set;}
public string Name{get;set;}
public string Designation {get;set;}
}
using which I simulated a list:
new List<Employee> employees
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" },
new Employee{Id = 3, ParentId = 1, Name = "C", Designation = "Manager" },
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead" },
new Employee{Id = 5, ParentId = 3, Name = "E", Designation = "Lead" },
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer" },
new Employee{Id = 7, ParentId = 4, Name = "G", Designation = "Developer" },
new Employee{Id = 8, ParentId = 5, Name = "H", Designation = "Developer" }
};
Well I need to write a LINQ query to filter the above list so that even the parent objects(if present) are retained during the filtering. I could not quiet wrap my head around the retainment of the parent part where I always end up getting it wrong.
To make it more clear this is what is the expected filtered list in case the filter search criteria are the Ids 6 and 7:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" },
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead" }
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer" },
new Employee{Id = 7, ParentId = 4, Name = "G", Designation = "Developer" }
}
and if the Id to filter is 8:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 3, ParentId = 1, Name = "C", Designation = "Manager" },
new Employee{Id = 5, ParentId = 3, Name = "E", Designation = "Lead" },
new Employee{Id = 8, ParentId = 5, Name = "H", Designation = "Developer" }
}
and if the Id to filter is 2:
{
new Employee{Id = 1, ParentId = 0, Name = "A", Designation = "CEO" },
new Employee{Id = 2, ParentId = 1, Name = "B", Designation = "Manager" }
}
You can implement a help method, EmployeeAndBosses which returns given employee and all the parents:
private static IEnumerable<Employee> EmployeeAndBosses(Employee value,
IEnumerable<Employee> collection) {
for (Employee item = value;
item != null;
item = collection.FirstOrDefault(x => x.ParentId == item.Id))
yield return item;
}
then you can filter topmost employee in the hierarchy, and add their bosses then:
HashSet<int> ids = new HashSet<int>() {
6, 7
};
var result = employees
.Where(item => ids.Contains(item.Id)) // topmost
.SelectMany(item => EmployeeAndBosses(item, employees)) // topmost and parents
.GroupBy(item => item.Id) // Duplicates (e.g. CEO) removing
.Select(group => group.First()); //
Edit: If you have a huge collection(s) and that's why FirstOrDefault and GroupBy are bad choice, you can implement Bread First Search:
private static IEnumerable<Employee> MyFilter(IEnumerable<Employee> list,
IEnumerable<int> idsToFind) {
Dictionary<int, Employee> stuff = list
.ToDictionary(item => item.Id, item => item);
HashSet<int> ids = new HashSet<int>(idsToFind);
HashSet<int> completed = new HashSet<int>();
Queue<Employee> agenda = new Queue<Employee>(list.Where(item => ids.Contains(item.Id)));
while (agenda.Count > 0) {
Employee current = agenda.Dequeue();
if (null != current && completed.Add(current.Id)) {
yield return current;
if (stuff.TryGetValue(current.ParentId, out current))
agenda.Enqueue(current);
}
}
}
As some comments seem to be quite... Subjective... Here is a simple (but somewhat inefficient) extension that handles your requirements like a charm:
(assuming you'll never hire an employee as a boss to another employee that in turn is their boss, but such madness would probably break the company quicker than it breaks the query)
public static IEnumerable<Employee> FindByIdsAndIncludeParents(this IEnumerable<Employee> employees, params int[] targetIds)
=> employees
.Where(r => targetIds.Contains(r.Id))
.SelectMany(r => employees.FindByIdsAndIncludeParents(r.ParentId).Append(r))
.Distinct();
As some are not quite as keen of exchanging this quite expensive operation for the mere beauty of it, we could trade in some beauty for speed using a dictionary as entry point for instant access to the appended boss-search:
public static IEnumerable<Employee> FindFriendsFaster(this IEnumerable<Employee> employees, params int[] targetIds)
=> employees
.ToDictionary(e => e.Id, e => e)
.FindWithBossFriend(targetIds)
.Distinct();
public static IEnumerable<Employee> FindWithBossFriend(this IDictionary<int, Employee> hierarchy, params int[] targetIds)
=> targetIds
.Where(eId => hierarchy.ContainsKey(eId))
.Select(eId => hierarchy[eId])
.SelectMany(e => hierarchy.FindWithBossFriend(e.ParentId).Append(e));
As you might be able to spot, I personally can't seem to be able to trade in any more of my dignity for the possible removal of that last .Distinct(), but there are rumors going around some would be.

MongoDB C# Query: Filter + Aggregation + Checking if value is true/false

I need to perform a pretty complex MongoDB query and I'm having a really hard time being able to narrow the entire thing down to one query, although I do think it's doable I don't really have enough experience with MongoDB to get it quite right and I'd really appreciate some help.
My class looks something like this:
class MyItem
{
public int ID { get; set; }
public int Value { get; set; }
public bool IsDropped { get; set; }
}
I need to be able to select the min value for each ID that isn't dropped. For example:
items.Add(new MyItem() { ID = 1, Value = 100, IsDropped = true });
items.Add(new MyItem() { ID = 1, Value = 150, IsDropped = false });
items.Add(new MyItem() { ID = 1, Value = 200, IsDropped = true });
items.Add(new MyItem() { ID = 2, Value = 100, IsDropped = false });
items.Add(new MyItem() { ID = 2, Value = 250, IsDropped = false });
For these items, the values I want returned are:
ID: 1, Value: 150, IsDropped: false
ID: 2, Value: 100, IsDropped: false
However, if all values for a certain ID are dropped, I want to be able to know that as well so for example for these values:
items.Add(new MyItem() { ID = 2, Value = 100, IsDropped = true });
items.Add(new MyItem() { ID = 2, Value = 150, IsDropped = true });
I want to get:
ID: 2, Value: (doesn't really matter), IsDropped: true
Also on top of that, I need to be able to perform simple filter queries for example "only return items where ID == 1 || ID == 2"
Can this be done in a single query? I'm able to aggregate the class based on minimum value but adding the IsDropped parameter into the mix is making it really hard for me to write a single query that can perform all of this.
Thanks in advance for the help.
I think this can help you:
var groupBy = new BsonDocument
{
{"_id", "$ID"},
{
"min", new BsonDocument("$min", new BsonDocument
{
{"IsDropped", "$IsDropped"}, //This line will do the trick ;)
{"Value", "$Value"}
})
}
};
var results = collection.Aggregate().Group(groupBy).ToList();
And to add a filter over grouping results use this:
// `where ID == 1 || ID == 2` is as same as `where ID IN (1,2)`
var having = Builders<BsonDocument>.Filter.In("_id", new[] { 1, 2 });
// Now put having after groupBy
var results = collection.Aggregate().Group(groupBy).Match(having).ToList();

Custom Sort in datagridview in winform

I have data table having rows like
ID Name
2 A
4 B
3 C
5 D
1 E
List order = new List() { "1", "3", "2", "5", "4" }
--------------order by list-----------------
ID Name
1 E
3 C
2 A
5 D
4 B
can anyone help to implement this.. I am using DataTable in Winforms.
Another solution to the already given ones, would be to loop your order list and then sort your source list.
// source list
List<Foo> lSource = new List<Foo>() {
new Foo() { ID = 2, Name = "A" },
new Foo() { ID = 4, Name = "B" },
new Foo() { ID = 3, Name = "C" },
new Foo() { ID = 5, Name = "D" },
new Foo() { ID = 1, Name = "E" },
};
// order list
List<int> order = new List<int>() { 1, 3, 2, 5, 4 };
// loop order list and sort source list
order.ForEach(x =>
{
lSource = lSource.OrderBy(g => g.ID == x).ToList();
});
// set datasource
dataGridView1.DataSource = lSource;
I just added a class Foo containing an int ID and a string Name, because you didn't share your whole code.
I think you can join your order and your datatable with AsEnumerable method and on on part you can equalize both of them and select rows, then you can generate a DataTable from that query with CopyToDataTable method.
var dt = new DataTable();
var dc = new DataColumn() { ColumnName = "ID", DataType = typeof(string) };
dt.Columns.Add(dc);
dc = new DataColumn() { ColumnName = "Name", DataType = typeof(string) };
dt.Columns.Add(dc);
dt.Rows.Add(new object[] { "2", "A" });
dt.Rows.Add(new object[] { "4", "B" });
dt.Rows.Add(new object[] { "3", "C" });
dt.Rows.Add(new object[] { "5", "D" });
dt.Rows.Add(new object[] { "1", "E" });
List<string> order = new List<string>() { "1", "3", "2", "5", "4" };
var query = from item in order
join row in dt.AsEnumerable() on item equals row.Field<string>("ID")
select row;
var result = query.CopyToDataTable();
result will be;
I'm not sure this is the best way or not but this seems to fit with your case.
You can join both lists (the one with items and the one with sorted id's) and then select the items:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var list = new List<Item>{
new Item { Id = 2, Text = "A" },
new Item { Id = 4, Text = "B" },
new Item { Id = 3, Text = "C" },
new Item { Id = 5, Text = "D" },
new Item { Id = 1, Text = "E" }
};
var sortorder = new List<int> { 1, 3, 2, 5, 4 };
var sortedlist = sortorder.Join(list, x => x, y => y.Id, (x,y) => y);
foreach(var item in sortedlist)
Console.WriteLine("{0} {1}", item.Id, item.Text);
}
}

Sub Totalling Relational Datatable

I have a datatable with relational data structure, I need to sum up the sub nodes to their parent nodes all the way up to the top parent (NULL parent Id)
I have attached 2 images which shows the original table and another with the expected results
Cheers
I've taken an approach that simulates data as they could have been materialized from a database by some ORM, i.e. a class that contains data and a collection of children. Plus some "business logic" to calculate the required numbers. So you can choose a db approach as well as an in-memory approach.
In Linqpad:
void Main()
{
var data = new[]
{
new Record { Id = 1, ParentId = null, Qty = 1, Cost = 0.0m },
new Record { Id = 2, ParentId = 1, Qty = 2, Cost = 0.0m },
new Record { Id = 3, ParentId = 1, Qty = 3, Cost = 0.0m },
new Record { Id = 4, ParentId = 2, Qty = 4, Cost = 0.0m },
new Record { Id = 5, ParentId = 3, Qty = 5, Cost = 0.0m },
new Record { Id = 6, ParentId = 2, Qty = 6, Cost = 1.7m },
new Record { Id = 7, ParentId = 4, Qty = 7, Cost = 1.8m },
new Record { Id = 8, ParentId = 5, Qty = 8, Cost = 1.9m },
new Record { Id = 9, ParentId = 5, Qty = 9, Cost = 2.0m },
}.ToList();
// Mimic ORM's job:
data.ForEach(d => d.ChildRecords =
data.Where(c => c.ParentId == d.Id).ToList());
data.Select(d => new { d.Id, d.Cost, d.TotalCost } ).Dump();
}
class Record
{
public int Id { get; set; }
public int? ParentId { get; set; }
public int Qty { get; set; }
private decimal _cost = 0m;
public decimal Cost
{
get { return this._cost + this.ChildRecords.Sum(cr => cr.TotalCost); }
set { this._cost = value; }
}
public decimal TotalCost
{
get { return this.Qty * this.Cost; }
}
public ICollection<Record> ChildRecords;
}
Result:
Id Cost TotalCost
1 619.2 619.2
2 60.6 121.2
3 166 498
4 12.6 50.4
5 33.2 166
6 1.7 10.2
7 1.8 12.6
8 1.9 15.2
9 2 18
An optimization could be to apply some memoization, i.e. let the Cost property store the result of its getter in a private member variable.

Categories

Resources