LINQ Group by and "count-if" - c#

I want to count all items in a list if a value is true and the count the false values.
I have this:
Items.GroupBy(
i => i.ItemID,
i => new { isScheduled = i.isScheduled },
(key, g) => new ItemStatistics()
{
ItemID = key,
ScheduledItems = g.Count(g=>g.isScheduled),
UnscheduledItems = g.Count(g=> !g.isScheduled)
}).ToList();
this gives me the following compilation error:
Cannot convert lambda expression to type
'System.Collections.Generic.IEqualityComparer' because it is not
a delegate type
as if the it expects a different method overload
when i do this everything seems to be okay...
Items.GroupBy(
i => i.ItemID,
i => new { isScheduled = i.isScheduled },
(key, g) => new ItemStatistics()
{
ItemID = key,
ScheduledItems = g.Count(),
UnscheduledItems = g.Count()
}).ToList();
why is it when i remove the g=> !g.isScheduled expression from the count method it accepts it ?

Found it ... ARGH !
when I can use "g" as the variable for my inner lambda expression inside the group becuse it refers to the original group "g". so i changed g=>g.isScheduled toi=>i.isScheduled
Items.GroupBy(
i => i.ItemID,
i => new { isScheduled = i.isScheduled },
(key, g) => new ItemStatistics()
{
ItemID = key,
ScheduledItems = g.Count(i=>i.isScheduled),
UnscheduledItems = g.Count(i=> !i.isScheduled)
}).ToList();
and now everything is okay

Related

How to Add Rownum to GroupBy Linq

I have a complex LINQ Query to extract Top students in my university. Here is the query :
var query = Db.Students.AsNoTracking().Where(...).AsQueryable();
var resultgroup = query.GroupBy(st => new
{
st.Student.CourseStudyId,
st.Student.EntranceTermId,
st.Student.StudyingModeId,
st.Student.StudyLevelId
}, (key, g) => new
{
CourseStudyId = key.CourseStudyId,
EntranceTermId = key.EntranceTermId,
StudyingModeId = key.StudyingModeId,
StudyLevelId = key.StudyLevelId,
list = g.OrderByDescending(x =>
x.StudentTermSummary.TotalAverageTillTerm).Take(topStudentNumber)
}).SelectMany(q => q.list).AsQueryable();
This Query give me top n students based on 4 parameters and on their TotalAverageTillTerm.
Now I want to add rownum for each group to simulate Total rank, for example Output is :
Now I want to Add TotalRank as rownumber like Sql. In the picture X1=1,X2=2,X3=3 and Y1=1,Y2=2,Y3=3
If I want to reduce problem. I only work on one group. Code Like this :
resultgroup = query.GroupBy(st => new
{
st.Student.StudyLevelId
}, st => st, (key, g) => new
{
StudyLevelId = key.StudyLevelId,
list = g.OrderByDescending(x =>
x.StudentTermSummary.TotalAverageTillTerm)
.Take(topStudentNumber)
}).SelectMany(q => q.list).AsQueryable();
list was a List of student but I see no sign of student having a rank property so I wrapped it into a annonimous type with rank.
var query = Db.Students.AsNoTracking().Where(...).AsEnumerable();
var resultgroup = query.GroupBy(st => new {
st.Student.CourseStudyId,
st.Student.EntranceTermId,
st.Student.StudyingModeId,
st.Student.StudyLevelId
})
.SelectMany( g =>
g.OrderByDescending(x =>x.StudentTermSummary.TotalAverageTillTerm)
.Take(topStudentNumber)
.Select((x,i) => new {
CourseStudyId = g.Key.CourseStudyId,
EntranceTermId = g.Key.EntranceTermId,
StudyingModeId = g.Key.StudyingModeId,
StudyLevelId = g.Key.StudyLevelId,
Rank = i+1
//studentPorperty = x.Prop1,
})
)
.AsQueryable();
Do you mean :
var query = Db.Students.AsNoTracking().Where(...).AsQueryable();
var resultgroup = query.GroupBy(st => new
{
st.Student.CourseStudyId,
st.Student.EntranceTermId,
st.Student.StudyingModeId,
st.Student.StudyLevelId
}, (key, g) => new
{
CourseStudyId = key.CourseStudyId,
EntranceTermId = key.EntranceTermId,
StudyingModeId = key.StudyingModeId,
StudyLevelId = key.StudyLevelId,
list = g.OrderByDescending(x =>
x.StudentTermSummary.TotalAverageTillTerm)
.Take(topStudentNumber)
.Select((x, i) => new { Item = x, TotalRank = i /* item number inside group */}),
StudentsInGroupCount = g.Count() // count group this items
}).SelectMany(q => q).AsQueryable();
To see the results :
foreach (var item in resultgroup.ToList())
{
item.list.ForEach(s => Console.WriteLine(s.TotalRank));
}

custom sort order within a group of alerts with same type id

****************************EDITED**********************************************
I have the following code that does the following:
Group alerts with same alert type ID together
sort entire groups in a custom order specified by AlertSorterUtil
within a group sort by severity in a specific order.
I am having trouble with 3. Within a group, the elements(alerts) have to be sorted by severity which is an integer. In the order of severity's 1,2,3,4,5,0.
var severityOrder = new[] { 1 ,2,3,4,5,0 };
// All Alerts
var alerts = new BPAlerts
{
AllAlerts = intakeAlerts.Select(
alert => new BPAlert
{
AlertTypeId = alert.AlertTypeId ?? 8100,
IsOverview = alert.IsOverviewAlert.GetValueOrDefault(),
Text = alert.AlertText,
Title = alert.AlertTitle,
Type = alert.AlertTypeId == 8106 ? "warning" : "report",
Severity = alert.AlertSeverity.GetValueOrDefault(),
Position = alert.Position.GetValueOrDefault()
})
.GroupBy(a => a.AlertTypeId)
.OrderBy(g => AlertSorterUtil.SortByAlertTypeId(g.Key))
.Select(g => g.OrderBy(a => a.Severity))
//list of lists (groups) need to be converted to a single list
.SelectMany(g => g)
.ToList()
};
if (!intakeTexts.IsNullOrEmpty())
{
foreach (BusinessReportCustomText text in intakeTexts)
{
var alert = new BPAlert
{
AlertTypeId = text.CustomTextSectionId,
IsOverview = false,
Text = text.CustomText,
Title = Utils.CustomTextTitleGenerator.getTitle(text.CustomTextSectionId)
};
alerts.AllAlerts.Add(alert);
}
}
alerts.TotalAlertCount = intakeAlerts.Count;
return alerts;
}
When you are sorting the groups with this lambda:
g => g.OrderBy(a => a.Severity)
you need to modify the return value of the lambda to produce the order needed. Using your provided
var severityOrder = new[] { 1 ,2,3,4,5,0 };
you could use
g => g.OrderBy(a => Array.IndexOf(severityOrder, a.Severity))
Alternatively, you can mathematically transform Severity into severityOrder:
g => g.OrderBy(a => (a.Severity+5) % 6)
Finally, you could preserve the flexibility of the first approach but avoid the search overhead of Array.IndexOf by creating a mapping:
var severitySortMap = new[] { 6, 1,2,3,4,5 };
g => g.OrderBy(a => severitySortMap[a.Severity])
so using Thenby twide does not disrupt the order in any way. This solved my problem:
var alerts = new BPAlerts
{
AllAlerts = intakeAlerts.Select(
alert => new BPAlert
{
AlertTypeId = alert.AlertTypeId ?? 8100,
IsOverview = alert.IsOverviewAlert.GetValueOrDefault(),
Text = alert.AlertText,
Title = alert.AlertTitle,
Type = alert.AlertTypeId == 8106 ? "warning" : "report",
Severity = alert.AlertSeverity.GetValueOrDefault(),
Position = alert.Position.GetValueOrDefault()
})
.OrderBy(x => AlertSorterUtil.SortByAlertTypeId(x.AlertTypeId))
.ThenBy(y => AlertSorterUtil.SortAlertBySeverity(y.Severity))
.ThenBy(z => z.Text)
.ToList()
};

How do I order the elements in a group by linq query, and pick the first?

I have a set of data that contains a type, a date, and a value.
I want to group by the type, and for each set of values in each group I want to pick the one with the newest date.
Here is some code that works and gives the correct result, but I want to do it all in one linq query rather than in the iteration. Any ideas how I can achieve the same result as this with purely a linq query...?
var mydata = new List<Item> {
new Item { Type = "A", Date = DateTime.Parse("2016/08/11"), Value = 1 },
new Item { Type = "A", Date = DateTime.Parse("2016/08/12"), Value = 2 },
new Item { Type = "B", Date = DateTime.Parse("2016/08/20"), Value = 3 },
new Item { Type = "A", Date = DateTime.Parse("2016/08/09"), Value = 4 },
new Item { Type = "A", Date = DateTime.Parse("2016/08/08"), Value = 5 },
new Item { Type = "C", Date = DateTime.Parse("2016/08/17"), Value = 6 },
new Item { Type = "B", Date = DateTime.Parse("2016/08/30"), Value = 7 },
new Item { Type = "B", Date = DateTime.Parse("2016/08/18"), Value = 8 },
};
var data = mydata.GroupBy(_ => _.Type);
foreach (var thing in data) {
#region
// How can I remove this section and make it part of the group by query above... ?
var subset = thing.OrderByDescending(_ => _.Date);
var top = subset.First();
#endregion
Console.WriteLine($"{thing.Key} {top.Date.ToString("yyyy-MM-dd")} {top.Value}");
}
Where Item is defined as:
public class Item {
public string Type {get;set;}
public DateTime Date {get;set;}
public int Value {get;set;}
}
Expected output:
A 2016-08-12 2
B 2016-08-30 7
C 2016-08-17 6
Use select to get the FirstOrDefault (or First - because of the grouping you won't get a null) ordered descending:
var data = mydata.GroupBy(item => item.Type)
.Select(group => group.OrderByDescending(x => x.Date)
.FirstOrDefault())
.ToList();
Or SelectMany together with Take(1)
var data = mydata.GroupBy(item => item.Type)
.SelectMany(group => group.OrderByDescending(x => x.Date)
.Take(1))
.ToList();
You can Select the first element of the ordered groups:
var topItems = mydata.GroupBy(item => item.Type)
.Select(group => group.OrderByDescending(item => item.Date).First())
.ToList();
topItems is now a List<Item> containing only the top items per Type.
You may also retrieve it as Dictionary<string,Item> mapping the Type strings to the top Item for that Type:
var topItems = mydata.GroupBy(item => item.Type)
.ToDictionary(group => group.Key,
group => group.OrderByDescending(item => item.Date).First());
var data = mydata.GroupBy(
item => item.Type,
(type, items) => items.OrderByDescending(item => item.Date).First());
foreach (var item in data)
{
Console.WriteLine($"{item.Type} {item.Date.ToString("yyyy-MM-dd")} {item.Value}");
}
Group by type, for each group, order it by date descending and pick the first (newest).
mydata.GroupBy(i => i.Type).Select(g => g.OrderByDescending(i => i.Date).First());

LINQ to SQL error on .Join()

I'm trying to query a database and join two tables. I've never used Join() this way and I'm getting an error on the second Join():
var adjustments = data.Inventory_ARCHIVEs
.Where(i => i.Location == comboBox3.Text &&
upcCodes.Contains(i.UPCCode) &&
(i.AVtime.Value.Date >= dateTimePicker1.Value.Date &&
i.AVtime.Value.Date <= dateTimePicker1.Value.AddDays(1).Date) &&
(i.BVtime.Value.Date >= dateTimePicker1.Value.Date &&
i.BVtime.Value.Date <= dateTimePicker1.Value.AddDays(1).Date))
.GroupBy(i => new { i.UPCCode })
.Select(i => new
{
ID = i.Max(x => x.ID),
i.Key.UPCCode
})
.Join(data.Inventory_ARCHIVEs, a => a.ID,
b => b.ID, (a, b) => new { a, b })
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { y.UPC_Code, y.Location }, (x, y) => new
{
ID = x.a.ID,
UPCCode = x.a.UPCCode,
Date = x.b.BVtime.Value.Date,
Description = y.Description,
BVamount = x.b.BVamount,
AVamount = x.b.AVamount,
Difference = x.b.AVamount - x.b.BVamount,
AverageCost = x.b.AverageCost,
ExtCost = (x.b.AVamount - x.b.BVamount) * x.b.AverageCost
});
x.a.UPCCode ,x.b.Location, y.UPC_Code, andy.Location are strings.
This is the error:
The type arguments for method 'System.Linq.Enumerable.Join<TOuter,TInner,TKey,TResult> (System.Collections.Generic.IEnumerable<TOuter>, System.Collections.Generic.IEnumerable<TInner>, System.Func<TOuter,TKey>, System.Func<TInner,TKey>, System.Func<TOuter,TInner,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
If I DO NOT include the join by "Location" column and just use "UPCCode", it works, but when I add the second join by column, I get the error
I suspect this is the problem - it's at least one problem:
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { y.UPC_Code, y.Location },
...)
You're trying to join using two different anonymous types as the key types. They've got different properties - one has UPCCode, the other has UPC_Code. You probably want:
.Join(data.BQItems, x => new { x.a.UPCCode, x.b.Location },
y => new { UPCCode = y.UPC_Code, y.Location },
...)
Or just be more consistent with your property names so that you use UPCCode or UPC_Code everywhere, rather than a mixture.
You must have most care about type of data on both side of 'equals' clause, They should be of same datatype like int and int , or string and string.
Or using lambda expression the second and third parameter must be same datatype in the Join clause.

Group By Multiple Columns

How can I do GroupBy multiple columns in LINQ
Something similar to this in SQL:
SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>
How can I convert this to LINQ:
QuantityBreakdown
(
MaterialID int,
ProductID int,
Quantity float
)
INSERT INTO #QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM #Transactions
GROUP BY MaterialID, ProductID
Use an anonymous type.
Eg
group x by new { x.Column1, x.Column2 }
Procedural sample:
.GroupBy(x => new { x.Column1, x.Column2 })
Ok got this as:
var query = (from t in Transactions
group t by new {t.MaterialID, t.ProductID}
into grp
select new
{
grp.Key.MaterialID,
grp.Key.ProductID,
Quantity = grp.Sum(t => t.Quantity)
}).ToList();
For Group By Multiple Columns, Try this instead...
GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new
{
Key1 = key.Column1,
Key2 = key.Column2,
Result = group.ToList()
});
Same way you can add Column3, Column4 etc.
Since C# 7 you can also use value tuples:
group x by (x.Column1, x.Column2)
or
.GroupBy(x => (x.Column1, x.Column2))
C# 7.1 or greater using Tuples and Inferred tuple element names (currently it works only with linq to objects and it is not supported when expression trees are required e.g. someIQueryable.GroupBy(...). Github issue):
// declarative query syntax
var result =
from x in inMemoryTable
group x by (x.Column1, x.Column2) into g
select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));
// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
.Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));
C# 3 or greater using anonymous types:
// declarative query syntax
var result3 =
from x in table
group x by new { x.Column1, x.Column2 } into g
select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };
// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
.Select(g =>
new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });
You can also use a Tuple<> for a strongly-typed grouping.
from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
LastName = grouping.Key.Item1,
FirstName = grouping.Key.Item2,
MiddleName = grouping.Key.Item3,
DayCount = grouping.Count(),
AmountBilled = grouping.Sum(x => x.Rate),
}
Though this question is asking about group by class properties, if you want to group by multiple columns against a ADO object (like a DataTable), you have to assign your "new" items to variables:
EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
.Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
var Dups = ClientProfiles.AsParallel()
.GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
.Where(z => z.Count() > 1)
.Select(z => z);
var Results= query.GroupBy(f => new { /* add members here */ });
A thing to note is that you need to send in an object for Lambda expressions and can't use an instance for a class.
Example:
public class Key
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
This will compile but will generate one key per cycle.
var groupedCycles = cycles.GroupBy(x => new Key
{
Prop1 = x.Column1,
Prop2 = x.Column2
})
If you wan't to name the key properties and then retreive them you can do it like this instead. This will GroupBy correctly and give you the key properties.
var groupedCycles = cycles.GroupBy(x => new
{
Prop1 = x.Column1,
Prop2= x.Column2
})
foreach (var groupedCycle in groupedCycles)
{
var key = new Key();
key.Prop1 = groupedCycle.Key.Prop1;
key.Prop2 = groupedCycle.Key.Prop2;
}
group x by new { x.Col, x.Col}
.GroupBy(x => (x.MaterialID, x.ProductID))
.GroupBy(x => x.Column1 + " " + x.Column2)
For VB and anonymous/lambda:
query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })

Categories