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.