0

I am using Entity Framework Core for my ASP.NET Core WebApi project.
I came across these two methods while using Linq, I am curious to find the difference between FirstOrDefaultAsync vs FirstOrDefault?

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
user14520249
  • 1
  • 1
  • 1

2 Answers2

1

Excellent medium post here: https://medium.com/@deep_blue_day/long-story-short-async-await-best-practices-in-net-1f39d7d84050

Summarized:

There are basically two scenarios where Async/Await is the right solution.

I/O-bound work: Your code will be waiting for something, such as data from a database, reading a file, a call to a web service. In this case you should use Async/Await, but not use the Task Parallel Library.

CPU-bound work: Your code will be performing a complex computation. In this case, you should use Async/Await but spawn the work off on another thread using Task.Run. You may also consider using the Task Parallel Library.

Your case falls down on the I/O-bound work.

Why is there a non-async version then? That is to support legacy code and/or code where using asynchronous methods is not allowed, due to execive refactoring. (one async method, means all the call stack should be async).

Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
0

Let's start with the implementation of these methods:

FirstOrDefault

Source (It has two overloads w/o Predicate)

public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source)
{
    if (source == null)
        throw Error.ArgumentNull("source");
    return source.Provider.Execute<TSource>(
        Expression.Call(
            null,
            GetMethodInfo(Queryable.FirstOrDefault, source),
            new Expression[] { source.Expression }
        ));
}

public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
{
    if (source == null)
        throw Error.ArgumentNull("source");
    if (predicate == null)
        throw Error.ArgumentNull("predicate");
    return source.Provider.Execute<TSource>(
        Expression.Call(
            null,
            GetMethodInfo(Queryable.FirstOrDefault, source, predicate),
            new Expression[] { source.Expression, Expression.Quote(predicate) }
        ));
}

FirstOrDefaultAsync

Source (It has two overloads w/o Predicate)

public static Task<TSource> FirstOrDefaultAsync<TSource>(
    [NotNull] this IQueryable<TSource> source,
    CancellationToken cancellationToken = default)
{
    Check.NotNull(source, nameof(source));

    return ExecuteAsync<TSource, Task<TSource>>(QueryableMethods.FirstOrDefaultWithoutPredicate, source, cancellationToken);
}

public static Task<TSource> FirstOrDefaultAsync<TSource>(
    [NotNull] this IQueryable<TSource> source,
    [NotNull] Expression<Func<TSource, bool>> predicate,
    CancellationToken cancellationToken = default)
{
    Check.NotNull(source, nameof(source));
    Check.NotNull(predicate, nameof(predicate));

    return ExecuteAsync<TSource, Task<TSource>>(QueryableMethods.FirstOrDefaultWithPredicate, source, predicate, cancellationToken);
}

They are both calling the following ExecuteAsync overload:

private static TResult ExecuteAsync<TSource, TResult>(
    MethodInfo operatorMethodInfo,
    IQueryable<TSource> source,
    Expression expression,
    CancellationToken cancellationToken = default)
{
    if (source.Provider is IAsyncQueryProvider provider)
    {
        if (operatorMethodInfo.IsGenericMethod)
        {
            operatorMethodInfo
                = operatorMethodInfo.GetGenericArguments().Length == 2
                    ? operatorMethodInfo.MakeGenericMethod(typeof(TSource), typeof(TResult).GetGenericArguments().Single())
                    : operatorMethodInfo.MakeGenericMethod(typeof(TSource));
        }

        return provider.ExecuteAsync<TResult>(
            Expression.Call(
                instance: null,
                method: operatorMethodInfo,
                arguments: expression == null
                    ? new[] { source.Expression }
                    : new[] { source.Expression, expression }),
            cancellationToken);
    }

    throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync);
}

Compare the to Provider call

Sync

return source.Provider.Execute<TSource>(Expression.Call(...))

Async

return provider.ExecuteAsync<TResult>(Expression.Call(...))

As you can see the difference between these two calls is that how you communicate with the data source:

  • If you are doing it in a synchronous fashion then your calling thread is blocked and remains idle while the underlying network driver executes the requested I/O operation.
  • If you are doing it in an asynchronous fashion then your calling thread is freed up after it has dispatched the work to the underlying network driver to perform the requested I/O operation in a non-blocking way. So your caller thread can execute other code while its waiting for the driver to notify the scheduler that the requested operation has run to completion.
Peter Csala
  • 17,736
  • 16
  • 35
  • 75