Grouping Linq Issue, can't get it right - c#

public class Emp
{
public int EmpId { get; set; }
public string Type { get; set; }
public List<string> Email { get; set; }
}
I fetch data from database and put it in a list
List<Emp> employees= new List<Emp>();
// fill the list here via DB call
The list would have, please also note that Type field would always be same for same user but email would be different
employees[0] = new Emp{ EmpId = 1, Type = "User", Email = "one#test.com" };
employees[1] = new Emp{ EmpId = 1, Type= "User", Email = "two#test.com" };
employees[2] = new Emp{ EmpId = 2, Type = "Test", Email = "three#test.com" };
employees[3] = new Emp{ EmpId = 2, Type= "Test", Email = "four#test.com"};
employees[4] = new Emp{ EmpId = 3, Type = "Test", Email = "five#test.com" };
employees[5] = new Emp{ EmpId = 3, Type= "Test", Email = "six#test.com"};
employees[6] = new Emp{ EmpId = 4, Type= "User", Email = "seven#test.com"};
I'm trying to group Emp based on their EmpId
so the result should be a new list
Expected Result
Result = new Emp{ EmpId = 1, Type = "User", Email = "one#test.com", "two#test.com" };
new Emp{ EmpId = 2, Type = "Test", Email = "three#test.com", "four#test.com" };
new Emp{ EmpId = 3, Type = "Test", Email = "five#test.com", "six#test.com" };
new Emp{ EmpId = 4, Type = "User", Email = ""seven#test.com" };
//This is what I have done so far
// Please let me know if this is incorrect
var result = from emp in employees
group emp.Email by new { emp.EmpId, emp.Type } into g
select new { Key = g.Key, Type = g.Key.Type, Emails = g.ToList() };
// My problem comes here when I loop this result
foreach (var r in result)
{
Console.WriteLine(r.Key.EmpId + "--" + r.Key.Type);
//This is where I need to get all emails of the Employee which I grouped
// iF I IMPLEMENT FOREACH
foreach (var e in r.Emails)
{
//?? WHAT i DO HERE
//e.?? to get email
}
// OR iF I IMPLEMENT FOR LOOP
for(int i = 0 ; i< r.Emails.Count; i++)
{
Console.WriteLine("Inner Loop" + "--" + r.Key.EmpId + "--" + r.Key.Type + "--" + r.Emails[0].ToString()); // r.Emails[0].ToString() prints out System.Collections.Generic.List '1[System.String]
}
}
Please let me know if I mad eany mistake or there's other way to do this.
All I need is Group employees based on EmpID and also have their Type but grouped Emails.

Your group emp.Email by new { emp.EmpId, emp.Type } means that each element of the group will have a key of the anonymous type, and an "element type" of List<string>. You're then propagating that element type using Emails = g.ToList() in your select clause. Therefore I'd expect the type of r.Emails to be List<List<string>> (which you should be able to validate in Visual Studio by hovering over r.Emails.
You could handle that in your loop - or you could just flatten it in your select call, creating a new Emp:
select new Emp {
EmpId = g.Key.EmpId,
Type = g.Key.Type,
Emails = g.SelectMany(x => x).ToList()
};
Here the SelectMany call is just flattening the "sequence of lists" to a single sequence.

Maybe a slight change could be usefull: if you change the Email property from a list to string you could group the employees like that:
// Group by EmpId
var group = employees.GroupBy(e => e.EmpId);
and get the list for a single emp like this:
// Example get email List of first emp
group.First().Select(g => g.Email);

You could change your Emp class so that EMail is a string instead of a List<string>.
The foreach loop then becomes
foreach(string e in r.EMails){
//e holds the EMail
//do stuff
}

Try This:-
var query1 = from emp in employees
group emp.Email by new { emp.EmpId, emp.Type } into empgroup
select new
{
UserId = empgroup.Key.EmpId,
EmployeeType = empgroup.Key.Type,
EmaiIds = empgroup.SelectMany(x => x)
};
foreach (var x in query1)
{
Console.WriteLine(x.UserId);
Console.WriteLine(x.EmployeeType);
foreach (var emails in x.EmaiIds)
{
Console.WriteLine(emails);
}
}

void Main()
{
List<Emp> employees= new List<Emp>();
employees.Add(new Emp{ EmpId = 1, Type = "User", Email = "one#test.com" });
employees.Add(new Emp{ EmpId = 1, Type = "User", Email = "two#test.com" });
employees.Add(new Emp{ EmpId = 2, Type = "Test", Email = "three#test.com" });
employees.Add(new Emp{ EmpId = 2, Type = "Test", Email = "four#test.com" });
employees.Add(new Emp{ EmpId = 3, Type = "Test", Email = "five#test.com" });
employees.Add(new Emp{ EmpId = 3, Type = "Test", Email = "six#test.com" });
employees.Add(new Emp{ EmpId = 4, Type = "User", Email = "seven#test.com" });
var groupedList = from emp in employees
group emp.Email by new { emp.EmpId, emp.Type } into g
select new { Key = g.Key, Type = g.Key.Type, Emails = g.ToList() };
foreach (var result in groupedList)
{
//I'm using LINQPad to output the results
result.Key.EmpId.Dump();
foreach(var email in result.Emails)
{
email.Dump();
}
}
}
public class Emp
{
public int EmpId { get; set; }
public string Type { get; set; }
public string Email { get; set; }
}
My results are:
or...
void Main()
{
List<Emp> employees= new List<Emp>();
employees.Add(new Emp{ EmpId = 1, Type = "User", Emails = new List<string>(){"one#test.com"} });
employees.Add(new Emp{ EmpId = 1, Type = "User", Emails = new List<string>(){"two#test.com"} });
employees.Add(new Emp{ EmpId = 2, Type = "Test", Emails = new List<string>(){"three#test.com"} });
employees.Add(new Emp{ EmpId = 2, Type = "Test", Emails = new List<string>(){"four#test.com"} });
employees.Add(new Emp{ EmpId = 3, Type = "Test", Emails = new List<string>(){"five#test.com"} });
employees.Add(new Emp{ EmpId = 3, Type = "Test", Emails = new List<string>(){"six#test.com"} });
employees.Add(new Emp{ EmpId = 4, Type = "User", Emails = new List<string>(){"seven#test.com"} });
var groupedList = from emp in employees
group emp.Emails by new { emp.EmpId, emp.Type } into g
select new Emp {
EmpId = g.Key.EmpId,
Type = g.Key.Type,
Emails = g.SelectMany(x => x).ToList()
};
foreach (var result in groupedList)
{ //I'm using LINQPad to output
result.EmpId.Dump();
result.Emails.ForEach(e => e.Dump());
}
}
public class Emp
{
public int EmpId { get; set; }
public string Type { get; set; }
public List<string> Emails { get; set; }
}
My results are also:

Related

How to avoid Looping and refactor the Code in C#

My use case is, I have a List of orders that I need to post to external API.
but the conditions are that, I can post 5 order in one post call of API.
and these 5 orders have to be of same store and of deliveryWindows should be either morning or afternoon for all 5 orders.
I have written the below code but I am not happy with that, Can anyone Kindly help to refactor the below logic.
I have used 3 for loops to Loop through Deliverywindow and also for stores and for all the orders in the store.
Is there better approach/async Looping of the below/ having separate method calls.
Any suggestion is really helpful!
using MoreLinq;
using System;
using System.Collections.Generic;
using System.Linq;
static void Main(string[] args)
{
//In real time I will have 1000's of orders for one store and deliveryWindow (Morning)
var allOrders = GetProductOrders();
string[] deliveryWindows = new[] { "Morning", "AfterNoon" };
//Looping for Morning & Afternoon
foreach (var deliveryWindow in deliveryWindows)
{
//Getting Morning order in first run and afternoon order in second run
var OrderForWindow = allOrders.Where(x => x.DeliveryWindow.Equals(deliveryWindow));
//Getting All Unique Store (Will have StoreA, StoreB, etc)
List<string> Stores = OrderForWindow.Select(x => x.StoreName).Distinct().ToList();
foreach (var Store in Stores)
{
//Store releated order for that windown (morning/afternoon)
var StoreOrders = OrderForWindow.Where(order => order.StoreName.Equals(Store)).ToList();
//taking 10 items from StoreOrders
//Batch will pick 5 items at once
foreach (var orders in StoreOrders.Batch(5))
{
//storeOrder will have list of 5 order which all have same delivery window
//Post External call
}
}
}
}
public static List<ProductOrder> GetProductOrders()
{
List<ProductOrder> productOrder = new List<ProductOrder>()
{
new ProductOrder(){ ID = 1, DeliveryWindow ="Morning", StoreName = "StoreA", customerDetails = "Cust1"},
new ProductOrder(){ ID = 2, DeliveryWindow ="Morning", StoreName = "StoreA",customerDetails = "Cust2"},
new ProductOrder(){ ID = 3, DeliveryWindow ="Morning", StoreName = "StoreA",customerDetails = "Cust3"},
new ProductOrder(){ ID = 4, DeliveryWindow ="AfterNoon", StoreName = "StoreA",customerDetails = "Cust4"},
new ProductOrder(){ ID = 5, DeliveryWindow ="AfterNoon", StoreName = "StoreA",customerDetails = "Cust5"},
new ProductOrder(){ ID = 6, DeliveryWindow ="Morning", StoreName = "StoreB",customerDetails = "Cust6"},
new ProductOrder(){ ID = 7, DeliveryWindow ="Morning", StoreName = "StoreB",customerDetails = "Cust7"},
new ProductOrder(){ ID = 8, DeliveryWindow ="AfterNoon", StoreName = "StoreB",customerDetails = "Cust8"},
new ProductOrder(){ ID = 9, DeliveryWindow ="AfterNoon", StoreName = "StoreB",customerDetails = "Cust9"},
new ProductOrder(){ ID = 10, DeliveryWindow ="AfterNoon", StoreName = "StoreC",customerDetails = "Cust10"},
};
return productOrder;
}
}
public class ProductOrder
{
public int ID { set; get; }
public string StoreName { set;get;}
public string DeliveryWindow { set; get; }
public string customerDetails { set; get; }
public string ProductDetails { set; get; }
}
As pointed out, this post is a great resource to help you understand how to group against multiple keys.
Here's what that would look like in your case:
var allOrders = GetProductOrders();
var groupedOrders = from order in allOrders
// We group using an anonymous object
// that contains the properties we're interested in
group order by new
{
order.StoreName,
order.DeliveryWindow
};
// Access is straightforward:
foreach (var orderGroup in groupedOrders)
{
Console.WriteLine($"Group {orderGroup.Key.StoreName} {orderGroup.Key.DeliveryWindow}");
// The group is a list itself, so you can apply
// your Batch LINQ extension
foreach (var order in orderGroup)
{
Console.WriteLine(order.ID);
}
}

LINQ to get unique id that has list of items

I am trying to generate a list result through linq.
I would like to have a result as a list with unique folder id with 0th item as folder_name, 1st Item as a list with one or more projects having fields project_id,name and description.
I have written following query:
Folders is basically model with format as folderid, name, List projects where Project Model has project_id, name and description
from the following records:
Models
public class FolderModel
{
public int folder_id { get; set; }
public string folder_name { get; set; }
public List<ProjectModel> projects{ get; set; }
}
public class ProjectModel
{
public int project_id { get; set; }
public string project_name { get; set; }
public string project_description { get; set; }
}
public class ResultModel
{
public List<FolderModel> folders { get; set; }
}
Result Data
List<FolderModel, List<ProjectModel>> result = new List<FolderModel, List<ProjectModel>>();
List<ProjectModel> projectList1 = new List<>();
ProjectModel projectModel1 = new ProjectModel();
projectModel1.project_name = "F1P1";
projectModel1.project_description = "F1P1";
projectList1.add(projectModel1);
List<ProjectModel> projectList2 = new List<>();
ProjectModel projectModel21 = new ProjectModel();
projectModel21.project_name = "F2P1";
projectModel21.project_description = "F2P1";
projectList2.add(projectModel21);
ProjectModel projectModel22 = new ProjectModel();
projectModel22.project_name = "F2P2";
projectModel22.project_description = "F2P2";
projectList2.add(projectModel22);
List<ProjectModel> projectList3 = new List<>();
ProjectModel projectModel3 = new ProjectModel();
projectModel3.project_name = "F3P1";
projectModel3.project_description = "F1P3";
projectList3.add(projectModel3);
ResultModel resultModel = new resultModel();
resultModel.(new FolderModel { folder_id: 1,folder-name: "F1" }, projectList1);
FolderModel folderModel1 = new FolderModel();
folderModel1.folder_id = 1
folderModel1.folder_name = "F1"
folderModel1.projects = projectList1
FolderModel folderModel2 = new FolderModel();
folderModel2.folder_id = 2
folderModel2.folder_name = "F2"
folderModel2.projects = projectList2
FolderModel folderModel3 = new FolderModel();
folderModel3.folder_id = 3
folderModel3.folder_name = "F3"
folderModel3.projects = projectList3
ResultModel resultModel = new ResultModel();
List<FolderModel> folderList = new List<>();
folderList.add(folderModel1);
folderList.add(folderModel2);
folderList.add(folderModel3);
resultModel.folders = folderList
SQL Data
folder_id | folder_name | project_id | project_name | project_description
1 F1 11 F1P1 F1P1
2 F2 21 F2P1 F2P1
2 F2 22 F2P2 F2P2
3 F3 31 F3P1 F3P1
4 F4 41 F4P1 F4P1
5 F5 51 F5P1 F5P1
This is what I have tried
var result = resultModel.folders.GroupBy(x => new { x.folder_id }).ToList();
I am sure that after group by I have to select name but not sure how can I generate project into a list and add it to main folder id. Can someone please guide on this.
You can query like this:
var result =
resultModel
.folders
.GroupBy(x => new { x.folder_id })
.Select(s => new
{
folder_id = s.Key,
folder_name = s.First().folder_name
})
.ToList();
It seems to me that you're looking for this:
var folders = new []
{
new { folder_id = 1, folder_name = "F1", project_id = 11, project_name = "F1P1", project_description = "F1P1" },
new { folder_id = 2, folder_name = "F2", project_id = 21, project_name = "F2P1", project_description = "F2P1" },
new { folder_id = 2, folder_name = "F2", project_id = 22, project_name = "F2P2", project_description = "F2P2" },
new { folder_id = 3, folder_name = "F3", project_id = 31, project_name = "F3P1", project_description = "F3P1" },
new { folder_id = 4, folder_name = "F4", project_id = 41, project_name = "F4P1", project_description = "F4P1" },
new { folder_id = 5, folder_name = "F5", project_id = 51, project_name = "F5P1", project_description = "F5P1" },
};
List<FolderModel> result =
folders
.GroupBy(
x => new { x.folder_id, x.folder_name },
x => new { x.project_id, x.project_name, x.project_description })
.Select(x => new FolderModel()
{
folder_id = x.Key.folder_id,
folder_name = x.Key.folder_name,
projects = x.Select(y => new ProjectModel()
{
project_id = y.project_id,
project_name = y.project_name,
project_description = y.project_description,
}).ToList(),
})
.ToList();

How to convert dynamic to unknown class in runtime

I have a dynamic rule engine and i want to pass class to this and each time the class is sent differently.
For example:
Order MyOrder = new Order()
{
OrderId = 1,
Customer = new Customer()
{
FirstName = "John",
LastName = "Doe",
Country = new Country()
{
CountryCode = "AUS"
}
},
Items = new List<Item>(){
new Item(){ ItemCode = "MM23", Cost=5.25M},
new Item(){ ItemCode = "LD45", Cost=5.25M},
new Item(){ ItemCode = "Test", Cost=3.33M},
}
};
I want to change this:
var compiledRule = engine.CompileRule<Order>(rule);
to :
dynamic obj = MyOrder;
var compiledRule = engine.CompileRule<?????>(rule);
how can I get Type of obj and put instead of "????"
I found solution after search a lot
var engine = new MRE();
var compiledRule = engine.CompileRule<Order>(rule);
change to:
var engine = new MRE();
var compiledRule = typeof(MRE).GetMethod("CompileRule").MakeGenericMethod(myOrder.GetType())
.Invoke(engine, new object[] { rule}) as dynamic;

LINQ group by List<results> and return results

I have Three List<Results>
var list1 = new List<Results> {
new Results{ Empid = 1,Empname = "John",Rating = 1.33},
new Results{ Empid = 2,Empname = "Aarya",Rating = 1.6},
new Results{ Empid = 3,Empname = "Sansa",Rating = 1.6}
};
var list2 = new List<Results> {
new Results{ Empid = 1,Empname = "John",Rating = 2.33},
new Results{ Empid = 2,Empname = "Aarya",Rating = 2.6},
new Results{ Empid = 3,Empname = "Sansa",Rating = 1.6}
};
var list3 = new List<Results> {
new Results{ Empid = 1,Empname = "John",Rating = 0.33},
new Results{ Empid = 2,Empname = "Aarya",Rating = 0.6}
};
I want final result like(sum of all ratings based on Empid)
Final={Empid=1,Empname="John",Rating=3.99}
{Empid=2,Empname="Aarya",Rating=4.8}
{Empid=3,Empname="Sansa",Rating=3.2}
How to achieve this by using Linq, I am new to LinQ i am trying using GroupBy but I couldn't.
Use Concat to merge the lists and then a normal group by:
var result = list1.Concat(list2).Concat(list3)
.GroupBy(item => new { item.Empid, item.Empname }, item => item.Rating)
.Select(g => new {
Id = g.Key.Empid,
Name = g.Key.Empname,
Rating = g.Sum() });
Or in query syntax:
var result = from item in list1.Concat(list2).Concat(list3)
group item.Rating by new { item.Empid, item.Empname } into g
select new {
Id = g.Key.Empid,
Name = g.Key.Empname,
Rating = g.Sum()
};
sum of all ratings based on Empid - Note that if the id and name correlate then it is simplest to group by the two of them as in the example above. Otherwise, group by id and retrieve for each group the .First().Empname
first you should concat all lists together and then write something like this:
var q = from p in concatlist
group p by new { p.Empid, p.Empname} into g
select new
{
Empid = g.Key.Empid,
Empname = g.Key.Empname,
Rating = g.Sum(c=>c.Rating)
};
Or you can use Enumerable.SelectMany method to "flatten" lists and then use same grouping as in other answers
var final =
new[] { list1, list2, list3 }.SelectMany(list => list)
.GroupBy(result => new { result.EmpId, result.EmpName })
.Select(group => new Results
{
EmpId = group.Key.EmpId,
EmpName = group.Key.EmpName,
Rating = group.Sum(result => result.Rating)
});
With SelectMany you will be able to query dynamic amount of lists

linq SQL statements for joining two id with single list

I want to join list of users with my records list, but records list has two columns where I should use value of user list, also one of those two columns is nullable. How to join it properly? I was trying to do something like this:
var results = (from r in records
join u in users on r.RegisteredBy equals u.Id
join u in users on r.ModifiedBy equals u.Id
select new CustomResult()
{
Id = r.Id,
Name = r.Name,
RegisteredByName = u.Name,
ModifiedByName = u.Name
}).ToList();
It didn't work as I expected, I remember that I have to set it to use default value if null.
For example I have list of users
var user1 = new User() { Id = 1, Name = "John" };
var user2 = new User() { Id = 2, Name = "Matt" }
var user3 = new User() { Id = 3, Name = "George" };
List<User> users = new List<User>(){ user1, user2, user3 };
And I have another List of my records, i.e
var record1 = new Record() { Id = 1, Name = "Record1", RegisteredBy = 1, ModifiedBy = 3};
var record2 = new Record() { Id = 2, Name = "Record2", RegisteredBy = 3, ModifiedBy = null };
var record3 = new Record() { Id = 3, Name = "Record3", RegisteredBy = 2, ModifiedBy = 1 };
List<Record> records = new List<Record>(){ record1, record2, record3 };
As a result of this join I want to make another list of class, having information that I need, i.e
var result1 = new CustomResult(){ Id = 1, Name = "Record1", RegisteredByName = "John", ModifiedByName = "George" };
var result2 = new CustomResult(){ Id = 2, Name = "Record2", RegisteredByName = "George", ModifiedByName = null };
var result3 = new CustomResult(){ Id = 1, Name = "Record3", RegisteredByName = "Matt", ModifiedByName = "John" };
Well, you need to perform left outer join for the optional (nullable) field (and then of course chech for null when accessing related object properties):
var results = (from r in records
join ru in users on r.RegisteredBy equals ru.Id
join mu in users on r.ModifiedBy equals mu.Id into modifiedBy
from mu in modifiedBy.DefaultIfEmpty()
select new CustomResult()
{
Id = r.Id,
Name = r.Name,
RegisteredByName = ru.Name,
ModifiedByName = mu != null ? mu.Name : string.Empty
}).ToList();

Categories