Group by List using inner list - c#

Below is my List collection.
public class A
{
public String id{ get; set; }
public string name{ get; set; }
public List<B> nestedList { get; set; }
}
public class B
{
public String innerid{ get; set; }
public string innername { get; set; }
}
I want to use group by on nested collection properties
So I can have output as
innerid="1",
innername="Name1",
{
id= "1", name= "One"
id= "2", name= "Two"
id= "4", name= "Four"
}
innerid="2",
innername="Name2",
{
id= "3", name= "Three"
id= "6", name= "Six"
id= "8", name= "Eight"
}
I tried
.GroupBy(a => a.nestedList.First().innerid).ToList()
But I am not getting required output.

You want to reverse the hierarchy, so grouping by the nested-B and list the parent-A as children?
var groups = aList
.SelectMany(a => a.nestedList.Select(b => new { A = a, B = b }))
.GroupBy(x => new { x.B.innerid, x.B.innername })
.Select(g => new {
g.Key.innerid,
g.Key.innername,
aItems = g.Select(x => new { x.A.id, x.A.name })
});
Instead of selecting anonymous types you can also select the original A and B instances.

Assuming the items from the result must contain items of type A, you could use query syntax to solve this:
var grouping = from a in collectionOfA
from b in a.nestedList
group a by new { b.innerid, b.innername } into g
select new
{
innerid = g.Key.innerid,
innername = g.Key.innername,
items = g.ToList()
};

Related

Cannot group data in LINQ

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.

C# aggregating attributes in a list

I have an object like as shown below
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
In the above aggregation of the PgId values grouped by MsfId. Same is the case with DcId as well.
For example:
MsfId: 100
PgId: "abc"
DcId: "123"
MsfId: 100
PgId: "def"
DcId: "456"
MsfId: 100
PgId: "ghi"
DcId: "789"
MsfId: 101
PgId: "abc"
DcId: "123"
How to write a LINQ query to aggregate this and create a list of SampleObjects like below?
MsfId: 100
PgId: "abc", "def", "ghi"
DcId: "123", "456", "789"
MsfId: 101
PgId: "abc"
DcId: "123"
Try aggregation like this:
var result = col.GroupBy(x => x.MsfId)
.Select(x => new SampleObject {
MsfId = x.Key,
PgCodes = x.Select(t=>t.PgId).ToList(),
DcCodes = x.Select(t=>t.DcId).ToList()
});
Scenario 1
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
Scenario 2
public class SampleObjectSource
{
public int MsfId { get; set; }
public string PgId { get; set; }
public string DcId { get; set; }
}
Scenario 1 Answer
var collection = new List<SampleObject>();
var result = collection.GroupBy(y => y.MsfId)
.Select(y => new SampleObject
{
MsfId = y.Key,
PgId = y.SelectMany(g => g.PgId).Distinct().ToList(),
}).ToList();
Scenario 2
var collection2 = new List<SampleObjectSource>();
var result1 = collection2.GroupBy(y => y.MsfId)
.Select(y => new SampleObject
{
MsfId = y.Key,
PgId = y.Select(h => h.PgId).Distinct().ToList(),
}).ToList();
Update : Please see the dotnetfiddle
You need to group the items with a query. This linq grouping will create a collection of collections(with a key)
I just made a full working example:
// The class to start with
public class SampleObjectSource
{
public int MsfId { get; set; }
public string PgId { get; set; }
public string DcId { get; set; }
}
// the result class
public class SampleObject
{
public int MsfId { get; set; }
public List<string> PgId { get; set; }
public List<string> DcId { get; set; }
}
// for example:
public class Example
{
public Example()
{
// create a list that contains the items.
var list = new List<SampleObjectSource>
{
new SampleObjectSource { MsfId= 100, PgId= "abc", DcId= "123" },
new SampleObjectSource { MsfId= 100, PgId= "def", DcId= "456" },
new SampleObjectSource { MsfId= 100, PgId= "ghi", DcId= "789" },
new SampleObjectSource { MsfId= 101, PgId= "abc", DcId= "123" },
};
// the linq query that does the grouping.
var query = from item in list
// group the items by MsfId
group item by item.MsfId into itemgroup
// create the new class and initialize the properties
select new SampleObject
{
// the grouping item is the .Key (in this case MsfId)
MsfId = itemgroup.Key,
// the itemgroup is a collection of all grouped items, so you need to select the properties you're interrested in.
DcId = itemgroup.Select(i => i.DcId).ToList(),
PgId = itemgroup.Select(i => i.PgId).ToList()
};
// show the results in the Output window.
foreach (var item in query)
{
Trace.WriteLine($"MsfId: {item.MsfId}");
// some trick to format a list of strings to one string
Trace.WriteLine($"PgId: {string.Join(", ", item.PgId.Select(s => Quote(s)))}");
Trace.WriteLine($"DcId: {string.Join(", ", item.DcId.Select(s => Quote(s)))}");
Trace.WriteLine("");
}
}
// this method will surround the passed string with quotes.
private string Quote(string item)
{
return "\"" + item + "\"";
}
}
results:
MsfId: 100
PgId: "abc", "def", "ghi"
DcId: "123", "456", "789"
MsfId: 101
PgId: "abc"
DcId: "123"
Do it all with one GroupBy using the appropriate overload. Working Fiddle Here.
Note the use of SelectMany to concatenate the grouped collections into one.
var result = sampleObjects
.GroupBy(
o => o.MsfId,
(k, g) => new SampleObject
{
MsfId = k,
PgId = g.SelectMany(p => p.PgId).ToList(),
DcId = g.SelectMany(p => p.DcId).ToList()
});
If you want to remove duplicates from the collections consider Distinct() e.g.
var result = sampleObjects
.GroupBy(
o => o.MsfId,
(k, g) => new SampleObject
{
MsfId = k,
PgId = g.SelectMany(p => p.PgId).Distinct().ToList(),
DcId = g.SelectMany(p => p.DcId).Distinct().ToList()
});

The most frequently occurring item in a list

I have a list in this table
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
I want to know what are the most frequent fruit in this table what is the code that appears to me this result
I am use
var max = db.Fruits.Max();
There is an error in that?
Try
public class Fruits
{
public int ID { get; set; }
public string Name{ get; set; }
}
var Val = fruitList.GroupBy(x => x.ID,
(key, y) => y.MaxBy(x => x.ID).value)
As Drew said in the comments, you want to GroupBy on the value that you care about (I did Name, since ID tends to be unique in most data structures) and then OrderByDescending based on the count.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var fruits = new List<Fruit> { new Fruit { ID = 1, Name = "Apple" }, new Fruit { ID = 2, Name = "Apple" }, new Fruit { ID = 3, Name = "Pear" } };
var most = fruits.GroupBy(f => f.Name).OrderByDescending(group => group.Count());
Console.WriteLine(most.First().Key);
}
}
public class Fruit
{
public int ID { get; set; }
public string Name{ get; set; }
}
If you want to get the name of the item that exists most in your list, first find the id that is most occurring:
var fruitAnon = fruits
.GroupBy(item => item.ID)
.Select(item => new {
Key = item.Key,
Count = item.Count()
})
.OrderByDescending(item => item.Count)
.FirstOrDefault();
This will return an anonymous object that will have the most frequent id, and the count represents the number of times it exists in the list. You can then find that object's name:
var fruit = fruits.FirstOrDefault(x => x.ID == fruitAnon.Key);
If you had a list like this:
List<Fruits> fruits = new List<Fruits>() {
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 1, Name = "Apple" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" },
new Fruits { ID = 2, Name = "Orange" }
};
Then:
Console.WriteLine(fruit.Name);
Would print Orange.

Linq query to get the distinct values for repeated id's in a list

Suppose this is my member class
class Member
{
public string CategoryId { get; set; }
public string MemberName { get; set; }
public int Distance { get; set; }
}
And, this is list.
var list = new List<Member>();
list.Add(new { CategoryId = "01", MemberName = "andy", Distance = 3 });
list.Add(new { CategoryId = "02", MemberName = "john", Distance = 5 });
list.Add(new { CategoryId = "01", MemberName = "mathew", Distance = 7 });
list.Add(new { CategoryId = "03", MemberName = "bakara", Distance = 2 });
Can anyone please suggest the logic/ linq query to get the List having distinct/unique categoryID with respective MemberNames separated with comma
The output should be :
list.Add(new { CategoryId = "01", MemberName="andy,mathew"});
list.Add(new { CategoryId = "02", MemberName="john"});
list.Add(new { CategoryId = "03", MemberName="bakara"});
You need to group by CategoryId and then join the MemberName values for each group like this:
var result =
list.GroupBy(member => member.CategoryId)
.Select(group => new //Do you want an anonymous type or a Member object?
{
CategoryId = group.Key,
MemberName = string.Join(",", group.Select(member => member.MemberName))
})
.ToList();

Intersect two generic lists by dynamic properties

i have two generic lists with a few properties to compare but i want that the key identifiers are dynamic by a List<string>.
So lets say we have the class:
class A
{
string Name { get; set; }
string Color1 { get; set; }
string Color2 { get; set; }
string Length { get; set; }
}
The user now can select from an user interface which properties of two lists of those objects need to overlap so that a correct pair is selected. This is stored in a List<string>. As example, if the list string contains "Name" and "Color1" there will be only objects returned where "Name" and "Color1" are overlapping.
I was trying to write a function, but unfortunately i'm not sure which collection i should cast the generic lists to and how do i apply the names of the properties on those? If the name of the "identificators" were always the same, it wouldn't be a problem with Linq/Lambda ;)
Thanks in advance
You need to use reflection for this. This works:
public class A
{
public string Name { get; set; }
public string Color1 { get; set; }
public string Color2 { get; set; }
public string Length { get; set; }
public static IEnumerable<A> Intersecting(IEnumerable<A> input, List<string> propertyNames)
{
if(input == null)
throw new ArgumentNullException("input must not be null ", "input");
if (!input.Any() || propertyNames.Count <= 1)
return input;
var properties = typeof(A).GetProperties();
var validNames = properties.Select(p => p.Name);
if (propertyNames.Except(validNames, StringComparer.InvariantCultureIgnoreCase).Any())
throw new ArgumentException("All properties must be one of these: " + string.Join(",", validNames), "propertyNames");
var props = from prop in properties
join name in validNames.Intersect(propertyNames, StringComparer.InvariantCultureIgnoreCase)
on prop.Name equals name
select prop;
var allIntersecting = input
.Select(a => new {
Object = a,
FirstVal = props.First().GetValue(a, null),
Rest = props.Skip(1).Select(p => p.GetValue(a, null)),
})
.Select(x => new {
x.Object, x.FirstVal, x.Rest,
UniqueValues = new HashSet<object>{ x.FirstVal }
})
.Where(x => x.Rest.All(v => !x.UniqueValues.Add(v)))
.Select(x => x.Object);
return allIntersecting;
}
}
Sample data:
var aList = new List<A> {
new A { Color1 = "Red", Length = "2", Name = "Red" }, new A { Color1 = "Blue", Length = "2", Name = "Blue" },
new A { Color1 = "Red", Length = "2", Name = "A3" }, new A { Color1 = "Blue", Length = "2", Name = "A3" },
new A { Color1 = "Red", Length = "3", Name = "Red" }, new A { Color1 = "Blue", Length = "2", Name = "A6" },
};
var intersecting = A.Intersecting(aList, new List<string> { "Color1", "Name" }).ToList();

Categories