Linq query problem - c#

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) };

Related

MongoDB GroupBy Aggregate and Count Documents C#

I have a requirement to get the count of documents based on status of customer. So I need to use aggregate function and then group by based on status. I have used following code for that but the problem is that in Result I am getting the list of documents but what I just want to have is the status and count of documents under that. Can any body please help in adjusting the query to achieve the results.
var result = collection.Aggregate()
.Group(
x => x.status,
g => new
{
Result = g.Select(x => new CustomerDetailsList
{
ActiveType = x.status,
Count = g.Count()
}
)
}
);
Thanks in advance
The reason you're getting a list of documents for every key is that you're running this nested Select, all you need is:
collection.Aggregate()
.Group(
x => x.status,
g => new CustomerDetailsList
{
ActiveType = g.Key,
Count = g.Count()
}).ToList();
I am satisfied with the answer of #mickl and it works well as I tested according to my requirement but here is the way I opted in my app as this is what I am comfortable with. The method is to use the collection as queryable
var result = collection.AsQueryable()
.GroupBy(x => x.status)
.Select(x => new CustomerDetailsList
{
ActiveType = x.Key, Count = x.Count()
}).ToList();
I have used more LINQ in this way so I choose this as it's better to understand for me.
You can choose any of the methods either this or as demonstrated by #mickl

How to filter a sublist inside parent and return parent with sublist filtered

I want to create a linq to sql query that will return a list of objects with a sublist that has been filtered.
It sounds easy but I'm not sure how to make this to work
Here the SQL Query which returns what I want:
select * from Texts t inner join Translations tt on t.TranslationId = tt.Id
inner join Pages p on tt.Id = p.TranslationId and tt.NeutralText = p.TitleNeutralTextId
where t.LanguageId = 1
Now I have to write this with linq.
What I've done so far is:
var query = this.Queryable() // Page entity
.AsNoTracking()
.Include(x => x.TitleTranslation.Texts);
return (from m in query
from l in m.TitleTranslation.Texts
where m.TitleTranslation.Texts.Any(l => l.LanguageId == 1)
select m);
But it didn't work because I got the sublist with all languages instead of language with id #1 only.
Thanks for helping,
David
Any specific reason you are writing query? Either you can use Eager Loading of EF to load all the child tables, Or below Linq statement can fetch the required result
var result = texts.Join(translations, t => t.TranslationId, tt => tt.Id, (t, tt) => new {t, tt})
.Join(pages, ttt => new { Id = ttt.tt.Id, NeutralTextId = ttt.tt.NeutralText }, p => new { Id = p.TranslationId, NeutralTextId = p.TitleNeutralTextId }, (ttt, p) => new {ttt, p})
.Where(tttt => tttt.ttt.t.LanguageId == 1);
Here replace texts, translations and pages with actual dbContext entities collection property.
I think you must try lime this. this will work for you .
This will be similar to sql query
One way to do this .
var result = from m in Texts
join Translations on Texts.TranslationId = Translation.Id
Join Pages on Translations.NeutralText = Pages.NeutralText
where Texts.LanguageId = 1
select m
There an other way to do this using entity framework
var result =
this.Queryable().AsNoTracking().Include(x=>x.Translations).Where(x=>x.LanguageId= 1)
I found the solution I wanted thanks to Hasnain Bukhari.
The solution was to start from the text table, assign the filter, include the desired Entity (Page) and put the results into memory (ToList()). Then select pages. It will give the result I want in the order I have to.
var query = textService.Queryable()
.AsNoTracking()
.Include(x => x.Translation.Pages)
.Where(x => x.LanguageId == languageId).ToList();
return query.SelectMany(x => x.Translation.Pages);

LINQ performance group by object vs group by id

I have a group by statement like so:
group s by c into g
select new ObjectDto
{
KeyObject = new KeyObjectDto
{
ObjectId = g.Key.Id
},
GroupedObjects = g.Select(a => a)
};
If I modify this ObjectDto to have an IEnumerable KeyObjects and modify the query like below I am seeing 2x the performance improvement:
group s by c.Id into g
select new ObjectDto
{
KeyObjects = KeyObjectsDbSet.Where(x => x.Id == g.Key).Select(x => new KeyObjectDto
{
ObjectId = x.Id
}),
GroupedObjects = g.Select(a => a)
};
If I look at the generated SQL from the first statement it looks like grouping by c uses more than just the Id to compare if two rows are unique or not (despite the fact that EF is aware of the Id being the primary key), but I wasn't sure that's all that's at play here. I also noticed that if instead try:
KeyObject = KeyObjectsDbSet.Where(x => x.Id == g.Key).Select(x => new KeyObjectDto
{
ObjectId = x.Id
}).FirstOrDefault()
that I immediately lose any performance benefits.
So my questions are:
(1) Why is the performance of the second better than the first statement (is it just because of the row/object comparison)?
(2) Is it possible to group by object, but tell LINQ to use a specific property (set of properties) on which to do that grouping? For instance: group s by c using c.Id into g

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);

Remove records from 1 query based on same values found in another query

So I have 2 IQueryable<Array> queries with a common string type; I want to use the second query to remove all records using the common type in the first, leaving only the records not found as the result of the first query. Not quite sure how to do it.
The most understandable way to do it is to filter out the items in ListA using Where():
IQueryable<LISTA> as = db.ListA.Where(x => x.Active);
IQueryable<LISTB> bs = db.ListB.Where(x => x.HadReview);
as = as.Where(a => bs.Any(b => a.LogginID == b.LogginID) == false);
A way that might be more efficient is to use a Group Join:
var as = from a in db.ListA.Where(x => x.Active)
join b in db.ListB.Where(x => x.HadReview)
on a.LogginID equals b.LogginID into bs
where bs.Any() == false
select a;
Edit: If you have two different DataContexts the above will not work. This might:
IQueryable<LISTA> as = db1.ListA.Where(x => x.Active);
IQueryable<LISTB> bs = db2.ListB.Where(x => x.HadReview);
as = as.Where(a => bs.Select(b => b.LogginID).Contains(a.LogginID) == false);
See also: Linq to SQL - How to inner join tables from different Data Context?

Categories

Resources