I have used two LINQ queries to manipulate a datatable and return the data that I want. Effectively, the table has the following columns:
Group Animal Numbers
There can be multiple Animals within a single group, and multiple numbers assigned to a single animal. Initially I want to calculate the range of the numbers for each specific animal, and then calculate an average range for each group. With the output being the group number and then the average range.
I have the following two LINQ queries, that do work, however my question is, can this be merged into a single query, or , as they are distinct operations, should they be kept seperate for readability and testability?
//Calculate range/delta for each number per animal
var query = from data in Data.AsEnumerable()
group data by new { animal = data.Field<string>("animal"), gp = data.Field<string>("group") }
into g
orderby g.Key.gp
select new
{
animal = g.Key.animal,
gp = g.Key.gp,
delta = g.Max(c => Convert.ToDouble(c.Field<string>("numbers")))
- g.Min(c => Convert.ToDouble(c.Field<string>("numbers")))
};
//calculate average range/delta per group
var temp = from q in query
group q by q.gp
into g
select new
{
gp = g.Key,
average = g.Average(c => c.delta)
};
Thanks.
Because you aren't forcing the first query to evaluate with a method like ToArray(), there's no advantage to combining these queries. I would leave them separate for readability.
You could first group the data by group and then, in a nested query, by animal to calculate the average of the deltas:
var result = from data in Data.AsEnumerable()
group data by data.Field<string>("group") into g
select new
{
gp = g.Key,
average = (
from item in g
group item by data.Field<string>("animal") into f
select f.Max(c => double.Parse(c.Field<string>("numbers")))
- f.Min(c => double.Parse(c.Field<string>("numbers")))
).Average()
};
Related
I am trying to figure out how to pick the last two "Transactions" from my query.
My Query looks like this
var summary= (from tType in _context.CommandCentre.TransactionTypes
join tSummary in _context.CommandCentre.TransSummary on tType.Id equals tSummary.TransactionType
where tSummary.ChargeFlag.ToLower() == ChargeFlag.Overcharge.ToString().ToLower()
group tSummary by new { tType.Name, tSummary.NumberOfTransactions, tSummary.PeriodDate }
into gResult
select new
{
Fee = gResult.Key.Name,
TransactionCount = gResult.Key.NumberOfTransactions,
Period = gResult.Key.PeriodDate,
ActualAmount = gResult.Sum(x => x.ActualAmount),
}).OrderByDescending(x=>x.Period);
Now if I do a Take(2) I get only the last two records, while I want to get the last two records for every "Fee" of my selection. Basically Two records for every "Fee" ordered by "Period" date.
not sure how to do this in a single query.
Try this :
var result = summary.GroupBy(p => p.Fee)
.SelectMany(d => d.OrderBy(r => r.Period).Take(2))
.ToList();
I've been looking at other threads here to learn how to do a GroupBy in linq. I am following the EXACT syntax that has worked for others, but, it's not working.
Here's the query:
var results = from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.ContactID, // <--- Error here
Amount = amount
};
pending is a List<T> that contains, among other columns, ContactID and Amount, but those are the only two I care about for this query.
The trouble is, inside the the select new, the g. won't give me any of the columns inside the original list, pending. And when I try, I get:
IGrouping <int, LeadPurchases> does not contain a definition for ContactID, and no extension method blah blah blah...
This is the SQL I am trying to emulate:
SELECT
lp.PurchasedFromContactID,
SUM (lp.Amount)
FROM
LeadPurchases lp
GROUP BY
lp.PurchasedFromContactID
You are grouping on the basis of ContactID, so it should be the Key for the result, So you have to use g.Key instead of g.ContactID; Which means the query should be like the following:
from p in pending
group p by p.ContactID into g
let amount = g.Sum(s => s.Amount)
select new PaymentItemModel
{
ContactID = g.Key,
Amount = amount
};
updates :
If you want to perform grouping based on more than one column then the GroupBy clause will be like this:
group p by new
{
p.ContactID,
p.Field2,
p.Field3
}into g
select new PaymentItemModel()
{
ContactID = g.Key.ContactID,
anotherField = g.Key.Field2,
nextField = g.Key.Field3
};
for reporting purposes i wanna split a list of purchase orders into multiple lists. One list for each purchase address. I know it's possible to group the list by purchase address, but my question is how to split the list by this purchase address into multiple lists and use these multiple list to create individual reporting files.
code:
(from o in orders
group o by new {field1, field2, field3, field4} into og
orderby og.Key.field1
select new ViewClass
{
purchaseAddress = og.Key.field1,
product = og.key.field2,
count = og.count
}).ToList()
question: how to split above list into multiple lists for each purchaseAddress?
There's a built-in function that I think does what you want. If I assume that your code is assigned to a variable called query then you can write this:
ILookup<string, ViewClass> queryLists = query.ToLookup(x => x.purchaseAddress);
This essentially creates a list of lists that you access like a dictionary, except that each value is a list of zero or more values. Something like:
IEnumerable<ViewClass> someList = queryLists["Some Address"];
Just turn each group into a List.
select new ViewClass
{
purchaseAddress = og.Key.field1,
product = og.key.field2,
count = og.count,
List = og.ToList()
}).ToList();
Oh, your grouping is one way for entities and another way for pages... just regroup.
List<ViewClass> classes = (
from o in orders
group o by new {field1, field2, field3, field4} into og
orderby og.Key.field1
select new ViewClass
{
purchaseAddress = og.Key.field1,
product = og.key.field2,
count = og.count
}).ToList();
List<List<ViewClass>> regrouped = (
from c in classes
group c by c.purchaseAddress into g
select g.ToList()
).ToList();
Another simple built-in function that you can use is the GroupBy function. It does a similar job as the ToLookup but it means that your new list is IQuerable, not like a dictionary and a few other things (see this article for a good breakdown)
var newList = orders.GroupBy(x => x.field1);
This will return a list of lists grouped by the field(s) you specify.
I need a way to reduce a list, or calculate a "Total." I have a class, lets call it Prod. Prod contains 4 values. One is the name of the product, the id, a serial number, and a quantity. Basically I have one product but 2 different serial numbers. So when I get my data back from my query I have 2 items which I want to treat as a single item. How can I go about using LINQ or something else (I cannot foreach over them. There are many more class members and that would take a while plus look terrible). I want to be able to take the 2 instances and combine their serial numbers (not add just Serail1 - Serial 2) and also calculate the quantities together.
I think what you want is the Linq grouping function (see GroupBy - Simple 3). This should give you a list of serial numbers and their quantity count:
public void Linq42()
{
List<Prod> products = GetProductList();
var serialCombined =
from p in products
group p by p.SerialNumber into g
select new { SerialNumber = g.Key, Total = g.Count() };
}
Use the join operator and place them in a Tuple. You can then call more LINQ on the tuples or iterate over them.
var combinedProducts =
from product1 in products1
join product2 in products2 on product1.serial equals product2.serial
select Tuple.Create(product1, product2);
// Use LINQ to calculate a total
combinedProducts.Sum(combined => combined.Item1.count * combined.Item2.price);
// Or use foreach to iterate over the tuples
foreach (var combined in combinedProducts) {
Console.WriteLine("{0} and {1}", combined.Item1.name, combined.Item2.name);
}
I have some data in a List of User defined types that contains the following data:
name, study, group, result, date. Now I want to obtain the name, study and group and then a calculation based onthe result and date. The calculation is effectively:
log(result).where max(date) minus log(result).where min(date)
There are only two dates for each name/study/group, so the result from the maximum data (log) minus the result from the minumum date (log). here is what I have tried so far with no luck:
var result =
from results in sortedData.AsEnumerable()
group results by results.animal
into grp
select new
{
animal = results.animal,
study = results.study,
groupNumber = results.groupNumber,
TGI = System.Math.Log(grp.Select(c => c.volume)
.Where(grp.Max(c=>c.operationDate)))
- System.Math.Log(grp.Select(c => c.volume)
.Where(grp.Min(c => c.operationDate)))
};
Anybody any pointers? Thanks.
It isn't entirely clear how the grouping relates to your problem (what sense does it make to extract a property from a range variable after it has been grouped?), but the part you're having difficult with can be solved easily with MaxBy and MinBy operators, such as the ones that come with morelinq.
var result = from results in sortedData.AsEnumerable()
group results by results.animal into grp
select new
{
animal = grp.Key,
study = ??,
groupNumber = ??,
TGI = Math.Log(grp.MaxBy(c => c.operationDate).volume)
- Math.Log(grp.MinBy(c => c.operationDate).volume)
};
Otherwise, you can simulate these operators with Aggregate, or if you don't mind the inefficiency of sorting:
var result = from results in sortedData.AsEnumerable()
group results by results.animal into grp
let sortedGrp = grp.OrderBy(c => c.operationDate)
.ToList()
select new
{
animal = grp.Key,
study = ??,
groupNumber = ??,
TGI = sortedGrp.Last().volume - sortedGrp.First().volume
};
You have a few syntax problems, you cannot use the results parameter after your into grp line. So my initial attempt would be to change your statement like so
var result =
from results in sortedData.AsEnumerable()
group results by new
{
Animal = results.animal,
Study = results.study,
GroupNumber = results.groupNumber
}
into grp
select new
{
animal = grp.Key.Animal,
study = grp.Key.Study,
groupNumber = grp.Key.GroupNumber,
TGI = System.Math.Log(grp.OrderByDescending(c=>c.operationDate).First().volume)
- System.Math.Log(grp.OrderBy(c=>c.operationDate).First().volume)
};