Linq group by with return count - c#

Net core and Linq. I have table like below
Orders Table
OrderId Status
1 New
2 New
3 In Progress
4 In Progress
5 Closed
6 Closed
I have below model
public class SummaryEntity
{
public int New { get; set; }
public int InProgress { get; set; }
public int Closed { get; set; }
}
Then I need to return and bind to below model like below
New : 2
InProgress : 2
Closed : 2
I have tried something like below
SummaryEntity result = (from item in Orders
group item by new { item.Status } into g
select new SummaryEntity{ //not sure how to get count and assign it to model }
);
I am finding hard to group by and assign values to model. Can someone help me to write query. Any help would be appreciated. Thank you

You've already grouped the items by status, so g.Key will contain the status value and g itself is an enumerable of the grouped items. If you want to calculate their count use Count(), eg :
var counts = from item in Orders
group item by new { item.Status } into g
select new {status=g.Key, count=g.Count()};
This will return one object per status value with its count. Getting different columns for each status is essentially pivoting, converting the rows to columns.
In this case though, where you know the status names in advance, you can convert the results into a dictionary and retrieve the counts by name, eg :
var dict=counts.ToDictionary(x=>x.status,x=>x.count);
var model= new SummaryEntity
{
New = dict.TryGetValue("New",out var c_n)
? c_n : 0,
InProgress = dict.TryGetValue("InProgress",out var c_p)
? c_p : 0,
Closed = dict.TryGetValue("Closed",out var c_c)
? c_c : 0,
};
Dictionary.TryGetValue is used to avoid exceptions if a status value is missing

You model makes no sense. You have only status not New, InProgress, Closed. Try following :
DataTable dt = new DataTable();
dt.Columns.Add("OrderId", typeof(int));
dt.Columns.Add("Status", typeof(string));
dt.Rows.Add( new object[] { 1, "New"});
dt.Rows.Add( new object[] { 2, "New"});
dt.Rows.Add( new object[] { 3, "In Progress"});
dt.Rows.Add( new object[] { 4, "In Progress"});
dt.Rows.Add( new object[] { 5, "Closed"});
dt.Rows.Add( new object[] { 6, "Closed"});
var results = dt.AsEnumerable()
.GroupBy(x => x.Field<string>("Status"))
.Select(x => new { status = x.Key, count = x.Count() })
.ToList();

Related

How to output Property not included in group by and its count

what I am trying to achieve here is that I want from that LINQ query to return the list with two poperties: billNo, and number of occurences of the importcode on the same fromDate.
So here we have a billNo 1 and 2 both have the same importcode which appears in two rows on the same date (01/01/2020) thus count is 2.
If it helps to clarify, think of it as import code should only cover one distinct fromDate. If it appears multiple time, I would like to see how many times (count) and which BillNo s are like that.
So expected result for dataset below would be:
BillNo
Count
1
2
2
2
3
1
4
1
5
1
I struggle to figure out how to select BillNo if it is not used for grouping.
Thanks a lot for helping.
var rows = new List<ImportRow>()
{
new ImportRow {billNo= 1, importCode = "one", fromDate = new DateTime(2020, 1, 1)},
new ImportRow {billNo= 2, importCode = "one", fromDate = new DateTime(2020, 1, 1)},
new ImportRow {billNo= 3, importCode = "two", fromDate = new DateTime(2020, 1, 1)},
new ImportRow {billNo= 4, importCode = "two", fromDate = new DateTime(2020, 2, 1)},
new ImportRow {billNo= 5, importCode = "one", fromDate = new DateTime(2020, 3, 1)}
};
public class ImportRow : IEnumerable
{
public int billNo { get; set; }
public string importCode { get; set; }
public DateTime fromDate { get; set; }
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
var billNosWithCounts = rows.GroupBy(info => new { info.importCode,
info.fromDate })
.Select(group => new
{
FromDate = group.Key.fromDate,
Count = group.Count()
});
You can join the source onto itself, I believe that will give you the output you are looking for:
var result = from r1 in rows
join r2 in rows
on new { r1.importCode, r1.fromDate } equals
new { r2.importCode, r2.fromDate }
group r1.billNo by r1.billNo into g
select new
{
BillNo = g.Key,
Count = g.Count()
};
You could potentially achieve what you are looking for by doing the following:
public static IDictionary<int, string> Map(this List<Sample> collection)
{
var kvp = new Dictionary<int, string>();
var records = collection.GroupBy(value => new { value.Code, value.Date });
foreach(var record in records)
foreach(var contents in record)
kvp.Add(contents.Bill, contents.Count());
}
A sample of the object you outlined above:
var collection = new List<Sample>()
{
new Sample() { Bill = 1, Code = "ABC", Date = DateTime.Now.ToShortDateString() },
new Sample() { Bill = 2, Code = "ABC", Date = DateTime.Now.ToShortDateString() },
new Sample() { Bill = 3, Code = "ABCD", Date = DateTime.Now.ToShortDateString() },
new Sample() { Bill = 4, Code = "ABCDE", Date = DateTime.Now.AddDays(-3).ToShortDateString() }
};
In essence, I created a method that will let you pass an object. It will then group on the import code and the date. Creating the unique pair you desire. Then I simply add the newly transformed content into a Dictionary. You could use Linq to do the full transform, but it might become more difficult to read or understand, so I chose to simplify into basic loops for the transformation.

Add two objects in the same list

I have a list of objects. Each object contains an ID and a value. There are several objects which have the same ID but different values. How would I go about adding the values together based on the matching ID? Additionally, how would I go about removing one entry after the addition is complete?
The object in question:
public class MyObject
{
public int ID { get; set; }
public int Value { get; set; }
}
Below is where I am getting the duplicate objects from a list of all objects. I'm simply getting all the duplicate IDs into a list of strings and then grabbing the entire duplicate object in the duplicateObjects list.
List<MyObject> myObjects = GetMyObjectsList();
List<string> duplicateIds = myObjects.GroupBy(x => x.ID)
.Where(group => group.Count() > 1)
.Select(group => group.Key).ToList();
List<MyObject> duplicateObjects = myObjects.Where(x => duplicateIds.Contains(x.ID)).ToList();
I'm stuck on the last steps which are adding the duplicate values and then removing one of the duplicates. How would I accomplish this with Linq?
Assume the list look like this
var list = new MyObject[]
{
new MyObject {ID = 1, Value = 2},
new MyObject {ID = 2, Value = 2},
new MyObject {ID = 1, Value = 3},
new MyObject {ID = 4, Value = 4},
new MyObject {ID = 2, Value = 4},
};
Then just select from list group by ID and sum value like this
var result = (from tm in list
group tm by tm.ID into Test
select new
{
ID = Test.Key,
Value = Test.Sum(x => x.Value)
});
Output
ID = 1, Value = 5
ID = 2, Value = 6
ID = 4, Value = 4
working fiddle here
I hope I understood your question correctly.As clarified in the comment,you want to "sum" all the values grouped by ID and then remove one of the duplicates. Please verify if the following is how you could like it to be behave.
var duplicates = myObjects.GroupBy(x => x.ID)
.Select(group => new { Group = group.Count() > 1 ? group.ToList().Take(group.Count()-1) : group.ToList(),Sum=group.Count()});
For Input
Output
If you need to exclude non-duplicates, then you would need to include an additional Where Condition
var duplicates = myObjects.GroupBy(x => x.ID)
.Where(x=>x.Count()>1)
.Select(group => new { Group = group.Count() > 1 ? group.ToList().Take(group.Count()-1) : group.ToList(),Sum=group.Count()});

Add duplicates together in List

First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();

Unable to pass multiple values in the where clause of a LINQ query

I have a LINQ query something like below:
var results=from p in inputTable.AsEnumerable()
where inputParameter.Contains(p.Field<String>(inputField))&&
p.Field<string>(CurrencyCode)==currencyCode
group p by new
{
p[CurrencyCode],
} into groupedTable
select new
{
Amount = groupedTable.Sum(r => r.Field<System.Decimal>(amountField))
};
if (results.Count() > 0)
{
retVal = results.ElementAt(0).Amount;
}
My inputParameter is basically a List<string> which will have values like {October, November, December}.
The inputField is November.
My thought is that since the where condition has a Contains method it will just filter rows by November, since inputField is November.
I basically need to pass all the elements of the list, i.e. October, November and December and then get records filtered by these months.
I tried to use where-in stuff of LINQ but was not successful.
Experts please help to crack this question.
Any help/pointer will be highly appreciable.
EDIT:
Let me try to make this question very simple.
My List<string> inputParameter can contain variable strings, like {October, November, December} or {January, February, March, April} and so on.
I need my query to pass in all these values and filter the records accordingly.
The simplified query which I tried is follows:
var results=from p in inputTable.AsEnumerable()
where p.Field<string>(FiscalMonth)==inputParameter[0] ||
p.Field<string>(FiscalMonth)==inputParameter[1] ||
p.Field<string>(FiscalMonth)==inputParameter[2]
select new
{
p.Amount
};
In the above instance I have basically hardcoded the individual elements of the List inputParameter, but my list will be variable at times. i.e it may hold 3 items, 4 items, or even 12 items.
How to mould the above query to avoid individual hard-coding?
Regards
Anurag
Responding to your edit, it should be :
var results = from p in inputTable.AsEnumerable()
where inputParameter.Contains(p.Field<string>(FiscalMonth))
select new
{
p.Amount
};
This is a working console example that demonstrate query with Contains :
//create datatable with column FiscalMonth
var table = new DataTable();
table.Columns.Add("FiscalMonth");
//add two rows, January and October
var row1 = table.NewRow();
row1["FiscalMonth"] = "January";
var row2 = table.NewRow();
row2["FiscalMonth"] = "October";
table.Rows.Add(row1);
table.Rows.Add(row2);
//query from data table where FiscalMonth in (October, November, December)
var inputParameter = new List<string> {"October", "November", "December"};
var result = from r in table.AsEnumerable()
where inputParameter.Contains(r.Field<string>("FiscalMonth"))
select r.Field<string>("FiscalMonth");
//the result will be only one row, which is October. January is successfully filtered out
foreach (var r in result)
{
Console.WriteLine(r);
}
Do not really understand exactly what you are looking for, since you are stating that you want to query on only Month and your code is querying on month and currency code. but looks like you are returning the sum of the Amount. So he is a first stab at what you want to do.
class MonthCurrency
{
public string Month { get; set; }
public int CurrencyCode { get; set; }
public decimal Amount { get; set; }
}
static List<MonthCurrency> inputTable = null;
static void Main(string[] args){
inputTable = new List<MonthCurrency>()
{ new MonthCurrency() { Month = "October", CurrencyCode= 1, Amount = 1},
new MonthCurrency() { Month = "October", CurrencyCode= 1, Amount = 2},
new MonthCurrency() { Month = "November", CurrencyCode= 2, Amount = 1},
new MonthCurrency() { Month = "November", CurrencyCode= 2, Amount = 2},
new MonthCurrency() { Month = "December", CurrencyCode= 3, Amount = 1},
new MonthCurrency() { Month = "December", CurrencyCode= 3, Amount = 2},
};
var result = GetCurrencyCode("November");
}
static public decimal GetCurrencyCode(string inputParameter)
{
decimal retVal = 0.0M;
var results = from p in inputTable.AsEnumerable()
where p.Month == inputParameter
group p by new
{
p.CurrencyCode,
} into groupedTable
select new MonthCurrency
{
Amount = groupedTable.Sum(r => r.Amount)
};
if (results.Count() > 0)
{
retVal = results.ElementAt(0).Amount;
}
return retVal;
}
Hopefully this will help you out

Group by same value and contiguous date

var myDic = new SortedDictionary<DateTime,int> ()
{ { new DateTime(0), 0 },
{ new DateTime(1), 1 },
{ new DateTime(2), 1 },
{ new DateTime(3), 0 },
{ new DateTime(4), 0 },
{ new DateTime(5), 2 }
};
How can group these items (with a LINQ request) like this :
group 1 :
startDate: 0, endDate:0, value:0
group 2 :
startDate: 1, endDate:2, value:1
group 3 :
startDate: 3, endDate:4, value:0
group 4 :
startDate: 5, endDate:5, value:2
group are defined by contiguous date and same values.
Is it possible with a groupby ?
Just use a keyGenerating function. This example presumes your dates are already ordered in the source with no gaps.
int currentValue = 0;
int groupCounter = 0;
Func<KeyValuePair<DateTime, int>, int> keyGenerator = kvp =>
{
if (kvp.Value != currentValue)
{
groupCounter += 1;
currentValue = kvp.Value;
}
return groupCounter;
}
List<IGrouping<int, KeyValuePair<DateTime, int>> groups =
myDictionary.GroupBy(keyGenerator).ToList();
It looks like you are trying to group sequential dates over changes in the value. I don't think you should use linq for the grouping. Instead you should use linq to order the dates and iterate over that sorted list to create your groups.
Addition 1
While you may be able to build your collections with by using .Aggregate(). I still think that is the wrong approach.
Does your data have to enter this function as a SortedDictionary?
I'm just guessing, but these are probably records ordered chronologically.
If so, do this:
public class Record
{
public DateTime Date { get; set; }
public int Value { get; set; }
}
public class Grouper
{
public IEnumerable<IEnumerable<Record>> GroupRecords(IEnumerable<Record> sortedRecords)
{
var groupedRecords = new List<List<Record>>();
var recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
foreach (var record in sortedRecords)
{
if (recordGroup.Count > 0 && recordGroup.First().Value != record.Value)
{
recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
}
recordGroup.Add(record);
}
return groupedRecords;
}
}

Categories