225

I have this query:

int maxShoeSize = Workers
    .Where(x => x.CompanyId == 8)
    .Max(x => x.ShoeSize);

What will be in maxShoeSize if company 8 has no workers at all?

UPDATE:
How can I change the query in order to get 0 and not an exception?

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Naor
  • 23,465
  • 48
  • 152
  • 268
  • Naor : have you heard of LINQPad? – Mitch Wheat Aug 06 '11 at 12:16
  • 3
    I don't understand why you would ask 'What will be in `maxShoeSize`?' if you had already tried it out. – jwg May 16 '13 at 07:54
  • @jwg: I guess I wanted to see if you know the answer :) Eventually I got a better way to do what I asked and this is what I meant. – Naor May 19 '13 at 15:39
  • @Naor, this isnt a guessing game. I also would downvote the original question. If you know the answer give it to us otherwise you look lazy. Just now I was about to do the same question and I prepare all info including the exception message. – Juan Carlos Oropeza Oct 11 '16 at 15:01

11 Answers11

393
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                         .Select(x => x.ShoeSize)
                         .DefaultIfEmpty(0)
                         .Max();

The zero in DefaultIfEmpty is not necessary.

Arve Systad
  • 5,471
  • 1
  • 32
  • 58
Ron K.
  • 3,970
  • 2
  • 15
  • 2
  • Works :) But on my code the zero in DefaultIfEmpty was necessary. – Carlos Tenorio Pérez Jul 12 '18 at 07:51
  • 2
    Regarding the first part of the question, which is not fully answered here: According to [the official documentation](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.max?view=netframework-4.7.2#System_Linq_Enumerable_Max__1_System_Collections_Generic_IEnumerable___0__), if the generic type of the _empty_ sequence is a reference type, you get null. Otherwise, according to [this question](https://stackoverflow.com/questions/12414674/linq-max-extension-method-gives-an-error-on-empty-collections/), calling `Max()` on an empty sequence results in an error. – Raimund Krämer Jan 29 '19 at 12:24
  • 3
    This **won't work for EF Core 3.1 and later** if the type is not nullable. Reason being there is a left join for all entries which will fail for non nullable types. Use `Workers.Where(...).Max(x => (int?)x) ?? 0;`. This generates efficient max query directly in sql. – Alby Jan 30 '21 at 21:58
  • If this a query to the database with EF, will this not bring all the lines to calculate in memory the max value? – Daniel Lobo Oct 29 '21 at 13:16
  • @DanielLobo It certainly appears to have done so for me... While Alby's solution generates a simple `Select Max(col) from table WHERE ...` – Auspex May 25 '23 at 12:43
82

I know this is an old question and the accepted answer works, but this question answered my question about whether such an empty set would result in an exception or a default(int) result.

The accepted answer however, while it does work, isn't the ideal solution IMHO, which isn't given here. Thus I am providing it in my own answer for the benefit of anyone who is looking for it.

The OP's original code was:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);

This is how I would write it to prevent exceptions and provide a default result:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;

This causes the return type of the Max function to be int?, which allows the null result and then the ?? replaces the null result with 0.


EDIT
Just to clarify something from the comments, Entity Framework doesn't currently support the as keyword, so the way to write it when working with EF would be:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;

Since the [TypeOfWorkers] could be a long class name and is tedious to write, I've added an extension method to help out.

public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
    return source.Max(selector) ?? nullValue;
}

This only handles int, but the same could be done for long, double, or any other value type you need. Using this extension method is very simple, you just pass in your selector function and optionally include a value to be used for null, which defaults to 0. So the above could be rewritten like so:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);

Hopefully that helps people out even more.

CptRobby
  • 1,441
  • 15
  • 19
  • 6
    Great solution! The more popular `DefaultIfEmpty` answer only works well when `Max` isn't doing an evaluation. – McGuireV10 Nov 21 '16 at 23:17
  • @McGuireV10 Yeah, I don't typically like using `Select` as a middle man when I'm just going to be using an aggregate function like `Max` on the result. I also _think_ (I haven't tested this yet) that the generated SQL would use an extra subselect query by doing that, while mine would just deal with an empty set by returning null. Thanks for the upvote and feedback! ;) – CptRobby Nov 22 '16 at 14:34
  • @McGuireV10 Similarly, if the `ShoeSize` was actually in a related `Uniform` entity, I would not use `Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize)`, instead I would just keep the entire evaluation in the `Max` function: `Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize)`. I prefer to use as few methods as possible in my queries to allow EF to have the greatest freedom in deciding how to efficiently construct queries. ;-) – CptRobby Nov 22 '16 at 14:54
  • Looking back over my last comment, I see that I forgot to add the `as int?) ?? 0` portion from my answer. Additionally, in my preferred method, care would have to be taken that it's only used when querying the database and not local objects if `Uniform` could be null. Or if you're using C#6, you could just change it to `.Max(x => x.Uniform?.ShoeSize) ?? 0` and that would both handle a null `Uniform` and it would change the datatype to `int?`. C#6 is awesome! ;-) – CptRobby Nov 22 '16 at 15:09
  • 1
    I couldn't get this to work in EntityFramework. Can anyone shed any light? (Using the DefaultIfEmpty technique worked). – Moe Sisko Jun 28 '18 at 00:36
  • @MoeSisko What do you mean you couldn't get it to work? What did you try and what was the result? – CptRobby Jun 28 '18 at 02:33
  • In EF: int max = context.Students.Where(s => s.StudentName == "blah").Max(s => s.StudentID as int?) ?? 0; gives error: "The 'TypeAs' expression with an input of type 'System.Int32' and a check of type 'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' is not supported. Only entity types and complex types are supported in LINQ to Entities queries." whereas: int max = context.Students.Where(s => s.StudentName == "blah").Select(s => s.StudentID).DefaultIfEmpty().Max(); returns 0. May be an EF issue. – Moe Sisko Jun 28 '18 at 02:48
  • Interesting. I actually happened to see a similar error just a few hours ago. Don't know if it's something new or what. But it's definitely specific to EF. – CptRobby Jun 28 '18 at 03:31
  • @cptRobby - It seems if you call "ToList()" after "Where", then it works in EF. e.g. this returns 0: int max = context.Students.Where(s => s.StudentName == "blah").ToList().Max(s => s.StudentID as int?) ?? 0; – Moe Sisko Jun 28 '18 at 05:13
  • That is to be expected, since the `Max` is no longer being run on the database through EF. But that does mean that EF is returning all of the records from the database that match the where clause, which might be fine if you only expect a few records to match the where clause. But I think it would be more efficient to use `AsEnumerable` instead of `ToList` because `ToList` will actually create a `List` in memory and iterate through the records to populate it, while `AsEnumerable` just causes the database query to run and provides the results. – CptRobby Jun 28 '18 at 13:15
  • 2
    A generic version of the extension method:`public static TResult MaxOrDefault(this IQueryable items, Expression> selector, TResult defaultValue = default) where TResult : struct => items.Select(selector).Max(item => (TResult?)item) ?? defaultValue;` – relatively_random Dec 31 '18 at 10:37
  • 1
    Async version: `public static async Task MaxOrDefaultAsync(this IQueryable source, Expression> selector, CancellationToken cancellationToken) => await source.MaxAsync(selector, cancellationToken) ?? 0;` – Th3B0Y Jun 09 '23 at 12:51
  • @Th3B0Y Yup, async has become far more significant to how I use EF (and most other things) since I first wrote this answer, so it's nice to include that. ;-) – CptRobby Jun 10 '23 at 07:30
37

Max() won't return anything in that case.

It will raise InvalidOperationException since the source contains no elements.

Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
  • 4
    It will only raise `InvalidOperationException` if the objects in the list are a non-nullable type: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.max?view=netcore-3.1 – Dominus.Vobiscum Jun 12 '20 at 18:00
  • You are talking about the worin `Max` method. The question is about a query so the discussed method is [`Queryable.Max`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.queryable.max?view=netcore-3.1). It may be similar but for example the documentation does not state that it throws an `InvalidOperationException ` – Ackdari Aug 13 '20 at 09:37
19
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                     .Select(x => x.ShoeSize)
                     .DefaultIfEmpty()
                     .Max();
Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
13

If this is Linq to SQL, I don't like to use Any() because it results in multiple queries to SQL server.

If ShoeSize is not a nullable field, then using just the .Max(..) ?? 0 will not work but the following will:

int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;

It absolutely does not change the emitted SQL, but it does return 0 if the sequence is empty because it changes the Max() to return an int? instead of an int.

d219
  • 2,707
  • 5
  • 31
  • 36
abkonsta
  • 461
  • 6
  • 7
5
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
    .Max(x=>(int?)x.ShoeSize).GetValueOrDefault();

(assuming that ShoeSize is of type int)

If Workers is a DbSet or ObjectSet from Entity Framework your initial query would throw an InvalidOperationException, but not complaining about an empty sequence but complaining that the materialized value NULL can't be converted into an int.

Slauma
  • 175,098
  • 59
  • 401
  • 420
3

NB: the query with DefaultIfEmpty() may be significantly slower. In my case that was a simple query with .DefaultIfEmpty(DateTime.Now.Date).

I was too lazy to profile it but obviously EF tried to obtain all the rows and then take the Max() value.

Conclusion: sometimes handling InvalidOperationException might be the better choice.

d219
  • 2,707
  • 5
  • 31
  • 36
Andrey St
  • 388
  • 3
  • 10
3

Max will throw System.InvalidOperationException "Sequence contains no elements"

class Program
{
    static void Main(string[] args)
    {
        List<MyClass> list = new List<MyClass>();

        list.Add(new MyClass() { Value = 2 });

        IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.

        int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
    }
}

class MyClass
{
    public int Value;
}
Johan Tidén
  • 664
  • 7
  • 19
0

You can use a ternary within .Max() to handle the predicate and set its value;

// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);

You would need to handle the Workers collection being null/empty if that's a possibility, but it would depend on your implementation.

Jecoms
  • 2,558
  • 4
  • 20
  • 31
0

You can try this:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;
Carlos Toledo
  • 2,519
  • 23
  • 23
  • I think this will fail as Max is expecting an int and it is getting a null; so an error has already occurred before the null-coalescing operator comes into effect. – d219 Nov 08 '18 at 23:13
0

You could check if there are any workers before doing the Max().

private int FindMaxShoeSize(IList<MyClass> workers) {
   var workersInCompany = workers.Where(x => x.CompanyId == 8);
   if(!workersInCompany.Any()) { return 0; }
   return workersInCompany.Max(x => x.ShoeSize);
}
Reverend Sfinks
  • 141
  • 1
  • 8