Josh Brunton
Josh Brunton

Reputation: 527

MudBlazor DataGrid loads entire database when used with Entity Framework

I'm using a MudBlazor DataGrid to display records from SQL Server, which I am accessing via Entity Framework. The table I'm querying might contain anywhere from several thousand to several million rows.

On small test data sets, the DataGrid performs just fine, however when I connect it to production-scale test data, the performance becomes extremely slow, taking roughly the same time a SELECT * FROM MyTable takes on SQL Server to load or change pages. The UI on the client side also becomes terribly slow (which suggests to me that the server may be transmitting a lot of data to the client and using up lots of memory).

My DataGrid looks as follows:

<MudDataGrid Items="MyContext.MyTable"
             FixedHeader="true" FixedFooter="true" Hover="true"
             SortMode="SortMode.Single" Filterable="true"
             FilterMode="DataGridFilterMode.ColumnFilterMenu"
             RowClick="NavigateToWork" T="IMyRecord" Hideable="true"
             FilterCaseSensitivity="DataGridFilterCaseSensitivity.CaseInsensitive"
             DragDropColumnReordering="true" ColumnsPanelReordering="true"
             ColumnResizeMode="ResizeMode.Column"
             ShowMenuIcon="true">

    <Columns>
        <PropertyColumn Property="@(x => x.Id)" />
        @* etc *@
    </Columns>

    <PagerContent>
        <MudDataGridPager T="IMyRecord" />
    </PagerContent>
</MudDataGrid>

Is there something I'm missing to specify that it shouldn't load the whole table, or does the DataGrid internally use AsEnumerable or ToList on the IQueryable I pass to it and force the download somewhere beyond my control?

Upvotes: 0

Views: 1702

Answers (2)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30330

when I connect it to production-scale test data, the performance becomes extremely slow, taking roughly the same time a SELECT * FROM MyTable takes on the SQL server to load or change pages.

That's because that's what it's doing.

You need to provide a delegate to the ServerData Parameter on MudDataGrid to let the control make paged data requests into the data pipeline. GridState is the request, containing the page data [and sort and filter data if specified]. GridData is the returned result, containing the page dataset and the total unpaged record count.

Here's a demo using the MudBlazor template Weather page.

First the data pipeline - in this case a simple singleton data provider. The important bit is GetDataAsync which matches the delegate signature of ServerData. It just gets the data page from the data source.

using MudBlazor;

namespace SO78621960.Components;

public class WeatherForecast
{
    public DateOnly Date { get; set; }
    public int TemperatureC { get; set; }
    public string? Summary { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

public class WeatherProvider
{
    private List<WeatherForecast> _forecasts;

    private WeatherProvider()
    {
        _forecasts = InitializeData();
    }

    private List<WeatherForecast> InitializeData()
    {
        var startDate = DateOnly.FromDateTime(DateTime.Now);
        var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
        return Enumerable.Range(1, 50000).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        }).ToList();
    }

    private static WeatherProvider? _instance;

    public static WeatherProvider GetInstance()
    {
        if (_instance == null)
            _instance = new WeatherProvider();
        
        return _instance;
    }

    // This code would normally query the DbSet in the DbContext instance to minimize the data requested
    public static async Task<GridData<WeatherForecast>> GetDataAsync(GridState<WeatherForecast> request)
    {
        // Fake async behaviour
        await Task.Delay(100);

        var instance = WeatherProvider.GetInstance();

        // construct a Query
        // may also contain sorting and filtering
        var startIndex = (request.Page) * request.PageSize;

        var query = instance._forecasts
            .Skip(startIndex)
            .Take(request.PageSize);

        // execute the queries - async in a live DbContext pipeline
        var items = query.ToList();
        var totalItems = instance._forecasts.Count;

        // construct a GridData object to return
        return new GridData<WeatherForecast>() { Items = items, TotalItems = totalItems };
        
    }
}

And then the demo page:

@page "/weather"

<PageTitle>Weather</PageTitle>

<MudText Typo="Typo.h3" GutterBottom="true">Weather forecast</MudText>
<MudText Class="mb-8">This component demonstrates fetching data from the server.</MudText>

<MudDataGrid T="WeatherForecast" ServerData="WeatherProvider.GetDataAsync">
    <Columns>
        <PropertyColumn Property="x => x.Date" />
        <PropertyColumn Property="x => x.TemperatureC" />
        <PropertyColumn Property="x => x.TemperatureF" />
        <PropertyColumn Property="x => x.Summary" />
    </Columns>
    <PagerContent>
        <MudDataGridPager T="WeatherForecast" />
    </PagerContent>
</MudDataGrid>

@code {
}

[Polite] Two broader points:

  1. You should never make unrestricted queries against large data sets. It's a recipe for poor performance. Only get what you can display.

  2. If your live dataset has 1 million records, your test data set should also have 1 million records.

enter image description here

Upvotes: 4

Steve Py
Steve Py

Reputation: 34908

When dealing with data grids you want one that supports server-side pagination vs. client-side pagination. (or worse, no pagination) Many grids support pagination but if it's client-side only this results in the data grid pulling all data via the select API call etc. then paginating results via JS. Server-side pagination involves a Select method that accepts search criteria, a page and pagesize, along with sorting conditions, as well as a Count method so that your server-side code can perform an OrderBy, Skip and Take plus render a pagination control for switching between pages of results.

I'm not familiar with MudBlazor but have a start with (https://github.com/MudBlazor/MudBlazor/issues/6623) and searching up on server side pagination with MudBlazor to see what is currently supported officially and via work-arounds. The key to server side pagination is ensuring the client-side or server side keeps track and relays info on search criteria, ordering, and the current page to send with each request.

Grids that don't support pagination can still work but you will need to handle the pagination control yourself where the grid only displays one page of data at any given time. I.e. as far as the grid's select method is concerned, if the page size is 50 records it will always be returned 50 or less rows. This requires more babysitting with client-side JS/C# code. If the grid supports only client-side pagination this usually involves disabling the grid's pagination and using your own.

Upvotes: 0

Related Questions