How to Add Rownum to GroupBy Linq - c#

I have a complex LINQ Query to extract Top students in my university. Here is the query :
var query = Db.Students.AsNoTracking().Where(...).AsQueryable();
var resultgroup = query.GroupBy(st => new
{
st.Student.CourseStudyId,
st.Student.EntranceTermId,
st.Student.StudyingModeId,
st.Student.StudyLevelId
}, (key, g) => new
{
CourseStudyId = key.CourseStudyId,
EntranceTermId = key.EntranceTermId,
StudyingModeId = key.StudyingModeId,
StudyLevelId = key.StudyLevelId,
list = g.OrderByDescending(x =>
x.StudentTermSummary.TotalAverageTillTerm).Take(topStudentNumber)
}).SelectMany(q => q.list).AsQueryable();
This Query give me top n students based on 4 parameters and on their TotalAverageTillTerm.
Now I want to add rownum for each group to simulate Total rank, for example Output is :
Now I want to Add TotalRank as rownumber like Sql. In the picture X1=1,X2=2,X3=3 and Y1=1,Y2=2,Y3=3
If I want to reduce problem. I only work on one group. Code Like this :
resultgroup = query.GroupBy(st => new
{
st.Student.StudyLevelId
}, st => st, (key, g) => new
{
StudyLevelId = key.StudyLevelId,
list = g.OrderByDescending(x =>
x.StudentTermSummary.TotalAverageTillTerm)
.Take(topStudentNumber)
}).SelectMany(q => q.list).AsQueryable();

list was a List of student but I see no sign of student having a rank property so I wrapped it into a annonimous type with rank.
var query = Db.Students.AsNoTracking().Where(...).AsEnumerable();
var resultgroup = query.GroupBy(st => new {
st.Student.CourseStudyId,
st.Student.EntranceTermId,
st.Student.StudyingModeId,
st.Student.StudyLevelId
})
.SelectMany( g =>
g.OrderByDescending(x =>x.StudentTermSummary.TotalAverageTillTerm)
.Take(topStudentNumber)
.Select((x,i) => new {
CourseStudyId = g.Key.CourseStudyId,
EntranceTermId = g.Key.EntranceTermId,
StudyingModeId = g.Key.StudyingModeId,
StudyLevelId = g.Key.StudyLevelId,
Rank = i+1
//studentPorperty = x.Prop1,
})
)
.AsQueryable();

Do you mean :
var query = Db.Students.AsNoTracking().Where(...).AsQueryable();
var resultgroup = query.GroupBy(st => new
{
st.Student.CourseStudyId,
st.Student.EntranceTermId,
st.Student.StudyingModeId,
st.Student.StudyLevelId
}, (key, g) => new
{
CourseStudyId = key.CourseStudyId,
EntranceTermId = key.EntranceTermId,
StudyingModeId = key.StudyingModeId,
StudyLevelId = key.StudyLevelId,
list = g.OrderByDescending(x =>
x.StudentTermSummary.TotalAverageTillTerm)
.Take(topStudentNumber)
.Select((x, i) => new { Item = x, TotalRank = i /* item number inside group */}),
StudentsInGroupCount = g.Count() // count group this items
}).SelectMany(q => q).AsQueryable();
To see the results :
foreach (var item in resultgroup.ToList())
{
item.list.ForEach(s => Console.WriteLine(s.TotalRank));
}

Related

How can i use distinct with count in Linq?

The extention method below does not have Distinct and Count
public static IEnumerable<Something> ToFilterModel(this IEnumerable<Product> products)
{
var v = products
.SelectMany(x => x.ProductVariants)
.GroupBy(x => x.OptionId)
.Select(x => new
{
Id = x.Key.ToString(),
Items = x.Select(x => new Item { Id = x.ValueId, Text = x.Value.OptionValue })
});
return v;
}
Given the input below it should return 2 Items rows and not 3, since i am interested for ValueIds
and also Count by ValueIds
how should i modify it?
More spesifically it should return items with rows 1 and 2 and also
Count equal to 1 for the first row and Count equal to 2 for the second row.
You could group by ValueId the grouped options, like :
Items = x
.GroupBy(y => y.ValueId)
.Select(z => new Item { Id = z.Key, Text = z.First().Value.OptionValue, Count = z.Count() })
The result will be :
{
"Id":1,
"Items":[
{
"Id":1,
"Text":"text1",
"Count":1
},
{
"Id":2,
"Text":"text2",
"Count":2
}
]
}
NOTE : the Text is the count of grouped value ids.
The whole code :
var v = products
.SelectMany(x => x.ProductVariants)
.GroupBy(x => x.OptionId)
.Select(x => new
{
Id = x.Key.ToString(),
Items = x
.GroupBy(y => y.ValueId)
.Select(z => new Item { Id = z.Key, Text = z.First().Value.OptionValue, Count = z.Count() })
});

How do I create groups and subgroup1 and subgroup2 Use linq

How do I create groups and subgroup1 and subgroup2 Use linq.
Example of this picture
I want to create json.
Example of this picture.
I tried to do this but there was a problem.
The items are repeated within one subgroup2.
var list = result
.GroupBy(x => new { x.GroupId, x.GroupName })
.Select(g => new
{
ID = g.Key.GroupId,
Name = g.Key.GroupName,
SubGroup1 = g.GroupBy(x => new { x.SubGroupID1, x.SubGroupName1 })
.Select(cg => new
{
ID = cg.Key.SubGroupID1,
Name = cg.Key.SubGroupName1,
SubGroup2 = g.GroupBy(x => new { x.SubGroupID2, x.SubGroupName2 })
.Select(ii => new
{
ID = ii.Key.SubGroupID2,
Name = ii.Key.SubGroupName2,
item = ii.GroupBy(x => new { x.Stock_Id, x.Stock_Name, x.Prices, x.ScreenNumber })
.Select(oo => new
{
Stock_Id = oo.Key.Stock_Id,
Stock_Name = oo.Key.Stock_Name,
Prices = oo.Key.Prices,
ScreenNumber = oo.Key.ScreenNumber
}).OrderBy(Or => Or.Stock_Id)
.ToList()
}).OrderBy(Or => Or.ID)
.ToList()
}).OrderBy(Or => Or.ID)
.ToList()
}).OrderBy(Or => Or.ID)
.ToList();
Your query could be a lot cleaner if you grouped the groups up front, then project out to your desired results.
var query =
from x in data
group new { x.StockId, x.StockName, x.Prices, x.ScreenNumber }
by new { x.GroupId, x.GroupName, x.SubGroupId1, x.SubGroupName1, x.SubGroupId2, x.SubGroupName2 }
into g
group g
by new { g.Key.GroupId, g.Key.GroupName, g.Key.SubGroupId1, g.Key.SubGroupName1 }
into g2
group g2
by new { g2.Key.GroupId, g2.Key.GroupName }
into g1
select new
{
Id = g1.Key.GroupId,
Name = g1.Key.GroupName,
SubGroup1 = g1.Select(g2 => new
{
Id = g2.Key.SubGroupId1,
Name = g2.Key.SubGroupName1,
SubGroup2 = g2.Select(g => new
{
Id = g.Key.SubGroupId2,
Name = g.Key.SubGroupName2,
Items = g.Select(x => new
{
x.StockId,
x.StockName,
x.Prices,
x.ScreenNumber,
}),
}),
}),
};
The idea is to start off with the most specific grouping first, then one-by-one group the groups by the next layer, and so on.
SubGroup2 = g.GroupBy(x => new { x.SubGroupID2, x.SubGroupName2 })
You are grouping g instead of cg.
I suggest structuring your code a bit, which would help avoiding this kind of mistake.

Linq group by with parent object

How do I group so that I don't loose the parent identifier.
I have the following
var grouped = mymodel.GroupBy(l => new { l.AddressId })
.Select(g => new
{
AddressId = g.Key.AddressId,
Quotes = g.SelectMany(x => x.Quotes).ToList(),
}).ToList();
this returns
{ AddressId1, [Quote1, Quote2, Quote3...]}
{ AddressId2, [Quote12, Quote5, Quote8...]}
Now I would like to group these by Quote.Code and Quote.Currency, So that Each address has 1 Object-Quote (that is if all 4 quotes belonging to the address have the same Code and Currency). I would like the sum of Currency in that object.
This works, but I can't get how to add Address to this result:
var test = grouped.SelectMany(y => y.Quotes).GroupBy(x => new { x.Code, x.Currency }).Select(g => new
{
test = g.Key.ToString()
});}
this gives compile error, whenever i try to add AddressId to result:
var test1 = grouped.SelectMany(y => y.Quotes, (parent, child) => new { parent.AddressId, child }).GroupBy(x => new { x.Provider, x.Code, x.Currency, x.OriginalCurrency }).Select(g => new
{
test = g.Key.ToString(),
Sum = g.Sum(x => x.Price)
});
compiler error as well:
var test1 = grouped.Select(x => new { x.AddressId, x.Quotes.GroupBy(y => new { y.Provider, y.Code, y.Currency, y.OriginalCurrency }).Select(g => new
{
addr = x.AddressId,
test = g.Key.ToString(),
Sum = g.Sum(q => q.Price)
};
I would do that this way:
var grouped = mymodel.GroupBy(l => new { l.AddressId })
.Select(g => new
{
AddressId = g.Key.AddressId,
QuotesByCode = g.SelectMany(x => x.Quotes)
.GroupBy(x=>x.Code)
.Select(grp=>new
{
Code = grp.Key.Code,
SumOfCurrency=grp.Sum(z=>z.Currency)
}).ToList(),
}).ToList();

Get item index of a list within Select statement

I need to index the item list with its position after grouping
var result = from i in items
group i by i.name into g
select new { groupname = g.Key,
index = //need to get the index of the item
};
How to get the item index of a list using linq/lambda?
I'm not 100% sure what you're trying to achieve, but I would definitely advice to use methods instead of syntax-based query.
var results = items.GroupBy(x => x.name)
.Select((g, i) => new { product = g.Key, index = i });
Or if you'd like to get indexes from source lift for all items within every group:
var results = items.Select((x, i) => new { x, i })
.GroupBy(x => x.x.name)
.Select(g => new {
product = g.Key,
indexes = g.Select(x => x.i).ToList()
});
var idx = 0;
var result = from i in items
group i by i.name into g
select new { product = g.Key,
index = idx++
};

Group By Multiple Columns

How can I do GroupBy multiple columns in LINQ
Something similar to this in SQL:
SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>
How can I convert this to LINQ:
QuantityBreakdown
(
MaterialID int,
ProductID int,
Quantity float
)
INSERT INTO #QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM #Transactions
GROUP BY MaterialID, ProductID
Use an anonymous type.
Eg
group x by new { x.Column1, x.Column2 }
Procedural sample:
.GroupBy(x => new { x.Column1, x.Column2 })
Ok got this as:
var query = (from t in Transactions
group t by new {t.MaterialID, t.ProductID}
into grp
select new
{
grp.Key.MaterialID,
grp.Key.ProductID,
Quantity = grp.Sum(t => t.Quantity)
}).ToList();
For Group By Multiple Columns, Try this instead...
GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new
{
Key1 = key.Column1,
Key2 = key.Column2,
Result = group.ToList()
});
Same way you can add Column3, Column4 etc.
Since C# 7 you can also use value tuples:
group x by (x.Column1, x.Column2)
or
.GroupBy(x => (x.Column1, x.Column2))
C# 7.1 or greater using Tuples and Inferred tuple element names (currently it works only with linq to objects and it is not supported when expression trees are required e.g. someIQueryable.GroupBy(...). Github issue):
// declarative query syntax
var result =
from x in inMemoryTable
group x by (x.Column1, x.Column2) into g
select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));
// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
.Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));
C# 3 or greater using anonymous types:
// declarative query syntax
var result3 =
from x in table
group x by new { x.Column1, x.Column2 } into g
select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };
// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
.Select(g =>
new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });
You can also use a Tuple<> for a strongly-typed grouping.
from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
LastName = grouping.Key.Item1,
FirstName = grouping.Key.Item2,
MiddleName = grouping.Key.Item3,
DayCount = grouping.Count(),
AmountBilled = grouping.Sum(x => x.Rate),
}
Though this question is asking about group by class properties, if you want to group by multiple columns against a ADO object (like a DataTable), you have to assign your "new" items to variables:
EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
.Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
var Dups = ClientProfiles.AsParallel()
.GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
.Where(z => z.Count() > 1)
.Select(z => z);
var Results= query.GroupBy(f => new { /* add members here */ });
A thing to note is that you need to send in an object for Lambda expressions and can't use an instance for a class.
Example:
public class Key
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
This will compile but will generate one key per cycle.
var groupedCycles = cycles.GroupBy(x => new Key
{
Prop1 = x.Column1,
Prop2 = x.Column2
})
If you wan't to name the key properties and then retreive them you can do it like this instead. This will GroupBy correctly and give you the key properties.
var groupedCycles = cycles.GroupBy(x => new
{
Prop1 = x.Column1,
Prop2= x.Column2
})
foreach (var groupedCycle in groupedCycles)
{
var key = new Key();
key.Prop1 = groupedCycle.Key.Prop1;
key.Prop2 = groupedCycle.Key.Prop2;
}
group x by new { x.Col, x.Col}
.GroupBy(x => (x.MaterialID, x.ProductID))
.GroupBy(x => x.Column1 + " " + x.Column2)
For VB and anonymous/lambda:
query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })

Categories