vkhorikov/CSharpFunctionalExtensions

Possilbe issues when using CombineAsyncLeft if the IEnumerable<Task<Result> must be iterated in order

Opened this issue · 3 comments

Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance. .

This mean that a code like:

ids.Select(async () => Result.Try(await dbContext.DataSet.FirstOrDefault(e => e.Id = id))
     .Combine()

May result with an error:

second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

I think we should have an option:

public enum AsyncExecutionOrder { Unordered = 0 , InOrder }

public static async Task<Result<IEnumerable<T>>> Combine<T>(this IEnumerable<Task<Result<T>>> tasks, string errorMessageSeparator = null, AsyncExecutionOrder  order= TaskCombineOrder.Unordered)
{
    Result<T>[] results = (order == AsyncExecutionOrder .Unordered)
                                      ? await Task.WhenAll(tasks).DefaultAwait();
                                      : await tasks.ExecuteInOrder()
    return results.Combine(errorMessageSeparator);
}

public static async Task<Result<IEnumerable<T>>> ExecuteInOrder<T>(this IEnumerable<Task<Result<T>>> tasks)
{
    List<Result<T>> results = new List<Result<T>>();
    foreach (var task in tasks)
    {
        results.Add(await task);
    }
    return results;
}

I tested it on my case with:

        public static async Task<Result<IEnumerable<T>>> CombineInOrder<T>(this IEnumerable<Task<Result<T>>> tasks, string? errorMessageSeparator = null)
        {
            List<Result<T>> results = new List<Result<T>>();
            foreach (var task in tasks)
            {
                results.Add(await task);
            }
            return results.Combine(errorMessageSeparator);
        }

and it fix the async issue of the DbContext.

I can implement it if this suggestion will be approved.

We can add the second method (CombineInOrder), but I don't think the AsyncExecutionOrder enum and the additional argument in Combine are needed.

@vkhorikov I have a branch I want to push for a PR but I get 403. Can you please add me as a contributer?

@YudApps You need to create a fork first, commit/push your changes and open a PR then. Here are the guidelines: https://github.com/MarcDiethelm/contributing/blob/master/README.md