ToDataSourceResultAsync is not actually Async

1 Answer 96 Views
Grid
ERCANPOLAT
Top achievements
Rank 1
ERCANPOLAT asked on 16 Oct 2024, 03:22 PM

In the example below products is actually DbSet<Product>

public async Task<ActionResult> Products_Read([DataSourceRequest]DataSourceRequest request)
{
    using (var northwind = new SampleEntities())
    {
        IQueryable<Product> products = northwind.Products;
        DataSourceResult result = await products.ToDataSourceResultAsync(request);
        return Json(result);
    }
}

Under the hood the ToDataSourceResultAsync call is executed inside Task.Run

And that Task.Run is calling sync methods of IQueryable which means EntityframeworkCore queries are not taking place with async I/O

 

I came up with something like below. But couldnt figured out how to support groups and aggregates.

It works with filters, sorts and paging.


        public static Task<AsDataSourceResult<TResult>> ToAsDataSourceResult<TModel, TResult>(this IQueryable<TModel> enumerable, DataSourceRequest request, Func<TModel, TResult> selector)
        {
            return enumerable.AsCreateDataSourceResultAsync(request, selector);
        }

        private static async Task<AsDataSourceResult<TResult>> AsCreateDataSourceResultAsync<TModel, TResult>(this IQueryable<TModel> queryable, DataSourceRequest request, Func<TModel, TResult> selector)
        {
            var result = new AsDataSourceResult<TResult>();

            var data = queryable;

            if (request.Filters.Count > 0)
                data = (IQueryable<TModel>)data.Where(request.Filters);

            result.Total = await data.CountAsync();

            var sort = new List<SortDescriptor>();

            if (request.Sorts != null)
                sort.AddRange(request.Sorts);

            if (sort.Count == 0 && queryable.Provider.IsEntityFrameworkProvider())
            {
                // The Entity Framework provider demands OrderBy before calling Skip.
                var sortDescriptor = new SortDescriptor
                {
                    Member = queryable.ElementType.FirstSortableProperty()
                };
                sort.Add(sortDescriptor);
            }

            if (sort.Count > 0)
                data = (IQueryable<TModel>)data.Sort(sort);

            data = data.Page(request.Page - 1, request.PageSize);

            var list = new List<TResult>();

a

            result.Data = list;

            return result;
        }

        private static IQueryable<TResult> Page<TResult>(this IQueryable<TResult> source, int pageIndex, int pageSize)
        {
            var query = source;

            query = query.Skip(pageIndex * pageSize);

            if (pageSize > 0)
                query = query.Take(pageSize);

            return query;
        }

        private static string FirstSortableProperty(this Type type)
        {
            var firstSortableProperty = type.GetProperties().FirstOrDefault(property => property.PropertyType.IsPredefinedType()) ?? throw new NotSupportedException("Exceptions.CannotFindPropertyToSortBy");
            return firstSortableProperty.Name;
        }

        private static bool IsEntityFrameworkProvider(this IQueryProvider provider)
        {
            var name = provider.GetType().FullName;
            return name == "System.Data.Objects.ELinq.ObjectQueryProvider" ||
                    name == "System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider" ||
                    name.StartsWith("LinqKit.ExpandableQueryProvider") ||
                    name.StartsWith("Microsoft.Data.Entity.Query.EntityQueryProvider") ||
                    name.StartsWith("System.Data.Entity.Internal.Linq");
        }

 

AsDataSourceResult<TResult> is wrapper for typed access to DataSourceResult

How do i support all operations which ToDataSourceResult (sync) one supports.

1 Answer, 1 is accepted

Sort by
0
Mihaela
Telerik team
answered on 21 Oct 2024, 12:18 PM

Hello Ercan,

The creation of the DataSourceResult is synchronous operation, but the Task.Run() method opens a new background thread for that synchronous operation and yields the control back to the calling thread (using await) before it starts. This way, the main thread is not blocked during the execution of the Task.Run() operation.

public async Task<ActionResult> Products_Read([DataSourceRequest]DataSourceRequest request)
{
    using (var northwind = new SampleEntities())
    {
        IQueryable<Product> products = northwind.Products;
        DataSourceResult result = await products.ToDataSourceResultAsync(request);
        return Json(result);
    }
}

// The ToDataSourceResultAsync() overload returns Task<DataSourceResult>
public static Task<DataSourceResult> ToDataSourceResultAsync<TModel, TResult>(this IQueryable<TModel> queryable, DataSourceRequest request, ModelStateDictionary modelState, Func<TModel, TResult> selector)
{
        return CreateDataSourceResultAsync(() => queryable.ToDataSourceResult(request, modelState, selector));
}

This approach is explained in the following section in the MS documnetation:

https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios#cpu-bound-example-perform-a-calculation-for-a-game

I hope this information will be helpful to you.

Regards,
Mihaela
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

ERCANPOLAT
Top achievements
Rank 1
commented on 22 Oct 2024, 10:47 AM

The creation of the DataSourceResult is synchronous operation, but the Task.Run() method opens a new background thread for that synchronous operation and yields the control back to the calling thread (using await) before it starts.

The problem is this operation is I/O Bound and should be executed with real async versions of those methods.


IQueryable<TModel> queryable

await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken))
{
	...
}

this is the real async version. 

 

https://github.com/dotnet/efcore/blob/main/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs#L2298

Mihaela
Telerik team
commented on 24 Oct 2024, 10:18 AM

Thank you for your clarifications, Ercan.

In this case, I would recommend submitting a feature request in our Feedback Portal and explaining your scenario in detail. Also, feel free to share your ideas on how this method can be improved. We regularly monitor the items there, and the ones that gain popularity are usually included in the roadmap.

Best,
Mihaela

Tags
Grid
Asked by
ERCANPOLAT
Top achievements
Rank 1
Answers by
Mihaela
Telerik team
Share this question
or