C# LINQ get max element in group by column of another collection - c#

I have an collection of students and collection of universities. For each university i need to pick student with highest score. I want to group universities by name and select student that got highest score in that univesity. Like this:
Country | Name | Student | Score
New York| NY University | Bob | 120
LA | LA Univesity | Tom | 140
So far i got there
return from university in Universities
join country in Countries on university.Id equals country.Id
orderby country.Name
group university by university.Name into g
select new
{
g.Key,
maxScore = g.Max(student => student.Score) <-- student.Score is only available in Students class
};
Problem is university only got access to students id i.e each Universty got only thos fields:
int Country.Id,
string Name,
int Student.Id
and problem is how do i get only 1 student with max score in that g group.
Student:
Id,
Name,
Score

Here is an example:
var countries = new List<Country>
{
new Country { CountryId = 1, Name = "Country 1" },
new Country { CountryId = 2, Name = "Country 2" }
};
var universities = new List<University>
{
new University { UniversityId = 1, CountryId = 1, Name = "University 1" },
new University { UniversityId = 2, CountryId = 1, Name = "University 2" },
new University { UniversityId = 3, CountryId = 2, Name = "University 3" },
new University { UniversityId = 4, CountryId = 2, Name = "University 4" }
};
var students = new List<Student>
{
new Student { StudentId = 1, UniversityId = 1, Name = "Student 1", Score = 50 },
new Student { StudentId = 2, UniversityId = 1, Name = "Student 2", Score = 100 },
new Student { StudentId = 3, UniversityId = 2, Name = "Student 3", Score = 100 },
new Student { StudentId = 4, UniversityId = 2, Name = "Student 4", Score = 50 },
new Student { StudentId = 5, UniversityId = 3, Name = "Student 5", Score = 100 },
new Student { StudentId = 6, UniversityId = 3, Name = "Student 6", Score = 50 },
new Student { StudentId = 7, UniversityId = 4, Name = "Student 7", Score = 50 },
new Student { StudentId = 8, UniversityId = 4, Name = "Student 8", Score = 100 }
};
var maxScoresByUniversity = from country in countries
join university in universities on country.CountryId equals university.CountryId
join student in students on university.UniversityId equals student.UniversityId
group new { country, university, student } by university.Name into g
let r = g.MaxBy(x => x.student.Score)
select new
{
Country = r.country.Name,
Name = r.university.Name,
Student = r.student.Name,
Score = r.student.Score
};
foreach (var score in maxScoresByUniversity)
{
Console.WriteLine($"{score.Country}, {score.Name}, {score.Student}, {score.Score}");
}
Note that it joins and groups all three collections. It then gets the record with the max Score from each group. This uses the MaxBy method that is only available in .NET 6 and later. If you're targeting an earlier framework, you'd need to implement that yourself or use more complex LINQ. I did ask that question in the comments for a reason.

Assume that your entity classes are as below:
public class University
{
public int Id { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public ICollection<Student> Students { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<University> Universities { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Score { get; set; }
public int UniversityId { get; set; }
public virtual University University { get; set; }
}
Updated
Without a relationship, you have to join between the entities.
To get the max score of the student by university you can work with .OrderByDescending() as:
var result = (from university in context.Universities
join country in context.Countries on university.CountryId equals country.Id
join student in context.Students on university.Id equals student.UniversityId
orderby country.Name
group new { university, student, country } by university.Id into g
select new
{
CountryName = g.First().country.Name,
UniversityName = g.First().university.Name,
Student = g.Select(x => x.student)
.OrderByDescending(x => x.Score)
.First()
.Name,
MaxScore = g.Select(x => x.student)
.OrderByDescending(x => x.Score)
.First()
.Score,
}).ToList();
Demo # .NET Fiddle

Related

How to get data from 3 table into 1 list

Sorry for my bad English.
Here is my SQL Design.
I have 3 table in Sqlsever. Each table has 4 column with same name, same datatype.
And i want to get data from 4 column "Id, Name, Quantity, IdCategory" from 3 table into 1 list object same as returning value in this code below:
public async Task<IEnumerable<Shirt>> LoadAllShirt()
{
return await _dbContext.Shirt.ToListAsync();
}
I use .NET Core 6 Mvc - code first. Thanks for your help.
I have 3 table in Sqlsever. Each table has 4 column with same name,
same datatype. And I want to get data from 4 column "Id, Name,
Quantity, IdCategory" from 3 table into 1 list, I use .NET Core 6 Mvc - code first.
Well, lot of way around to handle this kind of scenario. Most easy and convenient way I would prefer to use View model or using Linq query.
Lets assume you have below Models:
Models:
public class Bags
{
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public string Category { get; set; }
}
public class Shirts
{
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public string Category { get; set; }
}
public class Shoes
{
public int Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
public string Category { get; set; }
}
Seeds In Models:
List<Bags> listBags = new List<Bags>();
listBags.Add(new Bags() { Id = 101, Name = "Bag A", Quantity =10, Category = "Cat-A"});
listBags.Add(new Bags() { Id = 102, Name = "Bag B", Quantity =15, Category = "Cat-A"});
listBags.Add(new Bags() { Id = 103, Name = "Bag C", Quantity =20, Category = "Cat-A"});
List<Shirts> listShirts = new List<Shirts>();
listShirts.Add(new Shirts() { Id = 101, Name = "Shirt A", Quantity = 10, Category = "Cat-B" });
listShirts.Add(new Shirts() { Id = 102, Name = "Shirt B", Quantity = 15, Category = "Cat-B" });
listShirts.Add(new Shirts() { Id = 103, Name = "Shirt C", Quantity = 20, Category = "Cat-B" });
List<Shoes> listShoes = new List<Shoes>();
listShoes.Add(new Shoes() { Id = 101, Name = "Shirt A", Quantity = 10, Category = "Cat-S" });
listShoes.Add(new Shoes() { Id = 102, Name = "Shirt B", Quantity = 15, Category = "Cat-S" });
listShoes.Add(new Shoes() { Id = 103, Name = "Shirt C", Quantity = 20, Category = "Cat-S" });
Way: 1 using ViewModel:
public class AllViewModel
{
public List<Bags> Bags { get; set; }
public List<Shirts> Shirts { get; set; }
public List<Shoes> Shoes { get; set; }
}
Query Using ViewModel:
var allTableUsingViewModel = new AllViewModel();
allTableUsingViewModel.Bags = listBags;
allTableUsingViewModel.Shirts = listShirts;
allTableUsingViewModel.Shoes = listShoes;
Output Using ViewModel:
Way: 2 using Linq Annonymous Type:
Query Using Linq Annonymous Type:
var AllTableListUsingLinq = from a in listBags
join b in listShirts on a.Id equals b.Id
join c in listShoes on b.Id equals c.Id
select new
{
FromBagsID = a.Id,
FromBagsName = a.Name,
FromBagsQuantity = a.Quantity,
FromBagsCategory = a.Category,
FromShirtsID = b.Id,
FromShirtsName = b.Name,
FromShirtsQuantity = b.Quantity,
FromShirtsCategory = b.Category,
FromShoesID = c.Id,
FromShoesName = c.Name,
FromShoesQuantity = c.Quantity,
FromShoesCategory = c.Category
};
Output Using Linq Annonymous Type:
Full Controller:
[HttpGet("GetFrom3Tables")]
public IActionResult GetFrom3Tables()
{
List<Bags> listBags = new List<Bags>();
listBags.Add(new Bags() { Id = 101, Name = "Bag A", Quantity =10, Category = "Cat-A"});
listBags.Add(new Bags() { Id = 102, Name = "Bag B", Quantity =15, Category = "Cat-A"});
listBags.Add(new Bags() { Id = 103, Name = "Bag C", Quantity =20, Category = "Cat-A"});
List<Shirts> listShirts = new List<Shirts>();
listShirts.Add(new Shirts() { Id = 101, Name = "Shirt A", Quantity = 10, Category = "Cat-B" });
listShirts.Add(new Shirts() { Id = 102, Name = "Shirt B", Quantity = 15, Category = "Cat-B" });
listShirts.Add(new Shirts() { Id = 103, Name = "Shirt C", Quantity = 20, Category = "Cat-B" });
List<Shoes> listShoes = new List<Shoes>();
listShoes.Add(new Shoes() { Id = 101, Name = "Shirt A", Quantity = 10, Category = "Cat-S" });
listShoes.Add(new Shoes() { Id = 102, Name = "Shirt B", Quantity = 15, Category = "Cat-S" });
listShoes.Add(new Shoes() { Id = 103, Name = "Shirt C", Quantity = 20, Category = "Cat-S" });
//Way: 1 Linq Query
var AllTableListUsingLinq = from a in listBags
join b in listShirts on a.Id equals b.Id
join c in listShoes on b.Id equals c.Id
select new
{
FromBagsID = a.Id,
FromBagsName = a.Name,
FromBagsQuantity = a.Quantity,
FromBagsCategory = a.Category,
FromShirtsID = b.Id,
FromShirtsName = b.Name,
FromShirtsQuantity = b.Quantity,
FromShirtsCategory = b.Category,
FromShoesID = c.Id,
FromShoesName = c.Name,
FromShoesQuantity = c.Quantity,
FromShoesCategory = c.Category
};
//Way: 2 : ViewModel
var allTableUsingViewModel = new AllViewModel();
allTableUsingViewModel.Bags = listBags;
allTableUsingViewModel.Shirts = listShirts;
allTableUsingViewModel.Shoes = listShoes;
return Ok(AllTableListUsingLinq);
}
Note: If you need more information you could check our official document for View Model and Linq Projction here
The following sample query will list your 3 types of data into a single result set.
var allResults = resultSet1.Concat(resultSet2);
For the return type create a class which will be the parent class for all your products (Bag,Shirt,Shoes) Which will help you to return data in a single Generic data.
If you use any non-generic list to send the data like hashtable or Arraylist then then there will be no issue.
In my way I will suggest to use generic data list as it will help you fetch data in better time complexity.
In this case you may need to define additional indirect base class with these 4 parameters. Than you can create Collection of this base class, and concatinate all 3 tables into.
public class BaseEntity
{
public string Name {get;set;}
}
public class Shoes : BaseEntity
{
}
...
public IEnumerable<BaseEntity> GetAllTables()
{
var shirts = await _dbContext.Shirt.ToListAsync();
var shoes = await _dbContext.Shoes.ToListAsync();
var bags = await _dbContext.Bags.ToListAsync();
return shirts.Concat(shoes).Concat(bags);
}
Similar example but witout casting to base class is shown in Enumerable.Concat documentation: https://learn.microsoft.com/pl-pl/dotnet/api/system.linq.enumerable.concat?view=net-7.0

Error: 'StudentScore' is a type, which is not valid in the given context

Join the two collections and calculate the total score of each student by subject.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class StudentScore
{
public int StudentId { get; set; }
public string Subject { get; set; }
public int Points { get; set; }
}
public class StudentScoress
{
public static void Main(string[] args)
{
var totalscore = from s in Student
join ss in StudentScore
on s.Id equals ss.StudentId
group ss by ss.Subject into sss
select new { sss.Name, sss.Subject, sss.Points };
foreach (var ts in totalscore)
{
Console.WriteLine("{0}" + " " + "{1}" + " " + "{2}", ts.Name, ts.Subject, ts.Points);
}
}
IEnumerable<Student> student = new List<Student>()
{
new Student() {Id=1,Name="Sam",Age=16},
new Student() {Id=2,Name="Rick",Age=16},
new Student() {Id=3,Name="Warner",Age=17},
};
IEnumerable<StudentScore> studentScores = new List<StudentScore>()
{
new StudentScore() { StudentId = 1, Subject = "Maths", Points = 54},
new StudentScore() { StudentId = 1, Subject = "Maths", Points = 32},
new StudentScore() { StudentId = 1, Subject = "English", Points = 55},
new StudentScore() { StudentId = 1, Subject = "English", Points = 54},
new StudentScore() { StudentId = 1, Subject = "Biology", Points = 32},
new StudentScore() { StudentId = 1, Subject = "Biology", Points = 27},
new StudentScore() { StudentId = 2, Subject = "Maths", Points = 44},
new StudentScore() { StudentId = 2, Subject = "Maths", Points = 37},
new StudentScore() { StudentId = 2, Subject = "English", Points = 59},
new StudentScore() { StudentId = 2, Subject = "English", Points = 64},
new StudentScore() { StudentId = 2, Subject = "Biology", Points = 42},
new StudentScore() { StudentId = 2, Subject = "Biology", Points = 67},
new StudentScore() { StudentId = 3, Subject = "Maths", Points = 53},
new StudentScore() { StudentId = 3, Subject = "Maths", Points = 72},
new StudentScore() { StudentId = 3, Subject = "English", Points = 54},
new StudentScore() { StudentId = 3, Subject = "English", Points = 59},
new StudentScore() { StudentId = 3, Subject = "Biology", Points = 87},
new StudentScore() { StudentId = 3, Subject = "Biology", Points = 34}
};
}
I'm getting error in LINQ query that 'StudentScore' is a type, which is not valid in the given context.
What I'm missing here. Anyone help me out.
Demo on dotnet fiddle
You should replace Student model by student list.
You should replace StudentScore model by studentScores list.
You need to group by 2 fields new {s.Name, ss.Subject} to be able to get in selecting result.
Use Aggregate Operators like Sum function to get TotalScore or Average or Count, Max, Min etc.
var totalscore = from s in student
join ss in studentScores on s.Id equals ss.StudentId
group ss by new {s.Name, ss.Subject} into sss
select new { sss.Key.Name, sss.Key.Subject, TotalScore = sss.Sum(p => p.Points) };
foreach (var ts in totalscore)
{
Console.WriteLine("{0}" + " " + "{1}" + " " + "{2}", ts.Name, ts.Subject, ts.TotalScore);
}
Output
Sam Maths 86
Sam English 109
Sam Biology 59
Rick Maths 81
Rick English 123
Rick Biology 109
Warner Maths 125
Warner English 113
Warner Biology 121

need to include school entity within classroom entity

I have 2 lists School & Classroom like below,
public static class WebApiData
{
public static List<School> GetAllSchools()
{
List<School> schools = new List<School>
{
new School
{
id = 1,
name = "Fort Craig Elementary",
principal = "Michelle Thorne"
},
new School
{
id = 2,
name = "Edgewood Elementary",
principal = "Audrey Hills"
}
};
return schools;
}
public static List<Classroom> GetAllClassrooms()
{
List<Classroom> classrooms = new List<Classroom>
{
new Classroom
{
id = 1,
name = "Mrs. Cox's 2nd Grade",
teacher = "Beth Cox",
school_id = 1
},
new Classroom
{
id = 2,
name = "Mr. Elliott's Kindergarten",
teacher = "Martin Elliott",
school_id = 1
},
new Classroom
{
id = 3,
name = "Mrs. Smith's 1st Grade",
teacher = "Amanda Smith",
school_id = 2
}
};
return classrooms;
}
}
Now I need to get all Classrooms along with entire school entity as a property of Classroom?
I tried join,
var result = from l1 in classrooms
join l2 in schools
on l1.school_id equals l2.id
select l1;
But instead of single property of school, I need entire school entity as a property of classroom in projection result?
Thanks,
Well, you could do in a different way. Make School a property of Classroom, instead of having the id only .
Then you can do what you want and create a single list with all the information.
Or even better, make Classroom a property of School. That makes more sense from an object abstraction point of view. Then you can build a list of all the classrooms in a school easily.
your School could look like this :
public class School
{
public int Id { get; set; }
public string Name { get; set; }
public List<Classroom> Classrooms { get; set; }
}
then you could instantiate your schools like this :
public static List<School> GetAllSchools()
{
List<School> schools = new List<School>
{
new School
{
Id =1 ,
Name = "1",
Classrooms = new List<Classroom> { new Classroom { Id = 1, Name = "1" }, new Classroom { Id = 2, Name = "2" } }
},
new School
{
Id =2 ,
Name = "2",
Classrooms = new List<Classroom> { new Classroom { Id = 3, Name = "3" }, new Classroom { Id = 4, Name = "4" } }
}
};
return schools;
}
You have two options.
Option 1
Include both object in your Select statement, you have an anonymous type with two properties, one represents ClassRoom and other School.
// Approach 1
var results = from l1 in classrooms
join l2 in schools
on l1.school_id equals l2.id
select new {l1, l2 };
Option 2
Create an anonymous data type with combined properties.
// Appraoch 2
var results = from l1 in classrooms
join l2 in schools
on l1.school_id equals l2.id
select new {
Id= l1.id,
Name = l1.name,
Teacher = l1.teacher,
SchoolId = l2.id,
SchoolName = l2.name,
Principal = l2.principal
};
Check working Example

Error null reference in left join

When i comment dishes.Add(new Dishes { DishID = 8, DishName = "Name", DishTypeID = 2, IngredientID = 2 }); i get in ll one item Amount="1 cup" DishID=1 Ingridient="egg" Name ="Soup". When uncomment that line raising error, null reference exception in b.IngredientTypeID. The main question how to get in ll two item's:
1) Amount="1 cup" DishID=1 Ingridient="egg" Name ="Soup"
2) Amount=null DishID=2 Ingridient=null Name =null
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Dishes> dishes = new List<Dishes>();
List<Ingredients> ingredients = new List<Ingredients>();
List<Amount> amount = new List<Amount>();
List<Ingredient> ingredient = new List<Ingredient>();
dishes.Add(new Dishes { DishID = 1, DishName = "Soup", DishTypeID = 1, IngredientID = 1 });
//dishes.Add(new Dishes { DishID = 8, DishName = "Name", DishTypeID = 2, IngredientID = 2 });
ingredients.Add(new Ingredients { AmountID = 2, IngredientID = 1, IngredientTypeID = 1, IngredientUniqID = 1 });
amount.Add(new Amount { AmountID = 2, AmountName = "1 cup" });
ingredient.Add(new Ingredient { IngredientID = 1, IngredientName = "egg" });
var test = from dish in dishes
join ing in ingredients on dish.IngredientID equals ing.IngredientID into result
from b in result.DefaultIfEmpty()
join i in ingredient on b.IngredientTypeID equals i.IngredientID into r
from c in r.DefaultIfEmpty()
join am in amount on b.AmountID equals am.AmountID into s
from t in s.DefaultIfEmpty()
select new DisplayRecipe { Name = dish.DishName, Amount = t.AmountName, Ingredient = c.IngredientName, DishID = dish.DishID };
List<DisplayRecipe> ll = test.ToList();
}
}
public partial class Dishes
{
public int DishID { get; set; }
public string DishName { get; set; }
public Nullable<int> DishTypeID { get; set; }
public Nullable<int> IngredientID { get; set; }
}
public partial class Ingredients
{
public int IngredientID { get; set; }
public Nullable<int> AmountID { get; set; }
public Nullable<int> IngredientTypeID { get; set; }
public int IngredientUniqID { get; set; }
}
public partial class Amount
{
public int AmountID { get; set; }
public string AmountName { get; set; }
}
public partial class Ingredient
{
public int IngredientID { get; set; }
public string IngredientName { get; set; }
}
public class DisplayRecipe
{
public string Name { get; set; }
public string Ingredient { get; set; }
public string Amount { get; set; }
public int DishID { get; set; }
}
}
The problem is that any of the b, c, t variables can be null due to DefaultIfEmpty and you need to account for that in any member access, including join conditions.
If you are using C#6 (VS2015), you can use ?. operator like this
var test = from dish in dishes
join ing in ingredients on dish.IngredientID equals ing.IngredientID into result
from b in result.DefaultIfEmpty()
join i in ingredient on b?.IngredientTypeID equals i.IngredientID into r
from c in r.DefaultIfEmpty()
join am in amount on b?.AmountID equals am.AmountID into s
from t in s.DefaultIfEmpty()
select new DisplayRecipe { Name = dish.DishName, Amount = t?.AmountName, Ingredient = c?.IngredientName, DishID = dish.DishID };
while in pre C#6:
var test = from dish in dishes
join ing in ingredients on dish.IngredientID equals ing.IngredientID into result
from b in result.DefaultIfEmpty()
join i in ingredient on b != null ? b.IngredientTypeID : null equals i.IngredientID into r
from c in r.DefaultIfEmpty()
join am in amount on b != null ? b.AmountID : null equals am.AmountID into s
from t in s.DefaultIfEmpty()
select new DisplayRecipe { Name = dish.DishName, Amount = t != null ? t.AmountName : null, Ingredient = c != null ? c.IngredientName : null, DishID = dish.DishID };
Problem is you added this line:
dishes.Add(new Dishes { DishID = 8, DishName = "Name", DishTypeID = 2, IngredientID = 2 });
But did not also add the other lines that are dependent on your join (example):
ingredients.Add(new Ingredients { AmountID = 2, IngredientID = 2, IngredientTypeID = 1, IngredientUniqID = 1 });
ingredient.Add(new Ingredient { IngredientID = 2, IngredientName = "ham" });
So when your program tries to find an ingredientID of 2 because that has been added to dishes it does not find one and produces an error.
Sample of code that works:
dishes.Add(new Dishes { DishID = 1, DishName = "Soup", DishTypeID = 1, IngredientID = 1 });
dishes.Add(new Dishes { DishID = 8, DishName = "Name", DishTypeID = 2, IngredientID = 2 });
ingredients.Add(new Ingredients { AmountID = 2, IngredientID = 1, IngredientTypeID = 1, IngredientUniqID = 1 });
ingredients.Add(new Ingredients { AmountID = 2, IngredientID = 2, IngredientTypeID = 1, IngredientUniqID = 1 });
amount.Add(new Amount { AmountID = 2, AmountName = "1 cup" });
ingredient.Add(new Ingredient { IngredientID = 1, IngredientName = "egg" });
ingredient.Add(new Ingredient { IngredientID = 2, IngredientName = "ham" });

How to combine three lists of objects by primary key using linq

I'm trying to combine 3 lists of objects. I have a person list, address list and an addressRelation list.
I want to combine these lists into a new list ordered by person.id, use it as a datasource for a listview, and then be able to access the properties in the aspx page.
Is this possible?
Roughly
using System.Linq;
class Person
{
public int Id;
public string Name;
}
class Address
{
public int Id;
public string Street;
}
class PersonAddress
{
public int PersonId, AddressId;
}
public class Program
{
public static void Main(string[] args)
{
var personList = new []
{
new Person { Id = 1, Name = "Pete" },
new Person { Id = 2, Name = "Mary" },
new Person { Id = 3, Name = "Joe" }
};
var addressList = new []
{
new Address { Id = 100, Street = "Home Lane" },
new Address { Id = 101, Street = "Church Way" },
new Address { Id = 102, Street = "Sandy Blvd" }
};
var relations = new []
{
new PersonAddress { PersonId = 1, AddressId = 101 },
new PersonAddress { PersonId = 3, AddressId = 101 },
new PersonAddress { PersonId = 2, AddressId = 102 },
new PersonAddress { PersonId = 2, AddressId = 100 }
};
var joined =
from par in relations
join p in personList
on par.PersonId equals p.Id
join a in addressList
on par.AddressId equals a.Id
select new { Person = p, Address = a };
foreach (var record in joined)
System.Console.WriteLine("{0} lives on {1}",
record.Person.Name,
record.Address.Street);
}
}
Outputs:
Pete lives on Church Way
Mary lives on Sandy Blvd
Mary lives on Home Lane
Joe lives on Church Way

Categories

Resources