Filtering query based on condition - c#

I am trying to filter the results on my query based on whether or not they contain the string "baby".
IEnumerable<ICD.ViewModels.HomeSearchViewModel> query =
ICDUnitOfWork.AlphaGroups.Find().GroupJoin(ICDUnitOfWork.Alphas.Find(),
a => a.AlphaGroupID,
g => g.AlphaGroupID,
(alphaGroups, alphas) =>
new ICD.ViewModels.
HomeSearchViewModel
{
AlphaGroups =
alphaGroups,
Alphas = alphas
})
.Where(row =>
row.AlphaGroups.Title.Contains("baby")
|| row.Alphas.Any(alpha => alpha.Title.Contains("baby"))
);
The problem is that when an Alpha.Title contains the string "baby" it should only show the Alpha's that contain "baby", rather than every alpha in the AlphaGroup. If the AlphaGroup.Title contains "baby" it should continue to show each alpha in the group. How can I accomplish this?

You could try something like the following:
IEnumerable<ICD.ViewModels.HomeSearchViewModel> query =
ICDUnitOfWork.AlphaGroups.Find()
.GroupJoin(
ICDUnitOfWork.Alphas.Find()
.GroupBy(a => new
{
BabyIndicator = a.Title.Contains("baby"),
GroupID = a.AlphaGroupID
}),
a => a.AlphaGroupID,
g => g.Key.GroupID,
(alphaGroups, alphas) =>
new ICD.ViewModels.HomeSearchViewModel()
{
AlphaGroups = alphaGroups,
Alphas = alphaGroups.Title.Contains("baby") ?
alphas.Select(g => g.AsEnumerable()).Aggregate((g1,g2) => g1.Concat(g2)) :
alphas.Aggregate(
(g1,g2) => g1.Key.BabyIndicator ?
g1 :
g2).AsEnumerable()
})
Logic:
The problem is that when an Alpha.Title contains the string "baby" it
should only show the Alpha's that contain "baby", rather than every
alpha in the AlphaGroup.
Here we group the alphas by groupID and whether they have babies, we then group join this onto the alphGroups. So we have four possibilities, no groups, one group without baby, one group with only babies and one of each. To pull this all together we aggregate. If there are no groups it returns no groups, if there is one group it returns that group, if there are two groups it returns only the one with babies.
If the AlphaGroup.Title contains "baby" it should continue to show
each alpha in the group.
Here we check whether an alphaGroup has a title baby, if it does return the whole grouping, if not apply alpha title logic

Related

Linq OrderBy then GroupBy - group by unexpectedly changes order of list

When I run this expression i can see that the list order is correctly in sequence by the highest ActionId's
var list = db.Actions.Where(z => z.RunId
== RunId).OrderByDescending(w =>
w.ActionId).ToList();
I only want to select the highest ActionId's of each ActionName so I now do:
var list = db.Actions.Where(z => z.RunId
== RunId).OrderByDescending(w =>
w.ActionId).GroupBy(c => new
{
c.ActionName,
c.MachineNumber,
})
.Select(y =>
y.FirstOrDefault()).ToList();
When I look at the contents of list, it hasn't selected the ActionName/MachineNumber with the highest ActionId, which I assumed would be the case by ordering then using FirstOrDefault().
Any idea where I'm going wrong? I want to group the records by the ActionName and MachineId, and then pick the record with the highest ActionId for each group
Instead of grouping an ordered collection, group the collection first, and then select the record with the highest ID for each of the groups. GroupBy is not guaranteed to preserve the order in each group in LINQ to SQL - it depends on your database server.
var list = db.Actions.Where(z => z.RunId == RunId).GroupBy(c => new
{
c.ActionName,
c.MachineNumber,
})
.Select(y => y.OrderByDescending(z => z.ActionId).FirstOrDefault()).ToList();

Conditional GroupBy() in LINQ

I'm working with a matrix filled with similarities between items. I save these as a list of objects in my database. The Similarity object looks like this:
public class Similarity
{
public virtual Guid MatrixId { get; set; } //The id of the matrix the similarity is in
public virtual Guid FirstIndex { get; set; } //The id of the item of the left side of the matrix
public virtual Guid SecondIndex { get; set; } //The id of the item of the top side of the matrix
public virtual double Similarity { get; set; } //The similarity
}
A user can review these items. I want to retrieve a list of items which are 'similar' to the items the user has reviewed. The problem is where I can't tell for sure whether the item's id is in the FirstIndex or the SecondIndex. I have written some code which does what I want, but I want to know if this is possible in 1 statement.
var itemsNotReviewed = Similarities.Where(x => !itemsReviewed.Contains(x.SecondIndex))
.GroupBy(x => x.SecondIndex)
.ToList();
itemsNotReviewed.AddRange(Similarities.Where(x => !itemsReviewed.Contains(x.FirstIndex))
.GroupBy(x => x.FirstIndex)
.ToList());
Where itemsReviewed is a list of guids of the items the user has reviewed and where Similarities is a list of all items which are similar to the items the user has reviewed. I retrieve that list with this function:
return (from Row in _context.SimilarityMatrix
where itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex)
select Row)
.Distinct()
.ToList();
where itemIds is a list of guids of the items the user has reviewed.
Is there a way to group by either the first or second index based on the Where clause?
Please let me know if I should elaborate!
By my understanding, you have a list of Similarity which is guaranteed to contain items with either FirstIndex or SecondIndex contained in itemsReviewed list of Guid. And you need to take the elements (if any) with either index not contained in itemsReviewed (it could be only one of them due to the first constraint) and group by that index.
The straightforward LINQ translation of the above would be like this:
var itemsNotReviewed = Similarities
.Where(item => !itemsReviewed.Contains(item.FirstIndex) || !itemsReviewed.Contains(item.SecondIndex))
.GroupBy(item => !itemsReviewed.Contains(item.FirstIndex) ? item.FirstIndex : item.SecondIndex)
.ToList();
But it contains duplicate itemsReviewed.Contains checks, which affect negatively the performance.
So a better variant would be introducing intermediate variable, and the easiest way to do that is query syntax and let clause:
var itemsNotReviewed =
(from item in Similarities
let index = !itemsReviewed.Contains(item.FirstIndex) ? 1 :
!itemsReviewed.Contains(item.SecondIndex) ? 2 : 0
where index != 0
group item by index == 1 ? item.FirstIndex : item.SecondIndex)
.ToList();
I would go for changing the way you source the original list:
_context.SimilarityMatrix.Where(Row => itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex))
.Select(r => new { r.MatrixId, r.FirstIndex, r.SecondIndex, r.Similarity, MatchingIndex = itemIds.Contains(r.FirstIndex) ? r.FirstIndex : r.SecondIndex })
.Distinct()
.ToList();
This way you only need to group by Matching Index.
var itemsNotReviewed = Similarities.
.GroupBy(x => x.MatchingIndex)
.ToList();
You may want to convert after the dynamic object to your Similarity class or just change the class to include the Matching Index.
You can convert them to your Similarity type by:
var itemsNotReviewed = Similarities.
.GroupBy(x => x.MatchingIndex)
.Select(g => new { g.Key, Values = g.Values.Select(d => new Similarity { MatrixId = d.MatrixId, FirstIndex = d.FirstIndex, SecondIndex = d.SecondIndex, Similarity = d.Similarity }).ToList() })
.ToList();
What about
(from x in Similarities
let b2 = !itemsReviewed.Contains(x.SecondIndex)
let b1 = !itemsReviewed.Contains(x.FirstIndex)
where b1 || b2
groupby b2 ? x.SecondIndex : x.FirstIndex into grp
select grp)
.ToList()
The let statement introduces a new tempoary variable storing the boolean. You can of course inline the other function, too:
(from x in (from Row in _context.SimilarityMatrix
where itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex)
select Row)
.Distinct()
.ToList()
let b2 = !itemsReviewed.Contains(x.SecondIndex)
let b1 = !itemsReviewed.Contains(x.FirstIndex)
where b1 || b2
groupby b2 ? x.SecondIndex : x.FirstIndex into group
select group)
.ToList()
If you wanted to use non-LINQ syntax, you would probably need to introduce some anonymous types:
Similarities
.Select(s => new
{
b2 = !itemsReviewed.Contains(x.SecondIndex),
b1 = !itemsReviewed.Contains(x.FirstIndex),
s
})
.Where(a => a.b1 || a.b2)
.GroupBy(a => a.b2 ? a.s.SecondIndex : a.s.FirstIndex, a => a.x) //edit: to get same semantics, you of course also need the element selector
.ToList()

How to use groupby in linq sql

I am trying to group a list and using ToDictionary to achieve this which works:
var levels = ids.GroupBy(f => f.Id).
ToDictionary(g => g.Key, g => g.First().Name);
The problem is: in the string "Name" the last char is a number i.e. 2 or 5 or 7 etc.
I do NOT want to select the first but I want to select "Name" with the MAX number. How can i achieve this. example of Name can be: "abd-hbb-les3" , "abd-hbb-les1" , "abd-hbb-les6"
You could do this in the following way:
var levels = ids.GroupBy(f => f.Id).ToDictionary(g => g.Key,
g => g.First( x=> x.Name.Last() == g.Max( y=> y.Name.Last())).Name);
assuming that it's really about the last letter so it's not possible to have a two (or more) digits at the end e.g.:
abd-hbb-les16 //will not work with the above code
For every group simply select the name with the maximum last character of the string. Like this:
var levels = ids.
GroupBy(f => f.Id).
ToDictionary(
g => g.Key,
g => g.First(i => i.Name.Last() == g.Max(j => j.Name.Last())).Name);

LINQ: how to get a group of a table ordering with a related table?

I have a doubt about the object IGrouping that results from a linq where I use a "group by" sentence.
I have two tables in the database, Products and Responses they have a relationship 1 to *. In the Responses table we have a column called FinalRate which is the rate of the product. The products can have n responses or rates.
I want to get the Products order by the sum of the FinalRate divided by the number of rates done. That is to say, order by the average rate descending from higher to lower marks.
As it can be read in the code (at the end of the question), I try to get the responses first. To sum all the finalrates and divide them by the count I use a group.
There are 2 problems with the code, even if the current code works:
1.-I tried to get the Products in a single query but it is impossible because I can not use the products table in the group and then use the Response table in the "orderby". One more thing LINQ only gives you the possibility to group one table, it is imposible to have "group prod, response".
I couldn't get this sql sentence in LINQ:
select prod.ProductID,prod.Commercial_Product_Name,prod.Manufacturer_Name,
prod.ProductImageUrl
from rev_product prod
inner join rev_response res on res.AtProductid=prod.ProductID
group by prod.ProductID,prod.Commercial_Product_Name,prod.Manufacturer_Name
,prod.ProductImageUrl
order by (sum(res.FinalRate)/count(res.AtProductid))
I tried this:
var gruposproductos = (from prod in ctx.Products
join res in ctx.Responses on prod.ProductID equals res.AtProductId
group prod by prod.ProductID into g
orderby (g.Sum(ra =>ra.FinalRate)/g.Count())
descending select g).Take(2);
But as I say, the "orderby (g.Sum..." gives an error, because "into g" groups the Product table, not the Response Table.
So this is why in my final code I don't get the products in the same LINQ sentence.
2.-Once accepted this fact, the problem is that I get an IGrouping, but I don't obtain a list of Responses that I can iterate without doing the two foreach in the code. I wanted only one loop, as one would do if you had a "List" object.
It is not really a cool method but it works. Moreover, I have to control that in the second loop there is only added 1 time.
Any better code?
var groupproducts = (from res in ctx.Responses
group res by res.AtProductId into g
orderby (g.Sum(ra =>ra.FinalRate)/g.Count())
descending select g).Take(2).ToList();
List<Product> theproducts = new List<Product>();
foreach (var groupresponse in groupproducts)
{
foreach (var response in groupresponse)
{
var producttemp= (from prod in ctx.Products
where prod.ProductID == response.AtProductId
select prod).First();
theproducts.Add(producttemp);
}
}
}
FINAL SOLUTION (thx a lot #Daniel)
var productsanonymtype = ctx.Products.Select(x => new
{
Product = x,
AverageRating = x.Responses.Count() == 0 ? 0 : x.Responses.Select(r => (double)r.FinalRate).Sum() / x.Responses.Count()
}).OrderByDescending(x => x.AverageRating);
List<Product> products = new List<Product>();
foreach (var prod in productsanonymtype)
{
products.Add(prod.Product);
}
Try this:
products.Select(x => new
{
Product = x,
AverageRating = x.Responses.Sum(x => x.FinalRate) /
x.Responses.Count()
});
The Sum overload I am using is not implemented in all providers. If that's a problem for you, you can use this alternate version:
products.Select(x => new
{
Product = x,
AverageRating = x.Responses.Select(x => x.FinalRate)
.Sum() /
x.Responses.Count()
});
If there is no navigation property from product to its responses you should first try to fix that. If you can't you can use this version:
products.Join(responses, x => x.Id, x => x.ProductId,
(p, r) => new { Product = p, Response = r })
.GroupBy(x => x.Product)
.Select(g => new { Product = g.Key,
AverageRating = g.Select(x => x.Response.FinalRate)
.Sum() /
g.Count()
});
Assuming FinalRate is an int, both methods will calculate the average rating with an int, i.e. there will be no 4.5 rating. And there will be no rounding, i.e. an actual average rating of 4.9 will result in 4. You can fix that by casting one of the operands of the division to double.
Another problem is the case with no ratings so far. The code above will result in an exception in this case. If that's a problem for you, you can change the calculation to this:
AverageRating = g.Count() == 0
? 0
: g.Select(x => (double)x.Response.FinalRate).Sum() / g.Count()
ctx.Products.GroupBy(x => new {
ProductId = x.ProductId,
FinalRate = x.Responses.Sum(y => y.FinalRate),
CountProductId = x.Responses.Count
})
.OrderBy(x => x.Key.FinalRate / x.Key.CountProductId);
And here with the projection.....
ctx.Products.Select(x => new {
ProductID = x.ProductID,
Commercial_Product_Name = x.Commercial_Product_Name,
Manufacturer_Name = x.Manufacturer_Name,
ProductImageUrl = x.ProductImageUrl,
FinalRate = x.Responses.Sum(y => y.FinalRate),
CountProductId = x.Responses.Count
})
.GroupBy(x => new {
ProductId = x.ProductId,
FinalRate = x.FinalRate,
CountProductId = x.CountProductId
})
.OrderBy(x => x.Key.FinalRate / x.Key.CountProductId);

Linq query problem

I have the following information
var details = Details.Where(d => d.isActive);
I would like to query another table, Authorizations, that has a FK to Details, and get the sum amounts of the authorizations that are contained within the details object grouped by the detail and a FundCode.
Details (1 to many) Authorizations
Seems simple enough, however I'm having a bit of trouble.
Here is what I currently have:
var account = (from sumOfAuths in Authorizations
where details.Contains(sumOfAuths.Details)
&& sumOfAuths.RequestStatusId == 2
group sumOfAuths by new { sumOfAuths.Detail, sumOfAuths.FundCode } into child
select new {
....
Amount = child.Amount
}).Sum()
The problem is that inside the .Sum() function I have collection of objects rather than 1. So I can't Sum the amounts properly.
Generally, you can specify what it is you want to sum:
.Sum(x => x.Amount)
In groups, you can use nested sums:
.Sum(x => x.Sum(y => y.Amount));
I believe this query produces what you're looking for:
var account = from c in Authorizations
where details.Contains(c.Details) && c.RequestStatusId == 2
group c by new { c.Detail, c.FundCode } into g
select new { Key = g.Key, Sum = g.Sum(x => x.Amount) };

Categories

Resources