62

When calling Max() on an IQueryable and there are zero records I get the following exception.

The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.

var version = ctx.Entries
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
    .Max(e => e.Version);

Now I understand why this happens my question is how is the best way to do this if the table can be empty. The code below works and solves this problem, but its very ugly is there no MaxOrDefault() concept?

int? version = ctx.Entries
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
    .Select(e => (int?)e.Version)
    .Max();
bleevo
  • 1,637
  • 2
  • 18
  • 30

8 Answers8

104

Yes, casting to Nullable of T is the recommended way to deal with the problem in LINQ to Entities queries. Having a MaxOrDefault() method that has the right signature sounds like an interesting idea, but you would simply need an additional version for each method that presents this issue, which wouldn't scale very well.

This is one of many mismatches between how things work in the CLR and how they actually work on a database server. The Max() method’s signature has been defined this way because the result type is expected to be exactly the same as the input type on the CLR. But on a database server the result can be null. For that reason, you need to cast the input (although depending on how you write your query it might be enough to cast the output) to a Nullable of T.

Here is a solution that looks slightly simpler than what you have above:

var version = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .Max(e =>(int?)e.Version);
starball
  • 20,030
  • 7
  • 43
  • 238
divega
  • 6,320
  • 1
  • 31
  • 31
  • Hey Divega, see my answer do you see any issues with this approach? – bleevo Jul 18 '10 at 09:24
  • 1
    bleevo, I am afraid unless you change your method to take IQueryable and Expression> arguments, the method will implicitly switch to client-side evaluation with LINQ to Objects. Personally, I think just casting to T? is simpler. – divega Jul 18 '10 at 20:09
34

Try this to create a default for your max.

int version = ctx.Entries 
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId) 
    .Max(e =>(int?)e.Version) ?? 0;
CodeDog
  • 341
  • 3
  • 2
  • 6
    The accepted answer returns a nullable int. Your answer is a concise way to return a non-nullable int, which was more helpful for me. – kevinpo Feb 18 '14 at 20:48
  • WOW, your answer is the best, it worked for me, Really special thanks. Here is how I used it: `int MaxID = db.Employees.Max(emp => (int?)emp.EmpID) ?? 0;` – Ahmed Suror Jan 22 '21 at 01:48
13

You could write a simple extension method like this, it returns the default value of type T if no records exist and is then apply Max to that or the query if records exist.

public static T MaxOrEmpty<T>(this IQueryable<T> query)
{
    return query.DefaultIfEmpty().Max();
}

and you could use it like this

maxId = context.Competition.Select(x=>x.CompetitionId).MaxOrEmpty();
Code Uniquely
  • 6,356
  • 4
  • 30
  • 40
  • The nice thing about this solution is that you can specify a default value (DefaultIfEmpty(value)). So you don't have to return a nullable type. – Robert Sandu Dec 08 '17 at 09:14
  • This should be the preferred solution – deanwilliammills Sep 23 '19 at 19:19
  • This didn't work for me, on EF Core 3.1.14. Calling `query.Select(e=>e.Version).DefaultIfEmpty().Max()` throws `InvalidOperationException: Sequence contains no elements.`. Using `DefaultIfEmpty(0)` instead threw "Processing of the LINQ expression 'DbSet .Select(p => p.Version) .DefaultIfEmpty(__p_0)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core." Using `query.Max(e =>(int?)e.Version) ?? 0` worked. – rockgecko Aug 29 '22 at 02:36
3

I couldnt take no for an answer :) I have tested the below and it works, I havent checked the SQL generated yet so be careful, I will update this once I have tested more.

var test = ctx.Entries
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
    .MaxOrDefault(x => x.Version);

public static TResult? MaxOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
    where TResult : struct
{
    return source
        .Select(selector)
        .Cast<TResult?>()
        .Max();
}
bleevo
  • 1,637
  • 2
  • 18
  • 30
0

Try this:

IEnumerable<AlertsResultset> alerts = null;

    alerts = (from POA in SDSEntities.Context.SDS_PRODUCT_ORDER_ALERT
              join A in SDSEntities.Context.SDS_ALERT on POA.ALERT_ID equals A.ALERT_ID
              orderby POA.DATE_ADDED descending
              select new AlertsResultset
              {
                  ID = POA.PRODUCT_ORDER_ALERT_ID == null ? 0:POA.PRODUCT_ORDER_ALERT_ID ,
                  ITEM_ID = POA.ORDER_ID.HasValue ? POA.ORDER_ID.Value : POA.PRODUCT_ID.Value,
                  Date = POA.DATE_ADDED.Value,
                  orderType = SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().ORDER_TYPE,
                  TransactionNumber = POA.PRODUCT_ID.HasValue ? (SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().TRANSACTION_NUMBER) : (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().TRANSACTION_NUMBER),
                  Publisher = POA.PRODUCT_ID.HasValue ?
                  (
                  SDSEntities.Context.SDS_PRODUCT.Where(p => p.PRODUCT_ID == POA.PRODUCT_ID.Value).FirstOrDefault().PRODUCT_TYPE_NUMBER == "ISSUE" ? (from prod in SDSEntities.Context.SDS_PRODUCT
                                                                                                                                                      join ji in SDSEntities.Context.SDS_JOURNAL_ISSUE on prod.PRODUCT_ID equals ji.PRODUCT_ID
                                                                                                                                                      join j in SDSEntities.Context.SDS_JOURNAL on ji.JOURNAL_ID equals j.JOURNAL_ID
                                                                                                                                                      where prod.PRODUCT_ID == POA.PRODUCT_ID
                                                                                                                                                      select new { j.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME : (from prod in SDSEntities.Context.SDS_PRODUCT
                                                                                                                                                                                                                          join bi in SDSEntities.Context.SDS_BOOK_INSTANCE on prod.PRODUCT_ID equals bi.PRODUCT_ID
                                                                                                                                                                                                                          join b in SDSEntities.Context.SDS_BOOK on bi.BOOK_ID equals b.BOOK_ID
                                                                                                                                                                                                                          where prod.PRODUCT_ID == POA.PRODUCT_ID
                                                                                                                                                                                                                          select new { b.PUBLISHER_NAME }).FirstOrDefault().PUBLISHER_NAME
                  )
                  : (SDSEntities.Context.SDS_ORDER.Where(o => o.ORDER_ID == POA.ORDER_ID.Value).FirstOrDefault().PUBLISHER_NAME),
                  Alert = A.ALERT_NAME,
                  AlertType = A.ALERT_TYPE,
                  IsFlagged = POA.IS_FLAGGED.Value,
                  Status = POA.ALERT_STATUS
              });
Nazik
  • 8,696
  • 27
  • 77
  • 123
0

how about

var version = ctx.Entries
    .Where(e => e.Competition.CompetitionId == storeCompetition.CompetitionId)
    .Max(e => (int?)e.Version);

less ugly, more elegant

TimTIM Wong
  • 788
  • 5
  • 16
0

I want to suggest a merge from the existing answers:

@divega answer works great and the sql output is fine but because of 'don't repeat yourself' an extension will be a better way like @Code Uniquely showed. But this solution can output more complex sql as you needed. But you can use the following extension to bring both together:

 public static int MaxOrZero<TSource>(this IQueryable<TSource> source, 
 Expression<Func<TSource, int>> selector)
        {
            var converted = Expression.Convert(selector.Body, typeof(int?));
            var typed = Expression.Lambda<Func<TSource, int?>>(converted, selector.Parameters);

            return source.Max(typed) ?? 0;
        }
Flo
  • 122
  • 1
  • 1
  • 8
0

You can use:

FromSqlRaw("Select ifnull(max(columnname),0) as Value from tableName");
Fateme Mirjalili
  • 762
  • 7
  • 16
  • 1
    That's not an answer. It's a work-around. Suggestions for work-arounds are are often experienced as frustrating because they don't shed light on the issue in hand. – Gert Arnold Nov 12 '20 at 15:44