Grid data jumbled up (sort/filtering) after row edit

So I have this component, which works perfectly - now - after struggling with infamous issues relating to table data not updating when a row is edited.

This now works for me after using async code (START/END block)

However, when I click on the Edit link, the data form is correctly shown, I can update and save fine, and the new data is committed to the db. But, the table loses all sort/filtering and displays data in a different order. Sometimes the row is on another page! I clearly have got something wrong here.

questions..

  1. How can i ensure that after editing a row record, the table data presentation remains unchanged apart from of course showing any updates to the row.
    2.How might I improve the Where clause on retrieving Licences from context.LicenceEntity?
  2. what foes this code do? i obtained it ffrom a github example but no comment or idea what it does... without it, my component wont work..

protected override void OnInitialized()
{
context = DbFactory.CreateDbContext();
Licences = context.LicenceEntity.Where(l => l.Id != null); // not sure how to get all rows unconditionally ??
}
public async ValueTask DisposeAsync() => await context.DisposeAsync();

Thanks!

Component:

@page "/licenceentities"

@using Microsoft.EntityFrameworkCore
@using Microsoft.AspNetCore.Components.QuickGrid
@using Licenses;
@using Licenses.Data

@inject IDbContextFactory<Licenses.Data.LicensesContext> DbFactory 
@inject NavigationManager Navigation_Manager


<PageTitle>Licences</PageTitle>

<h1>Licence List</h1>
<div>
    <p>This page demonstrates Data Grid component featuring:</p>
    <ul>
        <li>Column Sorting</li>
        <li>Compound Column Filtering</li>
        <li>Pagination [set to 3 items for demo purposes]</li>
    </ul>
    <p>Additionally, the Add new button demonstrates how easy it is to enact client side rendering, as this is a component that would have required JavaScript and a websocket back to the server.</p>
</div>

<p>
    <RadzenButton Click=@( args => Navigation_Manager.NavigateTo("licenceentities/create")) Text="Add new" Icon="add_circle" ButtonStyle="ButtonStyle.Primary" />
</p>

<style type="text/css">
    .rz-column-title {
        font-weight: bold !important;
        }
</style>
<RadzenDataGrid @ref="licenceGrid" AllowFiltering="true" FilterMode="FilterMode.Advanced" LogicalFilterOperator="LogicalFilterOperator.Or" AllowPaging="true" PageSize="3" 
                AllowSorting="true" AllowMultiColumnSorting="true" @bind-Data=@Licences TItem="LicenceEntity" GridLines=Radzen.DataGridGridLines.Default>
    <Columns>
        <RadzenDataGridColumn TItem="LicenceEntity" Property="Name" Title="Name" />
        <RadzenDataGridColumn TItem="LicenceEntity" Property="Description" Title="Description" />
        <RadzenDataGridColumn TItem="LicenceEntity" Property="Date" Title="Date created" FormatString="{0:dd MMM yyyy}"/>
        <RadzenDataGridColumn TItem="LicenceEntity" Property="ExpiryDate" Title="Expiry date" FormatString="{0:dd MMM yyyy}" />
        <RadzenDataGridColumn TItem="LicenceEntity" Sortable="false" Filterable="false" >
            <Template Context="licence">
                    <a href="@($"licenceentities/edit?id={licence.Id}")">Edit</a> |
                    <a href="@($"licenceentities/details?id={licence.Id}")">Details</a> |
                    <a href="@($"licenceentities/delete?id={licence.Id}")">Delete</a>
            </Template>
        </RadzenDataGridColumn>
    </Columns>
    <FooterTemplate>
        Licences in list: <b>@licenceGrid?.View.Count()</b> of <b>@Licences?.Count()</b>
    </FooterTemplate>
</RadzenDataGrid>

@code {
    RadzenDataGrid<LicenceEntity> ?licenceGrid;
    PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    LicensesContext context = default!;
    
    // START - Ensure data is async loaded from spearate DbContext. This ensures edits are reflected in the table.
    IQueryable<LicenceEntity> ?Licences = default;
    protected override void OnInitialized()
    {
        context = DbFactory.CreateDbContext();
        Licences = context.LicenceEntity.Where(l => l.Id != null); // not sure how to get all rows unconditionally ??
    }
    public async ValueTask DisposeAsync() => await context.DisposeAsync();
    // END
}

As far as I understand you editing is performed in a separate page and since this is navigation when you return to the page with the DataGrid everything will be loaded from scratch as you visit this page for the first time. Possible approach will be to save/load settings as illustrated here:

Thank you. How did I miss that article. I have the top level guide page bookmarked!

Aside,
So I entered the missing async code for Settings et.al, this is what I have now
I also had to add the following to the top of the file
@using Microsoft.JSInterop
@using System.Text.Json

   DataGridSettings _settings;
   public DataGridSettings Settings
   {
       get
       {
           return _settings;
       }
       set
       {
           if (_settings != value)
           {
               _settings = value;
               InvokeAsync(SaveStateAsync);
           }
       }
   }

   private async Task LoadStateAsync()
   {
       await Task.CompletedTask;

**** NEXT LINE generates the error " Cannot convert from 'string' to 'object?[]?' "
       var result = await JSRuntime.InvokeAsync<string>("window.localStorage.getItem", "Settings");
       if (!string.IsNullOrEmpty(result))
       {
           _settings = JsonSerializer.Deserialize<DataGridSettings>(result);
       }
   }

   private async Task SaveStateAsync()
   {
       await Task.CompletedTask;

**** NEXT LINE generates the error " 'JSRuntime does not contain a definition for 'InvokeVoidAsync' "
       await JSRuntime.InvokeVoidAsync("window.localStorage.setItem", "Settings", JsonSerializer.Serialize<DataGridSettings>(Settings));
   }

   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
       if (firstRender)
       {
           await LoadStateAsync();
           StateHasChanged();
       }
   }

As can be see, there are compilation errors. This is my 3rd day working through examples. I am learning Visual Studio, C#, NET and Radzen all at the same time - so please be gentle with me :slight_smile:

I've no idea why this is wrong if it came from an official site example. Must be my setup.
Are you able to help please?

Setup:

Microsoft Visual Studio Professional 2022
Version 17.10.4
VisualStudio.17.Release.LTSC.17.10/17.10.4+35027.167
Microsoft .NET Framework
Version 4.8.09037

Installed Version: Professional

ASP.NET and Web Tools 17.10.344.38934
ASP.NET and Web Tools

Azure App Service Tools v3.0.0 17.10.344.38934
Azure App Service Tools v3.0.0

Azure Functions and Web Jobs Tools 17.10.344.38934
Azure Functions and Web Jobs Tools

C# Tools 4.10.0-3.24324.8+04fb39164c99c519832109f21b22630bc9246fce
C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Common Azure Tools 1.10
Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.

GitHub Copilot 0.2.889.30432
GitHub Copilot is an AI pair programmer that helps you write code faster and with less work.

Microsoft JVM Debugger 1.0
Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines

NuGet Package Manager 6.10.1
NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/

Razor (ASP.NET Core) 17.10.3.2427201+4f57d1de251e654812adde201c0265a8ca7ca31d
Provides languages services for ASP.NET Core Razor.

SQL Server Data Tools 17.10.178.1
Microsoft SQL Server Data Tools

TypeScript Tools 17.0.30327.2001
TypeScript Tools for Microsoft Visual Studio

Visual Basic Tools 4.10.0-3.24324.8+04fb39164c99c519832109f21b22630bc9246fce
Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Visual F# Tools 17.10.0-beta.24228.1+dd749058c91585e9b5dae62b0f8df892429ee28f
Microsoft Visual F# Tools

Visual Studio IntelliCode 2.2
AI-assisted development for Visual Studio.

PM> dotnet --info
.NET SDK:
 Version:           8.0.303
 Commit:            29ab8e3268
 Workload version:  8.0.300-manifests.34944930
 MSBuild version:   17.10.4+10fbfbf2e

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19045
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.303\

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.7
  Architecture: x64
  Commit:       2aade6beb0

.NET SDKs installed:
  8.0.107 [C:\Program Files\dotnet\sdk]
  8.0.303 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 6.0.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
  x86   [C:\Program Files (x86)\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
  Not set

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

Actually, i tried and got into a right pickle. So i started all over and did this:

Created a Utils\LocalStorage.cs file to set/get blobs in browser storage.

problem 1:
Then I recrafted the component, but, if I bind the settings then sorting and filtering doesn't work when clicking on column headers. Sorting just does not work now, it seems random.
However moving away from the page and returning, works (in that the layout is retained , not reset) - whereas it did not before. So I've improved the "memory" but ruined the sorting/filtering.
If i remove the "@bind-Settings" from the component, the sorting and filtering works but layout is reset when returning to the page.
Grr.

Here is my code - are you able to help please. It is a bit complex (to me)

Utils\LocalStorage

using Microsoft.JSInterop;
using System.Text.Json;

namespace Licenses.Utils;
/*
 * Class LocalStorage
 * 
 * Allows save/get blob data in browser storage to be performed asynchronous operations.
 */
public class LocalStorage
{
    // Instantiate the JS runtime interface
    protected IJSRuntime JSRuntimeInstance { get; set; }

    // Constructor
    public LocalStorage(IJSRuntime jsRuntime)
    {
        JSRuntimeInstance = jsRuntime;
    }

    // Set blob in local storage.
    // The blob will be referenced via the passed string keyname value
    public ValueTask SetItem(string key, object data)
    {
        return JSRuntimeInstance.InvokeVoidAsync("localStorage.setItem", new object[] {key, JsonSerializer.Serialize(data) });
    }

    // Get blob from local storage
    public async Task<T> GetItem<T>(string key)
    {
        var data = await JSRuntimeInstance.InvokeAsync<string>("localStorage.getItem", key);
        if (!string.IsNullOrEmpty(data))
        {
            return JsonSerializer.Deserialize<T>(data);
        }

        return default;
    }

    public ValueTask RemoveItem(string key)
    {
        return JSRuntimeInstance.InvokeVoidAsync("localStorage.removeItem", key);
    }
}

Components\Pages\LicenceEntityPages\index.razor

@page "/licenceentities"

@using Microsoft.EntityFrameworkCore
@using Microsoft.AspNetCore.Components.QuickGrid
@using Licenses;
@using Licenses.Data
@using Microsoft.JSInterop
@using System.Text.Json
@using Utils

@inject LocalStorage LocalStorage
@inject IJSRuntime JsInterop
@inject IDbContextFactory<Licenses.Data.LicensesContext> DbContextFactory 
@inject NavigationManager Navigation_Manager


<PageTitle>Licences</PageTitle>

<h1>Licence List</h1>
<div>
    <p>This page demonstrates Data Grid component featuring:</p>
    <ul>
        <li>Column Sorting</li>
        <li>Compound Column Filtering</li>
        <li>Pagination [set to 3 items for demo purposes]</li>
    </ul>
    <p>Additionally, the Add new button demonstrates how easy it is to enact client side rendering, as this is a component that would have required JavaScript and a websocket back to the server.</p>
</div>

<p>
    <RadzenButton Click=@( args => Navigation_Manager.NavigateTo("licenceentities/create")) Text="Add new" Icon="add_circle" ButtonStyle="ButtonStyle.Primary" />
</p>

<style type="text/css">
    .rz-column-title {
        font-weight: bold !important;
        }
</style>

<RadzenDataGrid @ref="licenceGrid"  AllowFiltering="true" FilterMode="FilterMode.Advanced" LogicalFilterOperator="LogicalFilterOperator.Or"
                AllowPaging="true" PageSize="5" AllowSorting="true" AllowMultiColumnSorting="true" @bind-Data=@Licences TItem="LicenceEntity" 
                GridLines=Radzen.DataGridGridLines.Default>
    <Columns>
        <RadzenDataGridColumn TItem="LicenceEntity" Property="Name" Title="Name" />
        <RadzenDataGridColumn TItem="LicenceEntity" Property="Description" Title="Description" />
        <RadzenDataGridColumn TItem="LicenceEntity" Property="Date" Title="Date created" FormatString="{0:dd MMM yyyy}"/>
        <RadzenDataGridColumn TItem="LicenceEntity" Property="ExpiryDate" Title="Expiry date" FormatString="{0:dd MMM yyyy}" />
        <RadzenDataGridColumn TItem="LicenceEntity" Sortable="false" Filterable="false" >
            <Template Context="licence">
                    <a href="@($"licenceentities/edit?id={licence.Id}")">Edit</a> |
                    <a href="@($"licenceentities/details?id={licence.Id}")">Details</a> |
                    <a href="@($"licenceentities/delete?id={licence.Id}")">Delete</a>
            </Template>
        </RadzenDataGridColumn>
    </Columns>
    <FooterTemplate>
        Licences in list: <b>@licenceGrid?.View.Count()</b> of <b>@Licences?.Count()</b>
    </FooterTemplate>
</RadzenDataGrid>

@code {
    public RadzenDataGrid<LicenceEntity>? licenceGrid;
    public PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    private Licenses.Data.LicensesContext context = default!;
    private IEnumerable<LicenceEntity> ?Licences = default;
    private DataGridSettings gridSettings = default!;
    
    protected override void OnInitialized()
    {
        context = DbContextFactory.CreateDbContext();
        Licences = context.LicenceEntity.ToList();
    }
    public async ValueTask DisposeAsync() => await context.DisposeAsync();

    // Keyname to be used to store blob data for grid settings
    private string UniqueStorageKey = "unique-grid-state-key";

     /*
     * Settings getter/setter method.
     * This method will ensure that when navigating away/back to the grid, 
     * that the settings for ordering, filtering, layout etc are maintained and as before.
     */ 
    public DataGridSettings Settings
    {
        get
        {
            return gridSettings;
        }
        set
        {
            if (gridSettings != value)
            {
                gridSettings = value;
                InvokeAsync(SaveStateAsync);
            }
        }
    }

     /*
     * Method to save settings to local browser.
    */
    private async Task SaveStateAsync()
    {
        // Using the keyname, save the blobl in local storage which is of type DataGridSettings
        await Task.CompletedTask; 
        await LocalStorage.SetItem(UniqueStorageKey, gridSettings);
    }

    private async Task LoadStateAsync()
    {
        try
        {
            var state = await LocalStorage.GetItem<DataGridSettings>(UniqueStorageKey);
            // Save blob into grid settings field
            if (state != null)
            {
                gridSettings = state;
            }
        }
        catch (InvalidOperationException e)
        {
                // JSInterop cannot be used during pre-rendering, so the code above will throw.
                // Once the app initializes, it will work fine.
        }
    }

    /*
     * Method to ensure that when the grid renders, restore blob from local browser storage
     */
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await LoadStateAsync();
            StateHasChanged();
        }
    }  
}

Program.cs

 ..
builder.Services.AddScoped<LocalStorage>(); // Util to store data in browser
 ..

The code in our demo is fairly simple, not sure however why isn't working in the way you adapted it. Try to debug your app.

I closed the browser and restarted.
All works.
Perfect.

Thanks for your help bud