Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Average extension method in Linq for default value

Tags:

Anyone know how I can set a default value for an average? I have a line like this...

dbPlugins = (from p in dbPlugins                 select new { Plugin = p, AvgScore = p.DbVersions.Average(x => x.DbRatings.Average(y => y.Score)) })             .OrderByDescending(x => x.AvgScore)             .Select(x => x.Plugin).ToList(); 

which throws an error becase I have no ratings yet. If I have none I want the average to default to 0. I was thinking this should be an extension method where I could specify what the default value should be.

like image 445
Rush Frisby Avatar asked Mar 29 '11 02:03

Rush Frisby


People also ask

How do you find the average in Linq?

In LINQ, you can find the average of the given sequence by using the Average method. This method returns the average of the elements present in the given sequence or collection. It returns nullable, non-nullable decimal, double, floats, int, etc. values.

What are extension methods in LINQ?

Extension Methods are a new feature in C# 3.0, and they're simply user-made pre-defined functions. An Extension Method enables us to add methods to existing types without creating a new derived type, recompiling, or modifying the original types.


2 Answers

There is: DefaultIfEmpty.

I 'm not sure about what your DbVersions and DbRatings are and which collection exactly has zero items, but this is the idea:

var emptyCollection = new List<int>(); var average = emptyCollection.DefaultIfEmpty(0).Average(); 

Update: (repeating what's said in the comments below to increase visibility)

If you find yourself needing to use DefaultIfEmpty on a collection of class type, remember that you can change the LINQ query to project before aggregating. For example:

class Item {     public int Value { get; set; } }  var list = new List<Item>(); var avg = list.Average(item => item.Value); 

If you don't want to/can not construct a default Item with Value equal to 0, you can project to a collection of ints first and then supply a default:

var avg = list.Select(item => item.Value).DefaultIfEmpty(0).Average(); 
like image 170
Jon Avatar answered Oct 05 '22 23:10

Jon


My advice would to create a reusable solution instead of a solution for this problem only.

Make an extension method AverageOrDefault, similar to FirstOrDefault. See extension methods demystified

public static class MyEnumerableExtensions {     public static double AverageOrDefault(this IEnumerable<int> source)     {         // TODO: decide what to do if source equals null: exception or return default?         if (source.Any())             return source.Average();         else             return default(int);     } } 

There are 9 overloads of Enumerable.Average, so you'll need to create an AverageOrDefault for double, int?, decimal, etc. They all look similar.

Usage:

// Get the average order total or default per customer var averageOrderTotalPerCustomer = myDbContext.Customers     .GroupJoin(myDbContext.Orders,     customer => customer.Id,     order => order.CustomerId,     (customer, ordersOfThisCustomer) => new     {         Id = customer.Id,         Name  = customer.Name,         AverageOrder = ordersOfThisCustomer.AverageOrDefault(),     }); 
like image 40
Harald Coppoolse Avatar answered Oct 05 '22 23:10

Harald Coppoolse