I have a list of Objects and one of the item is another list. How can I Group them based on the inner list.
Here is an example of what I wish to do.
class Student
{
public string Name;
public int Age;
public List<GroupInfo> GroupList; // This is the inner list
}
class GroupInfo
{
public string GroupName;
public int GroupId;
}
static void Main()
{
GroupInfo firstGroup = new GroupInfo
{
GroupId = 1,
GroupName = "First group"
};
GroupInfo secondGroup = new GroupInfo
{
GroupId = 2,
GroupName = "Second group"
};
GroupInfo thirdGroup = new GroupInfo
{
GroupId = 3,
GroupName = "Third group"
};
GroupInfo fourthGroup = new GroupInfo
{
GroupId = 4,
GroupName = "Fourth group"
};
List<Student> studentList = new List<Student>();
Student firstStudent = new Student();
firstStudent.Name = "Name1";
firstStudent.Age = 15;
firstStudent.GroupList = new List<GroupInfo>();
firstStudent.GroupList.Add(firstGroup);
firstStudent.GroupList.Add(secondGroup);
studentList.Add(firstStudent);
Student secondStudent = new Student();
secondStudent.Name = "Name2";
secondStudent.Age = 17;
secondStudent.GroupList = new List<GroupInfo>();
secondStudent.GroupList.Add(firstGroup);
secondStudent.GroupList.Add(thirdGroup);
studentList.Add(secondStudent);
Student thirdStudent = new Student();
thirdStudent.Name = "Name3";
thirdStudent.Age = 18;
thirdStudent.GroupList = new List<GroupInfo>();
thirdStudent.GroupList.Add(secondGroup);
thirdStudent.GroupList.Add(thirdGroup);
thirdStudent.GroupList.Add(fourthGroup);
studentList.Add(thirdStudent);
List<GroupInfo> groupInfoList = new List<GroupInfo>();
// Now What I want is to get a group List Where...
foreach (var student in studentList)
{
// ...First Group Should contains firstStudent and secondStudent
// Second group Should firstStudent & thirdStudent
// Third group Should contains secondStudent & thirdStuden
// Fourth Group Should contains only thirdStudent
}
}
One way is to iterate on the whole List and populate the GroupInfo List. Just wondering is there any other way to do this task.
You can do this with SelectMany like this:-
var result = studentList.SelectMany(x => x.GroupList,
(studentObj, groups) => new { studentObj, groups })
.GroupBy(x => new { x.groups.GroupId, x.groups.GroupName })
.Select(x => new
{
GroupId = x.Key.GroupId,
GroupName = x.Key.GroupName,
Students = x.Select(z => z.studentObj).ToList()
});
Since your GroupInfo class only has two properties i.e. GroupId & GroupName, you won't be able to fetch the Students associated with it. This is the reason I am fetching anonymous type out of it.
I am getting following output with this query:-
Related
I have a list with items as follows:
public class Student {
public string Name;
public string Nickname;
}
var students = new List<Student> { new Student {Name = "Becca", Nickname = "Rebecca"},
new Student {Name = "Ray", Nickname = "Raymond"}}
If I want to swap Name and Nickname so that students would now be
var students = new List<Student> { new Student {Name = "Rebecca", Nickname = "Becca"},
new Student {Name = "Raymond", Nickname = "Ray"}}
How do I do that in linq?
You can use Linq's Select method to create a new instance of student with the values swapped.
var students = new List<Student>
{
new Student {Name = "Becca", Nickname = "Rebecca"},
new Student {Name = "Ray", Nickname = "Raymond"}
}
var swapped = students.Select(x => new Student {Name = x.Nickname, Nickname = x.Name});
Whenever we ask how to do X with each item in a list, the answer is usually two things: How to do X, and how to do something with each item in a list. Once we see that, we can separate the two problems and start with how to do X. The second part, how to do anything with each item in a list, is much simpler.
In this case, how do we do X - create a new Student from an existing one, with the names swapped?
That could be a method like this:
Student SwapNames(Student student)
{
return new Student {Name = student.Nickname, Nickname = student.Name};
}
or simplified as
Student SwapNames(Student student) =>
new Student {Name = student.Nickname, Nickname = student.Name};
Having solved that, determining how to do it to to items in a list is easier.
var swappedStudents = new List<Student>();
foreach(var student in originalStudents)
{
swappedStudents.Add(SwapNames(student));
}
Or, using LINQ:
var swappedStudents = originalStudents.Select(student => SwapNames(student));
...or simplified:
var swappedStudents = originalStudents.Select(SwapNames);
These both produce an IEnumerable<Student>. If we wanted a List<Student> we could append:
var swappedStudents = originalStudents.Select(SwapNames).ToList();
In the examples so far, the method that does X - in this case, swapping the names, is in a separate function. That can be helpful for readability. But we can also do it inline with an anonymous function, like this:
var swappedStudents = originalStudents.Select(
student => new Student { Name = student.Nickname, Nickname = student.Name });
That's an anonymous function that takes a Student as an argument and returns a new Student with the names swapped.
I have user table (Default ApplicationUser Table from IdentityUser by ASP.CORE)
and I have added additional field for RoleType. There is also an Enum I added to specify Role Definition.
public enum Roles
{
Administrator = 1,
Headquarters = 2,
Branch = 3,
Driver = 4,
Client = 5
}
Now I want to show all the users in a view as a table along with role description.
I am unable to make LINQ query with Enum & User table using LINQ join.
To get the list of Roles from the enum use:
var roles = Enum.GetValues(typeof(Roles)).Cast<Roles>()
.Select(r => new { Value = (int)r, Name = r.ToString() }).ToList();
you can then use this in your Linq query, for example:
var roles = Enum.GetValues(typeof(Roles)).Cast<Roles>()
.Select(r => new { Value = (int)r, Name = r.ToString() }).ToList();
var users = from u in ApplicationUser
join r in roles on u.Role equals r.Value
select new {Name = u.Name, RoleId = u.Role, RoleDescription = r.Name} ;
A simpler way without the Enum.GetValues is:
var users = from u in ApplicationUser
select new {Name = u.Name, RoleId = u.Role, RoleDescription = (Roles)r.Role.ToString()} ;
var xx = from u in _context.Users
.Select(x => new ApplicationUserList
{ Firstname = x.Firstname,
RoleType = ((Roles)x.RoleId).ToString()
});
// This join is performed in memory
var results =
from e in Enum.GetValues(typeof(Roles)).Cast<Roles>()
join r in ApplicationUser on e equals r.Roles into rs
from r in rs.DefaultIfEmpty()
select new { Roles = e, Count = r?.Count ?? 0};
If I understand your question, you should first convert enum to dictionary an Join between what you need, here is an example:
static void Main(string[] args)
{
ApplicationUser a = new ApplicationUser();
a.userName = "a";
a.role = 1;
ApplicationUser b = new ApplicationUser();
b.userName = "b";
b.role = 3;
List<ApplicationUser> alist=new List<ApplicationUser>();
alist.Add(a);
alist.Add(b);
Dictionary<int, string> DicRoles = new Dictionary<int, string>();
var vals = Enum.GetValues(typeof(Roles));
foreach (var val in vals)
{
DicRoles.Add((int)val, val.ToString());
}
var result = from t in alist
join x in DicRoles on t.role equals x.Key
select new {t.userName,x.Value };
}
public enum Roles:int
{
Administrator = 1,
Headquarters = 2,
Branch = 3,
Driver = 4,
Client = 5
}
}
public class ApplicationUser
{
public string userName { get; set; }
public int role { get; set; }
}
I have the following responses from the API. How can I group them into the following structure?
Student[]
- Name
- Classes[]
- ClassName
- ClassId
- ClassCategories[]
- CategoryName
- CategoryWeight
- Assignments[]
- AssignmentName
- Score
I was managed to group them until the "Classes" level but unable to get the ClassCategories for each of the classes
var data = (from result in results
group result by new { result.StudentId, result.FirstName, result.LastName, result.MiddleInitial }
into StudentGroup
select new GroupedStudent
{
StudentId = StudentGroup.Key.StudentId,
FullName = string.Format("{0} {1} {2}", StudentGroup.Key.FirstName, StudentGroup.Key.MiddleInitial, StudentGroup.Key.LastName).Replace(" ", " "),
Classes = from result in results
group result by new { result.ClassId, result.ClassName } into ClassGroup
select new groupedClass
{
ClassName = ClassGroup.Key.ClassName,
ClassId = ClassGroup.Key.ClassId,
ClassCategories = ...
})
}).ToList();
Can anyone please assists me? Thank you.
First, you have make ClassGroup from StudentGroup not from results.
Classes = from s in StudentGroup group result by new { s.ClassId, s.ClassName } into ClassGroup
The complete linq query is as follows:
var data =
(from result in results
group result by new { result.StudentId, result.FirstName, result.LastName, result.MiddleInitial } into StudentGroup
select new
{
StudentId = StudentGroup.Key.StudentId,
FullName = string.Format("{0} {1} {2}", StudentGroup.Key.FirstName, StudentGroup.Key.MiddleInitial, StudentGroup.Key.LastName).Replace(" ", " "),
Classes = (from s in StudentGroup
group s by new { s.ClassId, s.ClassName } into ClassGroup
select new
{
ClassId = ClassGroup.Key.ClassId,
ClassName = ClassGroup.Key.ClassName,
ClassCategories = (from c in ClassGroup
group c by new { c.CategoryName, c.CategoryWeight } into CategoryGroup
select new
{
CategoryName = CategoryGroup.Key.CategoryName,
CategoryWeight = CategoryGroup.Key.CategoryWeight,
Assignments = (from ct in CategoryGroup
group ct by new { ct.AssignmentName, ct.Score } into AssingnmentGroup
select new
{
AssignmentName = AssingnmentGroup.Key.AssignmentName,
Score = AssingnmentGroup.Key.Score
}).ToList()
}).ToList()
}).ToList()
}).ToList();
For example, if you want to access to the first Assignment's score, you can get it like this:
var student = data.FirstOrDefault();
var score = student.Classes[0].ClassCategories[0].Assignments[0].Score;
This is usually how I do It.
Create a class to store your data
Create a list of that class type
In your case instead of string dataRow maybe you can use a sub class
.
// get data from webservice
var json = webClient.DownloadString(url);
var values = JsonConvert.DeserializeObject<JArray>(json);
// create a list to save all the element
List<myClass> classList = new List<myClass>();
// process every row
foreach (string dataRow in values)
{
string[] dataField = dataRow.Split(',');
// have a constructor to assign each value to this element
myClass ctROW = new myClass(dataField);
classList.add(ctROW );
I am currently developing an application that requires this senario.
Assuming I have this object
public class ObjectItem
{
public int Id {get;set;}
public int Name {get;set;}
public int Sex {get;set;}
public int Age {get;set;}
public string Complexion {get;set;}
}
If we now have two lists of this object
var studentWithAge = new List<ObjectItem>
{
new ObjectItem {Id = 1, Name = "John", Age = 2},
new ObjectItem {Id = 2, Name = "Smith", Age = 5},
new ObjectItem {Id = 3, Name = "Juliet", Age = 7},
};
var studentWithSexAndComplexion = new List<ObjectItem>
{
new ObjectItem {Id = 1, Name = "John", Sex = "Male", Complexion = "fair"},
new ObjectItem {Id = 2, Name = "Smith", Sex = "Male", Complexion = " "},
new ObjectItem {Id = 3, Name = "Juliet", Sex = "Female", Complexion = "Blonde"},
new ObjectItem {Id = 4, Name = "Shittu", Sex = "Male", Complexion = "fair"},
};
I want to merge these two lists into just one. The end result should look like this.
var CompleteStudentData=new List<ObjectItem>
{
new ObjectItem{Id=1,Name="John",Sex="Male", Complexion="fair",Age=2},
new ObjectItem{Id=2,Name="Smith",Sex="Male", Complexion=" ", Age=5},
new ObjectItem{Id=3,Name="Juliet",Sex="Female", Complexion="Blonde", Age=7},
new ObjectItem{Id=4,Name="Shittu",Sex="Male", Complexion="fair", Age=0},
}
How do i achieve this? Using Union to merge the two list does not produce the desired result. I would appreciate your help.
var result = StudentWithAge.Join(StudentWithSexAndComplexion,
sa => sa.Id,
ssc => ssc.Id,
(sa, ssc) => new ObjectItem
{
Id = sa.Id,
Name = sa.Name,
Age = sa.Age,
Sex = ssc.Sex,
Complexion = ssc.Complexion
}).ToList();
Or, avoiding creation of new objects:
var result = StudentWithAge.Join(StudentWithSexAndComplexion,
sa => sa.Id,
ssc => ssc.Id,
(sa, ssc) =>
{
sa.Sex = ssc.Sex;
sa.Complexion = ssc.Complexion;
return sa;
}).ToList();
And if you want to add students presented only in the second list, than also:
result.AddRange(StudentWithSexAndComplexion.Where(ssc => !StudentWithAge.Any(sa => sa.Id == ssc.Id)));
Since it's possible that your collections will not have a 1-to-1 correspondence, you would have to do a full outer join. See here for how you can compose it that way.
Here's one way you can get similar results.
Collect all the keys (the ids) from both collections, then perform a left join with each of the collections, then combine the results.
var ids = studentWithAge.Select(s => s.Id)
.Union(studentWithSexAndComplexion.Select(s => s.Id));
var query =
from id in ids
from sa in studentWithAge
.Where(sa => sa.Id == id)
.DefaultIfEmpty(new ObjectItem { Id = id })
from ssc in studentWithSexAndComplexion
.Where(ssc => ssc.Id == id)
.DefaultIfEmpty(new ObjectItem { Id = id })
select new ObjectItem
{
Id = id,
Name = sa.Name ?? ssc.Name,
Sex = ssc.Sex,
Age = sa.Age,
Complexion = ssc.Complexion,
};
.Net has a function which is concatenating collections:
var concatenatedCollection = StudentWithAge.Concat(StudentWithSexAndComplexion).ToList();
var StudentWithAge = new List<ObjectItem>()
{
new ObjectItem{Id=1,Name="John",Age=2},
new ObjectItem{Id=2,Name="Smith",Age=5},
new ObjectItem{Id=3,Name="Juliet",Age=7},
};
var StudentWithSexAndComplexion = new List<ObjectItem>()
{
new ObjectItem{Id=1,Name="John",Sex="Male", Complexion="fair"},
new ObjectItem{Id=2,Name="Smith",Sex="Male", Complexion=" "},
new ObjectItem{Id=3,Name="Juliet",Sex="Female", Complexion="Blonde"},
new ObjectItem{Id=4,Name="Shittu",Sex="Male", Complexion="fair"},
};
var concatenatedCollection = StudentWithAge.Concat(StudentWithSexAndComplexion).ToList();
I am using Dynamic Linq helper for grouping data. My code is as follows :
Employee[] empList = new Employee[6];
empList[0] = new Employee() { Name = "CA", State = "A", Department = "xyz" };
empList[1] = new Employee() { Name = "ZP", State = "B", Department = "xyz" };
empList[2] = new Employee() { Name = "AC", State = "B", Department = "xyz" };
empList[3] = new Employee() { Name = "AA", State = "A", Department = "xyz" };
empList[4] = new Employee() { Name = "A2", State = "A", Department = "pqr" };
empList[5] = new Employee() { Name = "BA", State = "B", Department = "pqr" };
var empqueryable = empList.AsQueryable();
var dynamiclinqquery = DynamicQueryable.GroupBy(empqueryable, "new (State, Department)", "it");
How can I get back the Key and corresponding list of grouped items i.e IEnumerable of {Key, List} from dynamiclinqquery ?
I solved the problem by defining a selector that projects the Key as well as Employees List.
var eq = empqueryable.GroupBy("new (State, Department)", "it").Select("new(it.Key as Key, it as Employees)");
var keyEmplist = (from dynamic dat in eq select dat).ToList();
foreach (var group in keyEmplist)
{
var key = group.Key;
var elist = group.Employees;
foreach (var emp in elist)
{
}
}
The GroupBy method should still return something that implements IEnumerable<IGrouping<TKey, TElement>>.
While you might not be able to actually cast it (I'm assuming it's dynamic), you can certainly still make calls on it, like so:
foreach (var group in dynamiclinqquery)
{
// Print out the key.
Console.WriteLine("Key: {0}", group.Key);
// Write the items.
foreach (var item in group)
{
Console.WriteLine("Item: {0}", item);
}
}