Complex Linq Grouping - c#

I'm new to Stack Overflow, but tried to put as much information
I have following class structure
public class ItemEntity
{
public int ItemId { get; set; }
public int GroupId { get; set; }
public string GroupName { get; set; }
public DateTime ItemDate { get; set; }
public string Field1 { get; set; }
public string Filed2 { get; set; }
public string Field3 { get; set; }
public string Field4 { get; set; }
public int Duration { get; set; }
}
public class MasterEntity
{
public ItemEntity Item { get; set; }
public List<int> ItemList { get; set; }
public List<int> GroupList { get; set; }
}
I am trying to group list of ItemEntity into MasterEntity. Grouping fileds are Field1,Field2 and Field3.
I have done the grouping so far like below
var items = new List<ItemEntity>
{
new ItemEntity
{
ItemId = 100,
GroupId = 1,
GroupName= "Group 1",
ItemDate = new DateTime(2018,10,17),
Duration = 7,
Field1 = "Item Name 1",
Filed2 = "aaa",
Field3= "bbb",
Field4= "abc"
},
new ItemEntity
{
ItemId = 150,
GroupId = 2,
GroupName= "Group 2",
ItemDate = new DateTime(2018,10,17),
Duration = 5,
Field1 = "Item Name 1",
Filed2 = "aaa",
Field3= "bbb",
Field4= "efg"
},
new ItemEntity
{
ItemId = 250,
GroupId = 3,
GroupName= "Group 3",
ItemDate = new DateTime(2018,10,15),
Duration = 7,
Field1 = "Item Name 1",
Filed2 = "aaa",
Field3= "bbb",
Field4= "xyz"
}
};
var group = items.GroupBy(g => new
{
g.Field1,
g.Filed2,
g.Field3
}).Select(s => new MasterEntity
{
Item = new ItemEntity
{
Field1 = s.Key.Field1,
Filed2 = s.Key.Filed2,
Field3 = s.Key.Field3
},
ItemList = s.Select(g => g.ItemId).ToList(),
GroupList = s.Select(g => g.GroupId).ToList()
}).ToList();
With in this group, I want to further split this by actual ItemDate and Duration so it looks like below
Basically, I want to split this group in to three in this case.
As only Group3 is having Date 15th to 17, it will be one group.
From 17th to 22nd Group1, Group2 and Group3 are same. so that will become another group.
And last only Group1 have 22nd to 24 so it become another group
Final grouped data to be like
G1
{
ItemEntity :{
ItemDate : 15/10/2018,
Duration : 2,
Field1 : "Item Name 1",
Filed2 : "aaa",
Field3 : "bbb",
},
ItemList: {250},
GroupList:{3}
}
,
G2
{
ItemEntity :{
ItemDate : 17/10/2018,
Duration : 5,
Field1 : "Item Name 1",
Filed2 : "aaa",
Field3 : "bbb",
},
ItemList: {100,150,250},
GroupList:{1,2,3}
}
,
G3
{
ItemEntity :{
ItemDate : 22/10/2018,
Duration : 2,
Field1 : "Item Name 1",
Filed2 : "aaa",
Field3 : "bbb",
},
ItemList: {100},
GroupList:{1}
}

This was pretty challenging. I used some convenient extension methods I already had to make it easier, and created a HashSet subclass that defaults to using SetEqual (.Net really needs some member equal collection classes built-in).
First, the class HashSetEq that implements equality when its members match:
public class HashSetEq<T> : HashSet<T>, IEquatable<HashSetEq<T>> {
private static readonly IEqualityComparer<HashSet<T>> SetEq = HashSet<T>.CreateSetComparer();
public override int GetHashCode() => SetEq.GetHashCode(this);
public override bool Equals(object obj) => obj != null && (obj is HashSetEq<T> hs) && this.Equals(hs);
public bool Equals(HashSetEq<T> other) => SetEq.Equals(this, other);
public HashSetEq(IEnumerable<T> src) : base(src) {
}
}
Now, some extensions to IEnumerable. One extension converts an IEnumerable to a HashSetEq for ease of creating a collection of keys. The other extension is a variation on GroupBy that groups while a predicate is true, based on an extension ScanPair that implements a pair-wise version of the APL Scan operator.
public static class IEnumerableExt {
public static HashSetEq<T> ToHashSetEq<T>(this IEnumerable<T> src) => new HashSetEq<T>(src);
// TKey combineFn((TKey Key, T Value) PrevKeyItem, T curItem):
// PrevKeyItem.Key = Previous Key
// PrevKeyItem.Value = Previous Item
// curItem = Current Item
// returns new Key
public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combineFn) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prevkv = (seedKey, srce.Current);
while (srce.MoveNext()) {
yield return prevkv;
prevkv = (combineFn(prevkv, srce.Current), srce.Current);
}
yield return prevkv;
}
}
}
public static IEnumerable<IGrouping<int, T>> GroupByWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) =>
src.ScanPair(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
.GroupBy(kvp => kvp.Key, kvp => kvp.Value);
}
In order to group the spans of dates, I expanded my GroupBySequential based on GroupByWhile inline so I could group by sequential date runs and matching sets of GroupIds. GroupBySequential depends on an integer sequence, so I need a base Date to compute a day sequence number so I use the earliest date in all the items:
var baseDate = items.Min(i => i.ItemDate);
Now I can compute the answer.
For each group of items, I expand each item out across all the dates it covers, based on Duration, and associated each date with the original item:
var group = items.GroupBy(g => new {
g.Field1,
g.Filed2,
g.Field3
})
.Select(g => g.SelectMany(i => Enumerable.Range(0, i.Duration).Select(d => new { ItemDate = i.ItemDate.AddDays(d), i }))
Now that I have all the individual date+item, I can group them for each date.
.GroupBy(di => di.ItemDate)
And then group each date+items on the date and set of groups for that date and order by the date.
.GroupBy(dig => new { ItemDate = dig.Key, Groups = dig.Select(di => di.i.GroupId).ToHashSetEq() })
.OrderBy(ig => ig.Key.ItemDate)
With them ordered by date, I can group the sequential dates together (using the number of days from the baseDate) that have the same Groups.
.GroupByWhile((prevg, curg) => (int)(prevg.Key.ItemDate - baseDate).TotalDays + 1 == (int)(curg.Key.ItemDate - baseDate).TotalDays && prevg.Key.Groups.Equals(curg.Key.Groups))
Finally, I can extract the information from each sequential date group into a MasterEntity and make it the whole answer a List.
.Select(igg => new MasterEntity {
Item = new ItemEntity {
ItemDate = igg.First().Key.ItemDate,
Duration = igg.Count(),
Field1 = g.Key.Field1,
Filed2 = g.Key.Filed2,
Field3 = g.Key.Field3
},
ItemList = igg.First().First().Select(di => di.i.ItemId).ToList(),
GroupList = igg.First().Key.Groups.ToList()
})
)
.ToList();

https://dotnetfiddle.net/fFtqgy
Okay so the example contains 3 parties going to a "hotel" as given in your explanation.
The groups are layed out below with the times the groups will arrive and depart from the hotel
Scenario
Group 1) 15th - 20th
Group 2) 17th - 19th
Group 3) 17th - 22nd
Result Groupings
15th - 17th: Group 1
17th - 19th: Groups 1, 2 , 3
19th - 20th: Groups 1, 3
20th - 22nd: Groups 3
Explanation
This depicts the groups that will be present in the hotel for each date, a new group is created each time a group joins or leaves the hotel, which is why the code joins all of the start and end dates for all of the groups and iterates through them.
I wasn't certain what to put for the GroupId and ItemID on the resulting MasterEntity since it contains a list of items and groups, so I've set it to negative 1 in the example
Code for fiddle
public static class Utilities
{
public static bool DatesOverlap(DateTime aStart, DateTime aEnd, DateTime bStart, DateTime bEnd)
{
return aStart < bEnd && bStart < aEnd;
}
public static IList<MasterEntity> GroupFunky(IList<ItemEntity> list)
{
var result = new List<MasterEntity>();
var ordered = list.OrderBy(x => x.ItemDate).ToArray();
var startDates = list.Select(x => x.ItemDate);
var endDates = list.Select(x => x.ItemDate.AddDays(x.Duration));
var allDates = startDates.Concat(endDates).OrderBy(x => x).ToArray();
for (var index = 0; index < allDates.Length - 1; index++)
{
var group = ordered.Where(x => DatesOverlap(allDates[index], allDates[index + 1], x.ItemDate,
x.ItemDate.AddDays(x.Duration)));
var item = new ItemEntity
{
Duration = (allDates[index + 1] - allDates[index]).Days,
ItemDate = allDates[index],
Field1 = group.First().Field1,
Field2 = group.First().Field2,
Field3 = group.First().Field3,
Field4 = group.First().Field4,
GroupName = group.First().GroupName,
ItemId = -1,
GroupId = -1
};
item.ItemDate = allDates[index];
item.Duration = (allDates[index + 1] - allDates[index]).Days;
result.Add(new MasterEntity
{
Item = item,
GroupList = group.Select(x => x.GroupId).ToList(),
ItemList = group.Select(x => x.ItemId).ToList()
});
}
return result.Where(x => x.Item.Duration > 0).ToList();
}
}
public class ItemEntity
{
public int ItemId { get; set; }
public int GroupId { get; set; }
public string GroupName { get; set; }
public DateTime ItemDate { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public string Field4 { get; set; }
public int Duration { get; set; }
}
public class MasterEntity
{
public ItemEntity Item { get; set; }
public List<int> ItemList { get; set; }
public List<int> GroupList { get; set; }
}
public class TestClass
{
public static void Main()
{
var items = new List<ItemEntity>
{
new ItemEntity
{
ItemId = 100,
GroupId = 1,
GroupName = "Group 1",
ItemDate = new DateTime(2018, 10, 15),
Duration = 5,
Field1 = "Item Name 1",
Field2 = "aaa",
Field3 = "bbb",
Field4 = "abc"
},
new ItemEntity
{
ItemId = 150,
GroupId = 2,
GroupName = "Group 2",
ItemDate = new DateTime(2018, 10, 17),
Duration = 2,
Field1 = "Item Name 1",
Field2 = "aaa",
Field3 = "bbb",
Field4 = "efg"
},
new ItemEntity
{
ItemId = 250,
GroupId = 3,
GroupName = "Group 3",
ItemDate = new DateTime(2018, 10, 17),
Duration = 5,
Field1 = "Item Name 1",
Field2 = "aaa",
Field3 = "bbb",
Field4 = "xyz"
}
};
var group = items.GroupBy(g => new
{
g.Field1,
g.Field2,
g.Field3
})
.Select(x => x.AsQueryable().ToList())
.ToList();
var result = group.Select(x => Utilities.GroupFunky(x));
foreach (var item in result)
{
Console.WriteLine(JsonConvert.SerializeObject(item, Formatting.Indented));
}
}
}

Related

Split a single list to 'x' numbers of lists during runtime? [duplicate]

I have an object:
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
}
I return a list that may look like the following:
List<Customer> CustomerList = new List<Customer>();
CustomerList.Add( new Customer { ID = 1, Name = "One", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 2, Name = "Two", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 3, Name = "Three", GroupID = 2 } );
CustomerList.Add( new Customer { ID = 4, Name = "Four", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 5, Name = "Five", GroupID = 3 } );
CustomerList.Add( new Customer { ID = 6, Name = "Six", GroupID = 3 } );
I want to return a linq query which will look like
CustomerList
GroupID =1
UserID = 1, UserName = "UserOne", GroupID = 1
UserID = 2, UserName = "UserTwo", GroupID = 1
UserID = 4, UserName = "UserFour", GroupID = 1
GroupID =2
UserID = 3, UserName = "UserThree", GroupID = 2
GroupID =3
UserID = 5, UserName = "UserFive", GroupID = 3
UserID = 6, UserName = "UserSix",
I tried from
Using Linq to group a list of objects into a new grouped list of list of objects
code
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID)
.Select(grp => grp.ToList())
.ToList();
works but does not give the desired output.
var groupedCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(grp =>new { GroupID =grp.Key, CustomerList = grp.ToList()})
.ToList();
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>{
u.Name = "User" + u.Name;
return u;
}, (key,g)=>g.ToList())
.ToList();
If you don't want to change the original data, you should add some method (kind of clone and modify) to your class like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public Customer CloneWithNamePrepend(string prepend){
return new Customer(){
ID = this.ID,
Name = prepend + this.Name,
GroupID = this.GroupID
};
}
}
//Then
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>u.CloneWithNamePrepend("User"), (key,g)=>g.ToList())
.ToList();
I think you may want to display the Customer differently without modifying the original data. If so you should design your class Customer differently, like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public string Prefix {get;set;}
public string FullName {
get { return Prefix + Name;}
}
}
//then to display the fullname, just get the customer.FullName;
//You can also try adding some override of ToString() to your class
var groupedCustomerList = CustomerList
.GroupBy(u => {u.Prefix="User", return u.GroupID;} , (key,g)=>g.ToList())
.ToList();
is this what you want?
var grouped = CustomerList.GroupBy(m => m.GroupID).Select((n) => new { GroupId = n.Key, Items = n.ToList() });
var result = from cx in CustomerList
group cx by cx.GroupID into cxGroup
orderby cxGroup.Key
select cxGroup;
foreach (var cxGroup in result) {
Console.WriteLine(String.Format("GroupID = {0}", cxGroup.Key));
foreach (var cx in cxGroup) {
Console.WriteLine(String.Format("\tUserID = {0}, UserName = {1}, GroupID = {2}",
new object[] { cx.ID, cx.Name, cx.GroupID }));
}
}
The desired result can be obtained using IGrouping, which represents a collection of objects that have a common key in this case a GroupID
var newCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(group => new { GroupID = group.Key, Customers = group.ToList() })
.ToList();

How to fetch records without Duplicates using Linq

Hi can someone assist please,i have a list that contain my promotions codes and in the list i would like to return only promotion codes that appear once i.e dont have duplicates,please see below data from JSON,i would like to return Promotion code A123 and B500 and store them in another list.
[
{
"PromCode": "A123",
"Priority": 1,
"offer": "Win a Free Cap",
"StartDte": "2020-08-11T00:16:23.184Z",
"endDte": "2020-09-10T17:16:23.184Z",
},
{
"PromCode": "A100",
"Priority": 1,
"offer": "Win a perfume",
"StartDte": "2020-08-11T00:16:23.184Z",
"endDte": "2020-09-10T17:16:23.184Z",
},
{
"PromCode": "A100",
"Priority": 2,
"offer": "Win a Phone pouch",
"StartDte": "2020-09-11T00:16:23.184Z",
"endDte": "2020-10-10T17:16:23.184Z",
},
{
"PromCode": "B500",
"Priority": 1,
"offer": "Win a free router",
"StartDte": "2020-08-11T00:16:23.184Z",
"endDte": "2020-09-10T17:16:23.184Z",
},
]
I have a list that contains all this promotion code as seen below
var existingProms = await _Repo.GetAllPromCodes(promCodeList);
i tried to get ones that appear once in the list like this
var appearOnce = existingProms.Count(x => existingBnplIndicators.Contains(x.PromCode)).ToList()<2;
var appearOnce = existingProms.where(x=> x.PromCode.Count()).ToList()<2;
But this did not work,there is 0 results returned,could someone show how to get my two Proms A123,B500 into my appearOnce lis.Thankst
You can use GroupBy to get all the results grouped by PromoCode. Then, filter the results based on the number of items each group has to only show when Count() == 1.
Something like this perhaps,
public class Class1
{
public string PromCode { get; set; }
public int Priority { get; set; }
public string offer { get; set; }
public DateTime StartDte { get; set; }
public DateTime endDte { get; set; }
}
var obj = JsonConvert.DeserializeObject<List<Class1>>(json);
var singlesOnly = obj.GroupBy(x => x.PromCode).Where(y => y.Count() == 1);
You should compare desired objects only by promcode implicitly. Take look how Equals and GetHashCode() works.
using System;
using System.Collections.Generic;
using System.Linq;
namespace test
{
public class TestObj : IEquatable<TestObj>
{
public string Promocode { get; set; }
public int Priority { get; set; }
public string offer { get; set; }
public DateTime StartDate { get; set; }
public DateTime endDate { get; set; }
public override int GetHashCode() => this.Promocode.GetHashCode();
public bool Equals(TestObj other) => Promocode.Equals(other.Promocode);
}
class Program
{
static void Main(string[] args)
{
var a = new TestObj()
{
Promocode = "1",
Priority = 1,
offer = "1",
StartDate = new DateTime(1, 2, 3),
endDate = new DateTime(1, 2, 3)
};
var b = new TestObj()
{
Promocode = "1",
Priority = 1,
offer = "1",
StartDate = new DateTime(1, 2, 3),
endDate = new DateTime(1, 2, 3)
};
var c = new TestObj()
{
Promocode = "2",
Priority = 1,
offer = "1",
StartDate = new DateTime(1, 2, 3),
endDate = new DateTime(1, 2, 3)
};
var list = new List<TestObj>()
{
a,
b,
c
};
var uniqueOnly = list.Distinct();
foreach (var item in uniqueOnly)
{
Console.WriteLine(item.Promocode);
}
}
}
}
First define class items-
public string PromCode { get; set; }
public int Priority { get; set; }
public string offer { get; set; }
public DateTime StartDte { get; set; }
public DateTime endDte { get; set; }
Then take multiple items in list and use distinct() function to remove duplicate values-
static void Main(string[] args)
{
List<TestClass> list = new List<TestClass>()
{
new TestClass(){PromCode="A123",Priority=1,offer="Win a Free
Cap",StartDte=new DateTime(2020-08-11),endDte=new DateTime(2020-09-10)},
new TestClass(){PromCode="A100",Priority=1,offer="Win a
perfume",StartDte=new DateTime(2020-08-11),endDte=new DateTime(2020-09-
10)},
new TestClass(){PromCode="A100",Priority=2,offer="Win a Phone
pouch",StartDte=new DateTime(2020-09-11),endDte=new DateTime(2020-10-10)},
new TestClass(){PromCode="B500",Priority=1,offer="Win a free
router",StartDte=new DateTime(2020-08-11),endDte=new DateTime(2020-09-10)}
};
var finalList = list.Select(b => b.PromCode).Distinct().ToList();
foreach(var item in finalList)
{
Console.WriteLine(item + "");
}
Console.Read();
}
}

How to query with Linq a List that contains a list of lists that contains a list of lists that contains a list of objects?

I need a list that contains a list of lists, that contains a list of lists, that contains a list of objects. And I believe that we can achieve this using Linq. But I don't know-how!
Here I left posted a diagram for a better understanding of what I need.
https://i.gyazo.com/fe52e851024b0b13e6d39eeb533c43f2.png
I have an object:
public class Question
{
public int ModuleID { get; set; }
public int GroupID { get; set; }
public int QuestionID { get; set; }
}
I return a list that may look like this:
List<Question> questionList = new List<Question>();
QuestionList.Add( new Question{ ModuleID = 1, GroupID = 1, QuestionID = 1 } );
QuestionList.Add( new Question{ ModuleID = 2, GroupID = 1, QuestionID = 2 } );
QuestionList.Add( new Question{ ModuleID = 3, GroupID = 2, QuestionID = 3 } );
QuestionList.Add( new Question{ ModuleID = 4, GroupID = 4, QuestionID = 4 } );
What I have tried:
var groupedCustomerList = userList
.GroupBy(u => u.GroupID)
.Select(grp => grp.ToList())
.ToList();
From:
Using Linq to group a list of objects into a new grouped list of list of objects
And I have tried:
var groupedCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(grp =>new { GroupID =grp.Key, CustomerList = grp.ToList()})
.ToList();
From:
Using LINQ to group a list of objects
Here's a small console app which I think demos how to get the hierarchical grouping you're after:
internal class Program
{
public class Module
{
public int ModuleID { get; set; }
public List<Group> Groups { get; set; }
}
public class Group
{
public int GroupID { get; set; }
public int ModuleID { get; set; }
public List<Question> Questions { get; set; }
}
public class Question
{
public int ModuleID { get; set; }
public int GroupID { get; set; }
public int QuestionID { get; set; }
}
private static void Main(string[] args)
{
var questions = new List<Question>();
questions.Add(new Question {ModuleID = 1, GroupID = 1, QuestionID = 1});
questions.Add(new Question {ModuleID = 2, GroupID = 1, QuestionID = 2});
questions.Add(new Question {ModuleID = 3, GroupID = 2, QuestionID = 3});
questions.Add(new Question {ModuleID = 4, GroupID = 4, QuestionID = 4});
var groups = questions
.GroupBy(q => new {q.GroupID, q.ModuleID})
.Select(qg => new Group
{
GroupID = qg.Key.GroupID,
ModuleID = qg.Key.ModuleID,
Questions = qg.ToList()
})
.GroupBy(g => g.ModuleID)
.Select(gg => new Module
{
ModuleID = gg.Key,
Groups = gg.ToList()
})
.ToList();
}
}
The most important line here is probably .GroupBy(q => new {q.GroupID, q.ModuleID}). Grouping by the GroupId along is not enough, because we also need to consider if Questions are in different Modules. e.g. In the example of the above test data this would surface by erroneously grouping together questions #1 and #2 because they both have the same GroupID, ignoring the fact that they are in different Modules.
To solve this we create a new anonymous object, containg both the GroupID and ModuleID of the question (new {q.GroupID, q.ModuleID})) and group on that anonymous object instead.
Get you types correct. Your list has integers but the class has strings. Use IEquatable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Question> questionList = new List<Question>();
questionList.Add(new Question { ModuleID = "1", GroupID = "1", QuestionID = 1 });
questionList.Add(new Question { ModuleID = "2", GroupID = "1", QuestionID = 2 });
questionList.Add(new Question { ModuleID = "3", GroupID = "2", QuestionID = 3 });
questionList.Add(new Question { ModuleID = "4", GroupID = "4", QuestionID = 4 });
questionList.Add(new Question { ModuleID = "4", GroupID = "4", QuestionID = 4 });
List<List<Question>> results = questionList.GroupBy(x => x).Select(x => x.ToList()).ToList();
}
}
public class Question : IEquatable<Question>
{
public string ModuleID { get; set; }
public string GroupID { get; set; }
public int QuestionID { get; set; }
public Boolean Equals(Question other)
{
return
(ModuleID == other.ModuleID) &&
(GroupID == other.GroupID) &&
(QuestionID == other.QuestionID);
}
public override int GetHashCode()
{
return (ModuleID + "^" + GroupID + "^" + QuestionID.ToString()).GetHashCode();
}
}
}

Convert flat db row data to nested typed objects linq

I'm getting the results of a sql outer join as flat results in an IEnumerable, and would like to convert them to nested typed objects in linq. From something like this:
[{id: 1, industryId: 1}, {id:1, industryId: 2}, {id:2, industryId: 1} etc..]
to something like this:
list of Company [{id: 1, list of Industry{industryId: 1, 2}, {id: 2, list of Industry{industryId: 1}}]
I'm currently trying a solution with GroupBy:
Companies = flatDbRows
.GroupBy(
row => row.CompanyId,
(key, value) => new CompanyModel
{
CompanyId = value.First().CompanyId,
CompanyName = value.First().CompanyName,
Industries = value
.GroupBy(
row => new { row.IndustryId, row.Industry },
(k, v) => new IndustryModel() { IndustryId = k.IndustryId, Name = k.Industry }
)
.Where(x => x.IndustryId != 0)
.ToList(),
}).ToList();
}
but it doesn't feel great, especially with all the value.First() I'm using to get the values that only belong to each grouped company. Is there something more appropriate? Group join sounded more like what I wanted, but I'm having trouble understanding how to apply it to a single list. I'm open to using query syntax instead of the lambdas if that's easier.
I'm trying to go from this model (where company-related info will be duplicated for each outer joined industry result):
public class CompanyFlatDbRowsModel
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public int IndustryId{ get; set; }
public string Industry { get; set; }
}
to this:
public class CompanyModel
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public IEnumerable<IndustryModel> Industries { get; set; }
}
// FULL edit after providing your models
public class TestClass
{
public class CompanyModel
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public List<IndustryModel> Industires { get; set; }
}
public class IndustryModel
{
public int IndustryId { get; set; }
public string IndustryName { get; set; }
}
public class CompanyFlatDbRowsModel
{
public CompanyFlatDbRowsModel()
{
}
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public int IndustryId { get; set; }
public string Industry { get; set; }
}
[Fact]
public void Test()
{
var data = new List<CompanyFlatDbRowsModel>
{
new CompanyFlatDbRowsModel
{
CompanyId = 1,
CompanyName = "Company 1",
IndustryId = 1,
Industry = "Industry 1"
},
new CompanyFlatDbRowsModel
{
CompanyId = 1,
CompanyName = "Company 1",
IndustryId = 2,
Industry = "Industry 2"
},
new CompanyFlatDbRowsModel
{
CompanyId = 2,
CompanyName = "Company 2",
IndustryId = 3,
Industry = "Industry 3"
},
new CompanyFlatDbRowsModel
{
CompanyId = 2,
CompanyName = "Company 2",
IndustryId = 4,
Industry = "Industry 4"
},
};
var result = data.GroupBy(x => x.CompanyId)
.Select(x => new CompanyModel()
{
CompanyId = x.Key,
CompanyName = x.First().CompanyName,
Industires = x.Select(y=> new IndustryModel
{
IndustryName = y.Industry,
IndustryId = y.IndustryId
}).ToList()
}).ToList();
foreach (var item in result)
{
var text = $"Company id : {item.CompanyId}, industries : {string.Join(',',item.Industires.Select(x=>$"(name: {x.IndustryName}, id: {x.IndustryId})"))}";
Debug.WriteLine(text);
}
}
}
output:
Company id : 1, industries : (name: Industry 1, id: 1),(name: Industry 2, id: 2)
Company id : 2, industries : (name: Industry 3, id: 3),(name: Industry 4, id: 4)
edit:
alternatively you can do as below, however the "first" thing still occurs somewhere, I have tried also the GroupJoin but it doesn't really help in that case.
var otherResult = data.Select(x =>
new CompanyModel
{
CompanyId = x.CompanyId,
CompanyName = x.CompanyName,
Industires = data
.Where(y => y.CompanyId == x.CompanyId)
.Select(y => new IndustryModel
{
IndustryId = y.IndustryId,
IndustryName = y.Industry
}).ToList()
})
.GroupBy(y => y.CompanyId)
.Select(x => x.First())
.ToList();
edit:
one more approach without using "first"
var anotherResult = data.GroupBy(x => x.CompanyId)
.Select(x =>
{
var companyModel = new CompanyModel()
{
CompanyId = x.Key
};
companyModel.Industires = x.Select(y =>
{
companyModel.CompanyName = y.CompanyName; // assignign here occurs multiple times however with the same value
return new IndustryModel
{
IndustryId = y.IndustryId,
IndustryName = y.Industry
};
}).ToList();
return companyModel;
}).ToList();

Using LINQ to group a list of objects

I have an object:
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
}
I return a list that may look like the following:
List<Customer> CustomerList = new List<Customer>();
CustomerList.Add( new Customer { ID = 1, Name = "One", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 2, Name = "Two", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 3, Name = "Three", GroupID = 2 } );
CustomerList.Add( new Customer { ID = 4, Name = "Four", GroupID = 1 } );
CustomerList.Add( new Customer { ID = 5, Name = "Five", GroupID = 3 } );
CustomerList.Add( new Customer { ID = 6, Name = "Six", GroupID = 3 } );
I want to return a linq query which will look like
CustomerList
GroupID =1
UserID = 1, UserName = "UserOne", GroupID = 1
UserID = 2, UserName = "UserTwo", GroupID = 1
UserID = 4, UserName = "UserFour", GroupID = 1
GroupID =2
UserID = 3, UserName = "UserThree", GroupID = 2
GroupID =3
UserID = 5, UserName = "UserFive", GroupID = 3
UserID = 6, UserName = "UserSix",
I tried from
Using Linq to group a list of objects into a new grouped list of list of objects
code
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID)
.Select(grp => grp.ToList())
.ToList();
works but does not give the desired output.
var groupedCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(grp =>new { GroupID =grp.Key, CustomerList = grp.ToList()})
.ToList();
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>{
u.Name = "User" + u.Name;
return u;
}, (key,g)=>g.ToList())
.ToList();
If you don't want to change the original data, you should add some method (kind of clone and modify) to your class like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public Customer CloneWithNamePrepend(string prepend){
return new Customer(){
ID = this.ID,
Name = prepend + this.Name,
GroupID = this.GroupID
};
}
}
//Then
var groupedCustomerList = CustomerList
.GroupBy(u => u.GroupID, u=>u.CloneWithNamePrepend("User"), (key,g)=>g.ToList())
.ToList();
I think you may want to display the Customer differently without modifying the original data. If so you should design your class Customer differently, like this:
public class Customer {
public int ID { get; set; }
public string Name { get; set; }
public int GroupID { get; set; }
public string Prefix {get;set;}
public string FullName {
get { return Prefix + Name;}
}
}
//then to display the fullname, just get the customer.FullName;
//You can also try adding some override of ToString() to your class
var groupedCustomerList = CustomerList
.GroupBy(u => {u.Prefix="User", return u.GroupID;} , (key,g)=>g.ToList())
.ToList();
is this what you want?
var grouped = CustomerList.GroupBy(m => m.GroupID).Select((n) => new { GroupId = n.Key, Items = n.ToList() });
var result = from cx in CustomerList
group cx by cx.GroupID into cxGroup
orderby cxGroup.Key
select cxGroup;
foreach (var cxGroup in result) {
Console.WriteLine(String.Format("GroupID = {0}", cxGroup.Key));
foreach (var cx in cxGroup) {
Console.WriteLine(String.Format("\tUserID = {0}, UserName = {1}, GroupID = {2}",
new object[] { cx.ID, cx.Name, cx.GroupID }));
}
}
The desired result can be obtained using IGrouping, which represents a collection of objects that have a common key in this case a GroupID
var newCustomerList = CustomerList.GroupBy(u => u.GroupID)
.Select(group => new { GroupID = group.Key, Customers = group.ToList() })
.ToList();

Categories

Resources