Pergunta

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);
                }
Foi útil?

Solução

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

Outras dicas

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);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top