I have two tables:
Retailers
Invoices
Retailers has two columns:
1.1. RetailerID
1.2. RetailerName
Invoices has three columns:
2.1. InvoiceID
2.2. InvoiceProfit
2.3. RetailerID
Retailers.RetailerID is linked to Invoices.RetailerID (one-to-many).
What I want to do is write a linq (or in the form of a lambda exp) that returns Retailer.RetailerID, Retailer.RetailerName, Invoice.InvoiceProfit.
I can do this like so:
var retailers = from r in db.Retailers select t;
var invoices = from i in db.Invoices select i;
var retailersAndInvoices = from r in retailers join i in invoices on r.RetailerID equals i.RetailerID select new {t.RetailerName, i.InvoiceProfit};
I want to return only Distinct RetailerNames and the Sum of all Invoices.InvoiceProfit next to each one - the purpose being "Top Ten Retailers"!
How can i do this?
Use GroupBy to convert a flat list to groups by RetailerName
Use Sum(i => i.InvoiceProfit) to compute totals
Use new { ... } to pair up retailers with their profit totals
Use OrderByDescending(p => p.TotalProfit) to get high-profit retailers to the top
Use Take(10) to limit the list to ten items.
Overall query would look like this:
var topTen = retailersAndInvoices
.GroupBy(ri => ri.RetailerName)
.Select(g => new {
Retailer = g.Key
, TotalProfit = g => g.Sum(i => i.InvoiceProfit)
})
.OrderByDescending(p => p.TotalProfit)
.Take(10)
.ToList();
I use combination of lambda and linq. See msdn : https://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication28
{
class Program
{
static void Main(string[] args)
{
DataTable retailers = new DataTable();
retailers.Columns.Add("RetailerID", typeof(int));
retailers.Columns.Add("RetailerName", typeof(string));
retailers.Rows.Add(new object[] { 123, "abc" });
retailers.Rows.Add(new object[] { 124, "abd" });
retailers.Rows.Add(new object[] { 125, "abe" });
DataTable invoices = new DataTable();
invoices.Columns.Add("InvoiceID", typeof(int));
invoices.Columns.Add("InvoiceProfit", typeof(decimal));
invoices.Columns.Add("RetailerID", typeof(int));
invoices.Rows.Add(new object[] { 100, 200, 123 });
invoices.Rows.Add(new object[] { 101, 201, 123 });
invoices.Rows.Add(new object[] { 102, 202, 123 });
invoices.Rows.Add(new object[] { 103, 203, 123 });
invoices.Rows.Add(new object[] { 104, 204, 124 });
invoices.Rows.Add(new object[] { 105, 205, 124 });
invoices.Rows.Add(new object[] { 106, 206, 124 });
invoices.Rows.Add(new object[] { 107, 207, 125 });
invoices.Rows.Add(new object[] { 108, 208, 125 });
invoices.Rows.Add(new object[] { 109, 209, 125 });
var retailersAndInvoices = (from r in retailers.AsEnumerable()
join i in invoices.AsEnumerable() on r.Field<int>("RetailerID") equals i.Field<int>("RetailerID")
select new { name = r.Field<string>("RetailerName"), profit = i.Field<decimal>("InvoiceProfit") })
.GroupBy(x => x.name)
.Select(x => new { name = x.Key, totalProfit = x.Select(y => y.profit).Sum() }).ToList();
}
}
}
Related
I have got this situation with a datatable like this
C1 C2 C3
A AA 4
BB 6
B CC 3
DD 3
EE 4
C FF 5
GG 5
and my output should be like this
C1 C2 C3
A AA,BB 10
B CC,DD,EE 10
C FF,GG 10
How can i group by the column with the space till the next value comes up
What i did was i took all the row itemarray and then using some string manipulation and regex got the row value as for the first two values like this and assigned to a variable in a query using Let
A,AA,BB,10|B,CC,DD,EE,10 but then i cannot add it using the
**DT.clone.rows.Add(x.split("|"c))* method as there its not incrementing and adding the whole joined string
Any other input where i can manipulate and add it (P.S i know linq is querying language)
Thank you for your time
You can use .GroupBy to get result needed
Here is your class:
public class Data
{
public string C1 { get; set; }
public string C2 { get; set; }
public int C3 { get; set; }
}
Imagine that you have list of Data objects, so your GroupBy expression will be following:
var result = list.GroupBy(g => g.C1, (a, b) => new {C1 = a, C2 = b.ToList()})
.Select(g => new
{
g.C1,
C2 = string.Join(",", g.C2.Select(m => m.C2)),
C3 = g.C2.Sum(m => m.C3)
})
.ToList();
A simple .GroupBy can give you expected result, Edited to handle Null or WhiteSpace Columns
var res = ListModel.Where(e => !string.IsNullOrWhiteSpace(e.C1)
&& !string.IsNullOrWhiteSpace(e.C2))
.GroupBy(e => e.C1).Select(e => new
{
e.Key,
c2 = string.Join(",", e.Select(x => x.C2).ToList()),
c3 = e.Sum(x => x.C3)
}).ToList();
Hello All first of all Thank you for your time and effort i Did this use case using this code
This gave me all row item array in string and than in the end with a little Split method i was able to add it to my datatable
String.Join("|",(System.Text.RegularExpressions.Regex.Replace(String.Join("|",(From roww In DT.AsEnumerable() Select String.Join(",",roww.ItemArray) ).ToList),"\|,",",")).Split("|"c).
Select(Function(q)CStr(q)+","+CStr(String.join("|",System.Text.RegularExpressions.Regex.Matches(CStr(q),"\d+").Cast(Of match)).Split("|"c).Sum(Function(r) CInt(r) ))).tolist),",\d+,",",")```
Try following code which is tested
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable dt1 = new DataTable();
dt1.Columns.Add("C1", typeof(string));
dt1.Columns.Add("C2", typeof(string));
dt1.Columns.Add("C3", typeof(int));
dt1.Rows.Add(new object[] { "A", "AA", 4});
dt1.Rows.Add(new object[] { null, "BB", 6});
dt1.Rows.Add(new object[] { "B", "CC", 3});
dt1.Rows.Add(new object[] { null, "DD", 3});
dt1.Rows.Add(new object[] { null, "EE", 4});
dt1.Rows.Add(new object[] { "C", "FF", 5});
dt1.Rows.Add(new object[] { null, "GG", 5});
//replace nulls in column 1 with actual values
string previous = "";
foreach(DataRow row in dt1.AsEnumerable())
{
if (row.Field<string>("C1") == null)
{
row["C1"] = previous;
}
else
{
previous = row.Field<string>("C1");
}
}
DataTable dt2 = dt1.Clone();
var groups = dt1.AsEnumerable().GroupBy(x => x.Field<string>("C1")).ToList();
foreach (var group in groups)
{
dt2.Rows.Add(new object[] {
group.Key,
string.Join(",", group.Select(x => x.Field<string>("C2"))),
group.Select(x => x.Field<int>("C3")).Sum()
});
}
}
}
}
Yet another way using Skip, TakeWhile, and GroupBy extensions:
DataTable dt1 = new DataTable();
dt1.Columns.Add("C1", typeof(string));
dt1.Columns.Add("C2", typeof(string));
dt1.Columns.Add("C3", typeof(int));
//The output table.
DataTable dt2 = dt1.Clone();
dt1.Rows.Add(new object[] { "A", "AA", 3 });
dt1.Rows.Add(new object[] { null, "BB", 6 });
dt1.Rows.Add(new object[] { "B", "CC", 3 });
dt1.Rows.Add(new object[] { null, "DD", 3 });
dt1.Rows.Add(new object[] { null, "EE", 4 });
dt1.Rows.Add(new object[] { "C", "FF", 5 });
dt1.Rows.Add(new object[] { null, "GG", 6 });
var rows = dt1.Rows.Cast<DataRow>().AsEnumerable();
foreach (var row in rows.Where(r => r.Field<string>("C1") != null))
{
var indx = dt1.Rows.IndexOf(row) + 1;
var q = rows
.Skip(indx)
.TakeWhile(t => t.Field<string>("C1") == null)
.GroupBy(g => g.Field<string>("C1"))
.Select(g => new
{
C1 = row.Field<string>("C1"),
C2 = $"{row.Field<string>("C2")}, {string.Join(", ", g.Select(s => s.Field<string>("C2")))}",
C3 = row.Field<int>("C3") + g.Sum(s => s.Field<int>("C3")),
}).FirstOrDefault();
if (q != null)
dt2.Rows.Add(q.C1, q.C2, q.C3);
}
dataGridView1.DataSource = null;
dataGridView1.DataSource = dt2;
The idea behind this snippet is to:
Get the complete rows and iterate through them.
For each complete row, we get it's index from the original DataTable and add 1 to make a starting search point for the incomplete rows. The Skip extension is the method to achieve that.
The TakeWhile extension function gets the incomplete rows and stops at the next complete row.
The GroupBy extension function groups the incomplete rows to concatenate their C2 values and sum their C3 values, add the results to the values of the complete row and create a temporary anonymous object to hold these values.
Extract the anonymous object and add a new DataRow to the output DataTable.
And finally, bind the output DataTable to a DGV.
Happy 2020 for all.
I have a stored procedure that looks like this:
SELECT UrlId, TypeId, DomainId, Url, d.OrgId AS orgId
FROM SystemUrls
JOIN Domaindata d ON d.Id = DomainId
It gives me this result:
in the code this looks like:
What I would like:
is to group this result on the domainId
so that I get two rows.
I have this class:
public class TestModel
{
public long DomainId { get; set; }
public List<SystemUrl> Urls { get; set; }
}
I am trying to get a resul like :
DomainId 79 Urls.count() = 2
DomainId 81 Urls.COunt = 2
My attempt:
var t =
(from u in urls
select
new TestModel
{
DomainId = u.DomainId,
Urls = new List<SystemUrl> {new SystemUrl {Url = u.Url}}
}).GroupBy(v => v.DomainId).ToList();
the problem is that I seem to get the domainId right but none of the other data seems to follow:
How can I solve this?
var t = urls.GroupBy(u => u.DomainId)
.Select(g => new TestModel{
DomainId = g.Key,
Urls = g.Select(u => new SystemUrl {Url = u.Url}).ToList()
})
.ToList();
Here is how you can do it:
var t = urls.GroupBy(v => v.DomainId).Select(g => new TestModel
{
DomainId = g.Key,
Urls = g.Select(u => new SystemUrl {Url = u.Url}).ToList()
}).ToList();
Put DataAdapter reults into a datatable . Then use following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
namespace ConsoleApplication49
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Urld", typeof(int));
dt.Columns.Add("Typeid", typeof(int));
dt.Columns.Add("DomainId", typeof(int));
dt.Rows.Add(new object[] { 13, 1, 79 });
dt.Rows.Add(new object[] { 14, 2, 79 });
dt.Rows.Add(new object[] { 15, 2, 81 });
dt.Rows.Add(new object[] { 16, 1, 81 });
var results = dt.AsEnumerable()
.GroupBy(x => x.Field<int>("DomainID"))
.ToDictionary(x => x.Key, y => y.Count());
}
}
}
I am stuck on this problem. I want to get users who are in the "Developer" role, who either are not assigned any tasks or are assigned 3 or fewer tasks which are marked "Active" or "Testing".
I don't know how to make a statement where I get all tasks with Status "Active, Testing" and sum them up for my count statement.
//here i specific role for users
var role = unitOfWork.RoleRepository._context.Roles.SingleOrDefault(m => m.Name == "Developer");
var query = (from u in users
where u.Roles.Any(r => r.RoleId == role.Id)
from t in u.ProjectTasks.Where(x => x.Users.Any(user => user.Id == u.Id)).DefaultIfEmpty()
//here I am able to get those users without assigned tasks but I dont know how to make the second condition with 3 or less tasks
where ((u.ProjectTasks.Count() == 0) || u.ProjectTasks.Any(z => z.Status == Status.Active || z.Status == Status.Testing))
select new { User = u } into Users
group Users by Users.User).ToList();
Here is my database model:
Perhaps the LINQ Count function is called for:
var query = (from u in users
where u.Roles.Any(r => r.RoleId == role.Id)
from t in u.ProjectTasks.Where(x => x.Users.Any(user => user.Id == u.Id)).DefaultIfEmpty()
where ((u.ProjectTasks.Count() == 0) || u.ProjectTasks.Count(z => z.Status == Status.Active || z.Status == Status.Testing) <= 3)
select new { User = u } into Users
group Usersby Users.User).ToList();
For examples of usages: http://www.csharp-examples.net/linq-count/
I simulated the database with DataTable to show how it is done
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable AspNetRoles = new DataTable();
AspNetRoles.Columns.Add("Id", typeof(int));
AspNetRoles.Columns.Add("Name", typeof(string));
AspNetRoles.Rows.Add(new object[] { 123, "Developer" });
AspNetRoles.Rows.Add(new object[] { 456, "Non Developer" });
DataTable AspNetUserRoles = new DataTable();
AspNetUserRoles.Columns.Add("UserId", typeof(int));
AspNetUserRoles.Columns.Add("RoleId", typeof(int));
AspNetUserRoles.Rows.Add(new object[] { 100, 123 });
AspNetUserRoles.Rows.Add(new object[] { 200, 456 });
AspNetUserRoles.Rows.Add(new object[] { 300, 123 });
AspNetUserRoles.Rows.Add(new object[] { 400, 456 });
DataTable AspNetUsers = new DataTable();
AspNetUsers.Columns.Add("Id", typeof(int));
AspNetUsers.Rows.Add(new object[] { 100 });
AspNetUsers.Rows.Add(new object[] { 200 });
AspNetUsers.Rows.Add(new object[] { 300 });
AspNetUsers.Rows.Add(new object[] { 400 });
DataTable UserToTask = new DataTable();
UserToTask.Columns.Add("UserId", typeof(int));
UserToTask.Columns.Add("TaskId", typeof(int));
UserToTask.Rows.Add(new object[] { 100, 1000 });
UserToTask.Rows.Add(new object[] { 100, 1001 });
UserToTask.Rows.Add(new object[] { 100, 1002 });
UserToTask.Rows.Add(new object[] { 200, 1001 });
UserToTask.Rows.Add(new object[] { 200, 1004 });
UserToTask.Rows.Add(new object[] { 200, 1006 });
UserToTask.Rows.Add(new object[] { 300, 1005 });
UserToTask.Rows.Add(new object[] { 300, 1006 });
UserToTask.Rows.Add(new object[] { 400, 1007 });
UserToTask.Rows.Add(new object[] { 400, 1008 });
DataTable ProjectTasks = new DataTable();
ProjectTasks.Columns.Add("Id", typeof(int));
ProjectTasks.Columns.Add("Status", typeof(string));
ProjectTasks.Rows.Add(new object[] { 1000, "Active" });
ProjectTasks.Rows.Add(new object[] { 1001, "Testing" });
ProjectTasks.Rows.Add(new object[] { 1002, "Idle" });
ProjectTasks.Rows.Add(new object[] { 1003, "Active" });
ProjectTasks.Rows.Add(new object[] { 1004, "Testing" });
ProjectTasks.Rows.Add(new object[] { 1005, "Idle" });
ProjectTasks.Rows.Add(new object[] { 1006, "Active" });
ProjectTasks.Rows.Add(new object[] { 1007, "Testing" });
ProjectTasks.Rows.Add(new object[] { 1008, "Idle" });
var results = (from aspNetUser in AspNetUsers.AsEnumerable()
join task in UserToTask.AsEnumerable() on aspNetUser.Field<int>("Id") equals task.Field<int>("UserId")
join proj in ProjectTasks.AsEnumerable() on task.Field<int>("TaskId") equals proj.Field<int>("Id")
join aspNetUserRole in AspNetUserRoles.AsEnumerable() on aspNetUser.Field<int>("Id") equals aspNetUserRole.Field<int>("UserId")
join aspNetRole in AspNetRoles.AsEnumerable() on aspNetUserRole.Field<int>("RoleId") equals aspNetRole.Field<int>("Id")
select new { aspNetUser = aspNetUser, task = task, proj = proj, aspNetUserRole = aspNetUserRole, aspNetRole = aspNetRole }).ToList();
var finalResults = results.GroupBy(x => x.aspNetUser.Field<int>("Id"))
.Where(x => (x.Select(y => y.task).Count() == 0) || (x.Where(y => (y.proj.Field<string>("Status") == "Active") || (y.proj.Field<string>("Status") == "Testing"))).Count() <= 3).ToList();
}
}
}
I am pulling some historical data from Firebird database as below:
Product_ID Date Price
1 2001-01-01 10
1 2001-02-01 10
1 2001-03-01 15
1 2001-04-01 10
1 2001-05-01 20
1 2001-06-01 20
What I am trying to do is to extract the first for occurrence every price change.
Example of expected data set:
Product_ID Date Price
1 2001-01-01 10
1 2001-03-01 15
1 2001-04-01 10
1 2001-05-01 20
I know that on MSSQL I could leverage LAG for that. Is it possible to do that with Firebird?
You can try this, but be aware I didn't tested it:
CREATE PROCEDURE SP_Test
RETURNS (
Product_ID INTEGER,
Date DATE,
Price INTEGER
)
AS
DECLARE VARIABLE Last_Product_ID INTEGER;
DECLARE VARIABLE Last_Date DATE;
DECLARE VARIABLE Last_Price INTEGER;
BEGIN
FOR SELECT Product_ID, Date, Price
FROM xxxx
ORDER BY Product_ID, Date
INTO Product_ID, Date, Price
DO BEGIN
IF ((:Last_Product_ID IS NULL) OR
(:Last_Date IS NULL) OR
(:Last_Price IS NULL) OR
(:Product_ID <> :Last_Product_ID) OR
(:Price <> :Last_Price)) THEN
SUSPEND;
Last_Product_ID = :Product_ID;
Last_Date = :Date;
Last_Price = :Price;
END;
END;
in MoreLinq there is a Lag extension method but it is supported only in Linq to Objects...
What you can do, if you are looking for a C# linq answer for that you can:
Basically order your data the correct way and then add a row index for while price (and product_id) is still the same. Then group by it and select the min date.
int groupingIndex = 0;
int previousPrice = 0;
var response = data
.OrderBy(item => item.Product_ID)
.ThenBy(item => item.Date)
.Select(item =>
{
if (item.Price != previousPrice)
{
previousPrice = item.Price;
groupingIndex++;
}
return new { Index = groupingIndex, Item = item };
})
.GroupBy(item => new { item.Index, item.Item.Product_ID, item.Item.Price } )
.Select(group => new Record
{
Product_ID = group.Key.Product_ID,
Price = group.Key.Price,
Date = group.Min(item => item.Item.Date)
}).ToList();
And if you don't mind doing the operation in the C# and not the DB (and using a beta version of the MoreLinq) then:
int index = 0;
var result2 = data
.OrderBy(item => item.Product_ID)
.ThenBy(item => item.Date)
.Lag(1, (current, previous) => new { Index = (current.Price == previous?.Price ? index : ++index), Item = current })
.GroupBy(item => new { item.Index, item.Item.Product_ID, item.Item.Price })
.Select(group => new Record { Product_ID = group.Key.Product_ID, Price = group.Key.Price, Date = group.Min(item => item.Item.Date) })
.ToList();
This is a little complicated but it works
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
DataTable dt = new DataTable();
dt.Columns.Add("Product_ID", typeof(int));
dt.Columns.Add("Date", typeof(DateTime));
dt.Columns.Add("Price", typeof(int));
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-01-01"), 10});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-02-01"), 10});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-03-01"), 15});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-04-01"), 10});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-05-01"), 20});
dt.Rows.Add(new object[] {1, DateTime.Parse("2001-06-01"), 20});
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-01-01"), 10 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-02-01"), 10 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-03-01"), 15 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-04-01"), 10 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-05-01"), 20 });
dt.Rows.Add(new object[] { 2, DateTime.Parse("2001-06-01"), 20 });
dt = dt.AsEnumerable().OrderBy(x => x.Field<DateTime>("Date")).CopyToDataTable();
List<DataRow> results = dt.AsEnumerable()
.GroupBy(g => g.Field<int>("Product_ID"))
.Select(g1 => g1.Select((x, i) => new { row = x, dup = (i == 0) || ((i > 0) && (g1.Skip(i - 1).FirstOrDefault().Field<int>("Price") != g1.Skip(i).FirstOrDefault().Field<int>("Price"))) ? false : true })
.Where(y => y.dup == false).Select(z => z.row)).SelectMany(m => m).ToList();
}
}
}
I have a Datatable as below and I want to select repeated names from other Classes
Name Class
Akbar 1B
Akbar 1B
Amar 1A
Amar 1C
Antoney 1A
Bindhu 1B
Bindhu 1D
John 1C
Raj 1B
Bindhu 2A
Th result should be as below
Amar 1A
Amar 1C
Bindhu 1D
Bindhu 1B
Bindhu 2A
Thanks in advance for any guidance
Code
var dtStudents = new DataTable();
dtStudents.Columns.Add("StudentID", typeof(int));
dtStudents.Columns.Add("StudentName", typeof(string));
dtStudents.Columns.Add("Class", typeof(string));
dtStudents.Columns.Add("ContactNo", typeof(string));
DataRow drStudent = dtStudents.NewRow();
drStudent["StudentID"] = 1;
drStudent["StudentName"] = "Akbar";
drStudent["Class"] = "1B";
drStudent["ContactNo"] = "989878679";
dtStudents.Rows.Add(drStudent);
dtStudents.Rows.Add(new object[] { 2, "Akabr", "1B", "989777" });
dtStudents.Rows.Add(new object[] { 3, "Amar", "1A", "3453" });
dtStudents.Rows.Add(new object[] { 4, "Amar", "1C", "543534" });
dtStudents.Rows.Add(new object[] { 5, "Antoney", "1A", "54345" });
dtStudents.Rows.Add(new object[] { 6, "Bindhu", "1B", "53453" });
dtStudents.Rows.Add(new object[] { 7, "Bindhu", "1D", "3453453" });
dtStudents.Rows.Add(new object[] { 8, "John", "1C", "3245345" });
dtStudents.Rows.Add(new object[] { 9, "Bindhu", "2A", "5345345" });
var results =
from d in dtStudents.AsEnumerable()
select d;
dataGridView1.DataSource = results.CopyToDataTable<DataRow>();
var results = dtStudents.AsEnumerable()
.GroupBy(
x => x.Field<string>("StudentName"), // group by student name
(k, xs) => xs.GroupBy(
x1 => x1.Field<string>("Class"), // group by class
(k1, xs1) => xs1.First())) // if there are duplicates, take only the first
.Where(x => x.Count() >= 2) // remove if student only has one class
.SelectMany(x => x); // flatten back to a single collection
dataGridView1.DataSource = results.CopyToDataTable<DataRow>();
Well, you don't really specify how you are connecting to your database so... regardless, let's assume you have some IQueryable called, I don't know, classes, or something. Then you can do the following:
classes
.GroupBy(
x => x.Name,
(key, values) => new { Name = key, Classes = values.Select(x => x.Class).Distinct())
.Where(x => x.Classes.Take(2).Count() == 2)
I would go about this this way:
var results =
dtStudents
.AsEnumerable()
.Select(x => new
{
StudentID = (int)x[0],
StudentName = (string)x[1],
Class = (string)x[2],
ContactNo = (string)x[3],
})
.GroupBy(
x => x.StudentName,
(key, xs) => new
{
StudentName = key,
Classes = xs.Select(x => x.Class).Distinct()
})
.Where(x => x.Classes.Skip(1).Any())
.SelectMany(x => x.Classes
.Select(y => new
{
StudentName = x.StudentName,
Class = y
}));