Cannot group data in LINQ - c#

I have a question about a LINQ grouping.
I thought that grouping would be a simple matter of using the GroupBy function on the result set and specifying what to group it by. However my items appear to not be grouping together and instead are displaying as if the GroupBy function wasn't there. I want to group by the itemPk, but I'm can't seem to do it. I have tried grouping by both category.ItemFk and Item.Itempk, but no luck. Could someone give me a pointer on this?
var itemIds = items.Select(i => i.ItemId).ToList();
var itemAndCatJoin =
from item in Context.SCS_Items
join category in Context.SCS_ItemCategories
on item.ItemPk equals category.ItemFk
into temp
from category in temp.DefaultIfEmpty()
select new ExportItemTable
{
Category = category,
Item = item
};
return itemAndCatJoin.Where(i => itemIds.Contains(i.Item.ItemPk))
.GroupBy(n => new {n.Item, n.Category})
.Select(i => new ExportableItem
{
ItemPk = i.Key.Item.ItemPk,
Name = i.Key.Item.Name,
Description = i.Key.Item.Description,
Price = i.Key.Item.Price,
Category = i.Key.Category.Category.Category_Name,
GLDepartment = i.Key.Category.GL_Department.Name ?? "",
GLName = i.Key.Category.GL_Name.Name ?? "",
StartDate = i.Key.Item.StartDate,
EndDate = i.Key.Item.EndDate,
FiscalYear = i.Key.Item.SCS_FiscalYear.Name,
School = i.Key.Item.School != null ? i.Key.Item.School.School_Name : i.Key.Item.Board.Board_Name,
Beneficiary = i.Key.Item.SCS_Beneficiary.Name,
Quantity = i.Key.Item.MaxQuantity,
Deleted = i.Key.Item.DeletedFlag,
OptionalStudents = i.Key.Item.SCS_Attachments.Where(a => !a.IsRequired).SelectMany(a => a.SCS_StudentAttachments).Where(s => !s.DeletedFlag).Select(s => s.StudentFk).Distinct().Count(),
RequiredStudents = i.Key.Item.SCS_Attachments.Where(a => a.IsRequired).SelectMany(a => a.SCS_StudentAttachments).Where(s => !s.DeletedFlag).Select(s => s.StudentFk).Distinct().Count(),
IsPublic = i.Key.Item.IsPublic,
AllowRecurring = i.Key.Item.AllowRecurringPayments,
EffectiveCutoff = i.Key.Item.SCS_Attachments.Where(a => !a.DeletedFlag && a.CourseDropCutoff.HasValue).Select(a => a.CourseDropCutoff).OrderBy(a => a).FirstOrDefault(),
CreatedDate = i.Key.Item.CreatedDate
}).OrderBy(i => i.ItemPk).ToList();
}

your groupbyy is indeed doing nothing for you, you need to tell the groupby what to group by....
like
.GroupBy(n => n.Category)

Here is a simple example to your grouping question:
class Program
{
static void Main()
{
var allItems = GetAllItems();
var groups = from item in allItems
group item by item.Category
into newGroup
select newGroup;
foreach (var group in groups)
{
Console.WriteLine($"\nCategory: {group.Key}");
foreach (var item in group)
{
Console.WriteLine($"{item.Name}: {item.Price}");
}
}
Console.ReadLine();
}
static List<Category> GetAllCategories()
{
return new List<Category>()
{
new Category() { Id = 1, Name = "Programming Books" },
new Category() { Id = 2, Name = "Fiction Books" }
};
}
static List<Item> GetAllItems()
{
return new List<Item>()
{
new Item() { Id = 1, Name = "Embedded Linux", Category = 1, Price = 9.9 },
new Item() { Id = 2, Name = "LINQ In Action", Category = 1, Price = 36.19 },
new Item() { Id = 3, Name = "C# 6.0 and the .NET 4.6 Framework", Category = 1, Price = 40.99 },
new Item() { Id = 4, Name = "Thinking in LINQ", Category = 1, Price = 36.99 },
new Item() { Id = 5, Name = "The Book Thief", Category = 2, Price = 7.99 },
new Item() { Id = 6, Name = "All the Light We Cannot See", Category = 2, Price = 16.99 },
new Item() { Id = 7, Name = "The Life We Bury", Category = 2, Price = 8.96 }
};
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public int Category { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
This example is simple enough for anyone new to LINQ. I am sure you can make some adjustment to make it work for your specific issue. Hope this will help.

Related

LINQ distinct on groups of information

I have multiple items (List)
I need to get distinct features for the items but the issue is that each item has two features. So both features need to match on GroupTypeId and GroupId to become a distinct group.
I need to group items that have the same features (per above distinct groups found). I don't need features here again at the item level since I will have these per above in a separate object.
I need to keep items order intact, the first item will go in group 1, then second might go in group 1 or group 2 and so on.
Also, each item in group, item number needs to be overwritten per the new sequence in that group.
Can i do above tasks purely with LINQ rather than using nested loops?
In the below sample for items
i have 3 distinct feature groups
and 3 item groups. Item 2 and 4 needs to be grouped together and the line no needs to change to 1 and 2. For item 1 and 3, line number should become as 1.
Need to add to List whose count will be 3
Index[0] will have 2 features and 1 item
Index[1] will have 2 features and 2 items
Index[2] will have 2 features and 1 item
public class ItemPicked
{
public int Id { get; set; }
public string Description { get; set; }
public int LineNumber { get; set; }
public int PartId { get; set; }
public List<ItemFeature> Features { get; set; }
}
public class ItemFeature
{
public string OriginalReceived { get; set; }
public Group Feature { get; set; }
}
public class Group
{
public int Id { get; set; }
public string GroupTypeId { get; set; }
public string GroupId { get; set; }
public int SequenceNo { get; set; }
}
public class PickedGrouping
{
public List<ItemFeature> Features { get; set; }
public List<ItemPicked> Items { get; set; }
}
var SampleFeatures1 = new List<ItemFeature>() {
new ItemFeature {
OriginalReceived = "SomeThing",
Feature = new Group() {
Id = 1, SequenceNo = 1, GroupTypeId = "A", GroupId = "B"
}
},
new ItemFeature {
OriginalReceived = "SomeThing2",
Feature = new Group() {
Id = 2, SequenceNo = 2, GroupTypeId = "Y", GroupId = "Z"
}
}
};
var SampleFeatures2 = new List<ItemFeature>() {
new ItemFeature {
OriginalReceived = "SomeThing3",
Feature = new Group() {
Id = 3, SequenceNo = 3, GroupTypeId = "C", GroupId = "D"
}
},
new ItemFeature {
OriginalReceived = "SomeThing4",
Feature = new Group() {
Id = 4, SequenceNo = 4, GroupTypeId = "X", GroupId = "Y"
}
}
};
var SampleFeatures3 = new List<ItemFeature>() {
new ItemFeature {
OriginalReceived = "SomeThing5",
Feature = new Group() {
Id = 3, SequenceNo = 3, GroupTypeId = "C", GroupId = "D"
}
},
new ItemFeature {
OriginalReceived = "SomeThing5",
Feature = new Group() {
Id = 2, SequenceNo = 2, GroupTypeId = "M", GroupId = "K"
}
}
};
var items = new List<ItemPicked>(){
new ItemPicked{
Id = 1, Description = "Item 1", LineNumber = 1, Features = SampleFeatures1
},
new ItemPicked{
Id = 2, Description = "Item 2", LineNumber = 2, Features = SampleFeatures2
},
new ItemPicked{
Id = 3, Description = "Item 3", LineNumber = 3, Features = SampleFeatures3
},
new ItemPicked{
Id = 4, Description = "Item 4", LineNumber = 4, Features = SampleFeatures2
}
};
var pickedGroupings = new List<PickedGrouping>();
PickedGrouping selectedGroup = null;
foreach (var item in items)
{
var found = 0;
if(item.Features == null || !item.Features.Any())
{
selectedGroup = pickedGroupings.FirstOrDefault(x => x.Features == null || !x.Features.Any());
if (selectedGroup == null) selectedGroup = new PickedGrouping();
selectedGroup.Features.AddRange(item.Features);
}
else
{
foreach (var feature in item.Features)
{
foreach (var pg in pickedGroupings)
{
if ((item.Features == null || !item.Features.Any()) && (pg.Features == null || !pg.Features.Any())){
selectedGroup = pg;
found += 1;
}
else
{
foreach (var pgf in pg.Features)
{
if (pgf.Feature == null) continue;
if (pgf.Feature.GroupId == feature.Feature.GroupId && pgf.Feature.GroupTypeId == feature.Feature.GroupTypeId)
{
selectedGroup = pg;
found += 1;
}
}
}
}
}
if (found < 2)
{
pickedGroupings.Add(new PickedGrouping() { Features = item.Features });
selectedGroup = pickedGroupings[pickedGroupings.Count - 1];
}
}
//add item
if (selectedGroup.Items == null) selectedGroup.Items = new List<ItemPicked>();
selectedGroup.Items.Add(item);
}
//update line number
foreach(var pg in pickedGroupings)
{
var lineNum = 1;
foreach(var item in pg.Items)
{
item.LineNumber = lineNum;
lineNum += 1;
}
}

List union that depends / selects on property values

I have a class "item":
public class Item
{
public int Id { get; set; }
public decimal Price { get; set; }
public override bool Equals(object obj)
{
if (obj is Item item)
return item.Id == Id;
return false;
}
// GetHashCode omitted...
}
And I have 2 lists that I need to union:
var items1 = new List<Item>
{
new Item { Id = 1, Price = 10 },
new Item { Id = 2, Price = 10 },
new Item { Id = 3, Price = 10 },
};
var items2 = new List<Item>
{
new Item { Id = 1, Price = 10 },
new Item { Id = 2, Price = 8 },
new Item { Id = 4, Price = 10 },
};
The union I get like this:
var union = items1.Union(items2).ToList();
But I need also the constraint that the items with the lowest price is in the union. So for example in the above lists Item.ID = 2 from "items2" must be in the union...so the result should be a list consisting of these 4 items:
Item { Id = 1, Price = 10 }
Item { Id = 2, Price = 8 } // Not the one with Price = 10
Item { Id = 3, Price = 10 }
Item { Id = 4, Price = 10 }
Is there an elegant way of doing this in C# (preferably using Linq)?
You can try using groupby, like below :
var result = items1.Union(items2).GroupBy(x => x.Id)
.Select(x => new Item
{
Id = x.Key,
Price = x.Min(i => i.Price)
});

How can I join different property object collections

I have a list of my issue class
var firstList = new List<object> {
new { id =1, Name = "item-1"},
new { id =2, Name = "item-2"},
new { id =3, Name = "item-3"},
new { id =4, Name = "item-4"}
}
And my second list is like this
var secondList = new List<Issue> {
new Issue{ id =1, Date = "01.01.2017"},
new Issue{ id =2, Date = "01.02.2017"}
}
So I want to create a new list like this. (same id objects will be isPlanned=true)
var firstList = new List<object> {
new { id =1, Name = "item-1", isPlanned=true, Date = "01.01.2017"},
new { id =2, Name = "item-2", isPlanned=true, Date = "01.02.2017"},
new { id =3, Name = "item-3", isPlanned=false },
new { id =4, Name = "item-4", isPlanned=false }
}
How can I do this linq functions or lambda operations?
You can use left join for this:
var items = new[] {
new { id =1, Name = "item-1"},
new { id =2, Name = "item-2"},
new { id =3, Name = "item-3"},
new { id =4, Name = "item-4"}
};
var issues = new[] {
new { id =1, Date = "01.01.2017"},
new { id =2, Date = "01.02.2017"}
};
var joined = from item in items
join issue in issues on item.id equals issue.id into gj
from sub in gj.DefaultIfEmpty()
select new { item.id, item.Name, isPlanned=sub?.Date != null, sub?.Date };
foreach (var t in joined) {
Console.WriteLine("{0} {1} {2} {3}", t.id, t.Name, t.isPlanned, t.Date);
}
The result will have nulls when dates are missing, i.e.
new { id =1, Name = "item-1", isPlanned=true, Date = "01.01.2017"},
new { id =2, Name = "item-2", isPlanned=true, Date = "01.02.2017"},
new { id =3, Name = "item-3", isPlanned=false, Date = null },
new { id =4, Name = "item-4", isPlanned=false, Date = null }
Demo.
If you wanted to use a lambda expression, you could do it the following way:
List<Foo> foos = new List<Foo>()
{
new Foo(){ Id = 1, Name = "item-1"},
new Foo(){ Id = 2, Name = "item-2"},
new Foo(){ Id = 3, Name = "item-3"},
new Foo(){ Id = 4, Name = "item-4"}
};
List<Bar> bars = new List<Bar>()
{
new Bar(){ Id = 1, Date = "01.01.2017"},
new Bar(){ Id = 2, Date = "01.02.2017"}
};
IEnumerable<dynamic> result = foos.GroupJoin(bars,
f => f.Id,
b => b.Id,
(foo, bar) => new
{
Id = foo.Id,
Name = foo.Name,
isPlanned = String.IsNullOrEmpty(bar.SingleOrDefault()?.Date),
Date = bar.SingleOrDefault()?.Date
});
here are the classes
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Date { get; set; }
}
I noticed also from the comments from another answer that you are not able to use C#6.0, so this section would not work for you
isPlanned = String.IsNullOrEmpty(bar.SingleOrDefault()?.Date),
Date = bar.SingleOrDefault()?.Date
You can replace that with the following:
isPlanned = bar.SingleOrDefault() == null ? false : String.IsNullOrEmpty(bar.SingleOrDefault().Date),
Date = bar.SingleOrDefault() == null ? null : bar.SingleOrDefault().Date

LINQ group list and combine collections

Given these classes:
public class Employee
{
public int EmployeeId { get; set; }
public int GroupId { get; set; }
public List<Plans> Plans { get; set; }
}
public class Plan
{
public int PlanYearId { get; set; }
public string Name { get; set; }
}
And given a setup like so:
var employees = new List<Employee> {
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
}}};
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 3"
}}}};
How can I use LINQ to group these employees by both EmployeeId and GroupId and then combine the two List<Plan> properties so that i would end up with something like this:
var employee = new Employee
{
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 3"
}
}
}
Just use combination of GroupBy and SelectMany:
var result = employees
.GroupBy(e => new { e.EmployeeId, e.GroupId })
.Select(g => new Employee
{
EmployeeId = g.Key.EmployeeId,
GroupId = g.Key.GroupId,
Plans = g.SelectMany(e => e.Plans).ToList()
}).ToList();

C# LINQ Take limited results per grouped

I have list that includes class named 'ID', 'Name' and 'Category'. There are 6 item in list.
List<MyData> list =
{
{0, "John", "Police"},
{1,"Michael", "Police"},
{2,"Alice", "Police"},
{3, "Ferdinand", "Thief"},
{4, "Jocas", "Thief"},
{5, "Connor", "Thief"}
};
I wanna list them with limited quantity per group by 'Category' with LINQ.
Example : I want list 2 item for each 'Cateogory'. Listed should be below :
John Police
Michael Police
Ferdinand Thief
Jocas Thief
Use combination of Take and SelectMany:
var results = list.GroupBy(x => x.Category).SelectMany(g => g.Take(2)).ToList();
I've tested it on following Item class:
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public string Category { get; set; }
}
And query:
List<Item> list = new List<Item>
{
new Item { ID = 0, Name = "John", Category = "Police"},
new Item { ID = 1, Name = "Michael", Category = "Police"},
new Item { ID = 2, Name = "Alice", Category = "Police"},
new Item { ID = 3, Name = "Ferdinand", Category = "Thief"},
new Item { ID = 4, Name = "Jocas", Category = "Thief"},
new Item { ID = 5, Name = "Connor", Category = "Thief"}
};
var results = list.GroupBy(x => x.Category).SelectMany(g => g.Take(2)).ToList();
Returns 4 elements, right as you want.

Categories