Unable to insert new row into DataGrid

The code below edits existing records and deletes them, but doesn't insert new records. I've followed as much guidance as I can find here in the forums and in the documentation (in fact this is practically identical) but the grid stubbornly refuses to add a new row. I can see in the debugger that it executes the InsertRow() but CountryCreated() is never called, nor does the new country appear in the list.

@page "/settings"
@using Bookstore.Domains.People.Models
@using Bookstore.UI.Web.Store.Shared.Requests.People
@using Bookstore.UI.Web.Store.Shared.Responses.People
@using Blazorise
@inject HttpClient _httpClient 

<h3>Settings</h3>

<div class="row">
    <div class="col-md-6">
        <fieldset>
            <legend>Countries</legend>
            <RadzenButton Icon="add_circle_outline" Size="ButtonSize.Medium" Click="NewCountry" Style="margin-bottom: 10px" />
            <RadzenDataGrid @ref="_countriesGrid" TItem="Country"
                            Data="_countries" Count="_countryCount" LoadData="ReadCountries"
                            AllowFiltering="true" AllowSorting="true" AllowVirtualization="true"
                            SelectionMode="DataGridSelectionMode.Single" @bind-Value="_selectedCountries"
                            EditMode="DataGridEditMode.Single" RowCreate="CountryCreated" RowUpdate="CountryUpdated"
                            Style="height: 640px">
                <Columns>
                    <RadzenDataGridColumn TItem="Country" Property="Id" Visible="false" />
                    <RadzenDataGridColumn TItem="Country" Property="Abbreviation" Title="Abbreviation">
                        <EditTemplate Context="country">
                            <RadzenTextBox @bind-Value="country.Abbreviation" /> 
                        </EditTemplate>
                    </RadzenDataGridColumn>
                    <RadzenDataGridColumn TItem="Country" Property="Name" Title="Name" SortOrder="SortOrder.Ascending">
                        <EditTemplate Context="country">
                            <RadzenTextBox @bind-Value="country.Name" />
                        </EditTemplate>
                    </RadzenDataGridColumn>
                    <RadzenDataGridColumn TItem="Country" Title="Actions" Filterable="false" Sortable="false" Width="100px">
                        <Template Context="country">
                            <RadzenButton Icon="edit" Size="ButtonSize.Small" Click="async () => await _countriesGrid.EditRow(country)" />
                            <RadzenButton Icon="close" Size="ButtonSize.Small" Click="async () => await RemoveCountry(country)" />
                        </Template>
                        <EditTemplate Context="country">
                            <RadzenButton Icon="save" Size="ButtonSize.Small" Click="async () => await SaveCountry(country)" />
                            <RadzenButton Icon="cancel" Size="ButtonSize.Small" Click="() => _countriesGrid.CancelEditRow(country)" />
                        </EditTemplate>
                    </RadzenDataGridColumn>
                </Columns>
            </RadzenDataGrid>
        </fieldset>
    </div>
    <div class="col-md-6">
        <fieldset>
            <legend>Provinces</legend>
        </fieldset>
    </div>
</div>

@code 
{
    RadzenDataGrid<Country> _countriesGrid;
    IList<Country> _countries;
    IList<Country> _selectedCountries;
    int _countryCount;

    async Task NewCountry()
    {
        var country = new Country {Id = Guid.NewGuid(), Abbreviation = "XX", Name = "New Country"};
        await _countriesGrid.InsertRow(country);
    }

    async Task CountryCreated(Country country)
    {
        var request = new SaveCountryRequest {Country = country};
        await _httpClient.PostAsJsonAsync($"/country", request);
    }

    async Task ReadCountries(LoadDataArgs ev)
    {
        var response = await _httpClient.GetFromJsonAsync<FindCountriesResponse>($"/country?Filter={ev.Filter}&OrderBy={ev.OrderBy}&Skip={ev.Skip}&Top={ev.Top}");
        _countries = response?.Countries;
        _countryCount = response?.Count ?? 0;
        _selectedCountries ??= _countries?.Take(1).ToList();
    }

    async Task RemoveCountry(Country country)
    {
        _selectedCountries?.Remove(country);
        if (_selectedCountries?.Count == 0) _selectedCountries = null;
        _countries.Remove(country);
        await _httpClient.DeleteAsync($"/country?CountryId={country.Id}");
        await _countriesGrid.Reload();
    }

    async Task SaveCountry(Country country)
    {
        await _countriesGrid.UpdateRow(country);
    }

    async Task CountryUpdated(Country country)
    {
        var saveCountryRequest = new SaveCountryRequest { Country = country };
        await _httpClient.PostAsJsonAsync($"/country", saveCountryRequest);
        _countriesGrid.CancelEditRow(country);
    }
}

Did you try changing the data type from var to Country in NewCountry() method ?

No, I did not try this. I have been programming in C# for years and I have not seen this ever make a difference. Is there a reason that this is different?

Hey @bjrichardson999 ,

There is indeed problem with InsertRow() method when the DataGrid is bound using LoadData. The only workaround we can offer is to insert new item in the collection assigned to Data property instead. You can check also the code generated by Radzen inline edit page template for reference.

It's over a year later and this bug still seems to be there. if you use LoadData (which is kinda needed to do proper database handled paging), you can't insert a new record if there are no records in the list.
Please do fix this issue!

Hey @Marcel_Isler,

We accept pull requests! Here is also our in-line editing demo with LoadData, everything works normally:

@page "/datagrid-inline-edit"

@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind
@using Microsoft.EntityFrameworkCore
@using System.Linq.Dynamic.Core

@inherits DbContextPage

<RadzenText TextStyle="TextStyle.H3" TagName="TagName.H1" Class="my-4">
    DataGrid InLine Editing
</RadzenText>
<RadzenText TextStyle="TextStyle.Body1" Class="my-4">
    This page demonstrates how to configure the Razden Blazor DataGrid for inline editing.
</RadzenText>

<RadzenExample Name="DataGrid" Source="DataGridInLineEdit" Heading="false">
    <RadzenButton ButtonStyle="ButtonStyle.Success" Icon="add_circle_outline" Class="mt-2 mb-4" Text="Add New Order" Click="@InsertRow" Disabled=@(orderToInsert != null) />
    <RadzenDataGrid @ref="ordersGrid" AllowAlternatingRows="false" AllowFiltering="true" AllowPaging="true" PageSize="5" AllowSorting="true" EditMode="DataGridEditMode.Single"
                Count="@count" Data="@orders" LoadData="@LoadData" TItem="Order" RowUpdate="@OnUpdateRow" RowCreate="@OnCreateRow">
        <Columns>
            <RadzenDataGridColumn TItem="Order" Property="OrderID" Title="Order ID" Width="120px" />
            <RadzenDataGridColumn TItem="Order" Property="Customer.CompanyName" Title="Customer" Width="280px" >
                <EditTemplate Context="order">
                    <RadzenDropDown @bind-Value="order.CustomerID" Data="@customers" TextProperty="CompanyName" ValueProperty="CustomerID" Style="width:100%; display: block;" />
                </EditTemplate>
            </RadzenDataGridColumn>
            <RadzenDataGridColumn TItem="Order" Property="Employee.LastName" Title="Employee" Width="220px">
                <Template Context="order">
                    <RadzenImage Path="@order.Employee?.Photo" style="width: 32px; height: 32px; border-radius: 16px; margin-right: 6px;" />
                    @order.Employee?.FirstName @order.Employee?.LastName
                </Template>
                <EditTemplate Context="order">
                    <RadzenDropDown @bind-Value="order.EmployeeID" Data="@employees" ValueProperty="EmployeeID" Style="width:100%; display: block;">
                        <Template>
                            <RadzenImage Path="@context.Photo" style="width: 20px; height: 20px; border-radius: 16px; margin-right: 6px;" />
                            @context.FirstName @context.LastName
                        </Template>
                    </RadzenDropDown>
                </EditTemplate>
            </RadzenDataGridColumn>
            <RadzenDataGridColumn TItem="Order" Property="OrderDate" Title="Order Date" Width="200px">
                <Template Context="order">
                    @String.Format("{0:d}", order.OrderDate)
                </Template>
                <EditTemplate Context="order">
                    <RadzenDatePicker @bind-Value="order.OrderDate" Style="width:100%" />
                </EditTemplate>
            </RadzenDataGridColumn>
            <RadzenDataGridColumn TItem="Order" Property="Freight" Title="Freight">
                <Template Context="order">
                    @String.Format(new System.Globalization.CultureInfo("en-US"), "{0:C}", order.Freight)
                </Template>
                <EditTemplate Context="order">
                    <RadzenNumeric @bind-Value="order.Freight" Style="width:100%" />
                </EditTemplate>
            </RadzenDataGridColumn>
            <RadzenDataGridColumn TItem="Order" Property="ShipName" Title="Ship Name">
                <EditTemplate Context="order">
                    <RadzenTextBox @bind-Value="order.ShipName" Style="width:100%; display: block" Name="ShipName" />
                    <RadzenRequiredValidator Text="ShipName is required" Component="ShipName" Popup="true" />
                </EditTemplate>
            </RadzenDataGridColumn>
            <RadzenDataGridColumn TItem="Order" Context="order" Filterable="false" Sortable="false" TextAlign="TextAlign.Right" Width="156px">
                <Template Context="order">
                    <RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" Click="@(args => EditRow(order))" @onclick:stopPropagation="true">
                    </RadzenButton>
                    <RadzenButton ButtonStyle="ButtonStyle.Danger" Icon="delete" Variant="Variant.Flat" Shade="Shade.Lighter" Size="ButtonSize.Medium" Class="my-1 ms-1" Click="@(args => DeleteRow(order))"  @onclick:stopPropagation="true">
                    </RadzenButton>
                </Template>
                <EditTemplate Context="order">
                    <RadzenButton Icon="check" ButtonStyle="ButtonStyle.Success" Variant="Variant.Flat" Size="ButtonSize.Medium" Click="@((args) => SaveRow(order))">
                    </RadzenButton>
                    <RadzenButton Icon="close" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" Class="my-1 ms-1" Click="@((args) => CancelEdit(order))">
                    </RadzenButton>
                    <RadzenButton ButtonStyle="ButtonStyle.Danger" Icon="delete" Variant="Variant.Flat" Shade="Shade.Lighter" Size="ButtonSize.Medium" Class="my-1 ms-1" Click="@(args => DeleteRow(order))">
                    </RadzenButton>
                </EditTemplate>
            </RadzenDataGridColumn>
        </Columns>
    </RadzenDataGrid>
</RadzenExample>

@code {
    RadzenDataGrid<Order> ordersGrid;
    IList<Order> orders;
    IEnumerable<Customer> customers;
    IEnumerable<Employee> employees;

    protected override void OnInitialized()
    {
        base.OnInitialized();

        customers = dbContext.Customers.ToList();
        employees = dbContext.Employees.ToList();

        // For demo purposes only
        //orders = dbContext.Orders.Include("Customer").Include("Employee").ToList();

        // For production
        //orders = dbContext.Orders.Include("Customer").Include("Employee");
    }

    async Task EditRow(Order order)
    {
        await ordersGrid.EditRow(order);
    }

    void OnUpdateRow(Order order)
    {
        if (order == orderToInsert)
        {
            orderToInsert = null;
        }

        dbContext.Update(order);

        // For demo purposes only
        order.Customer = dbContext.Customers.Find(order.CustomerID);
        order.Employee = dbContext.Employees.Find(order.EmployeeID);

        // For production
        //dbContext.SaveChanges();
    }

    async Task SaveRow(Order order)
    {
        if (order == orderToInsert)
        {
            orderToInsert = null;
        }

        await ordersGrid.UpdateRow(order);
    }

    void CancelEdit(Order order)
    {
        if (order == orderToInsert)
        {
            orderToInsert = null;
        }

        ordersGrid.CancelEditRow(order);

        // For production
        var orderEntry = dbContext.Entry(order);
        if (orderEntry.State == EntityState.Modified)
        {
            orderEntry.CurrentValues.SetValues(orderEntry.OriginalValues);
            orderEntry.State = EntityState.Unchanged;
        }
    }

    async Task DeleteRow(Order order)
    {
        if (order == orderToInsert)
        {
            orderToInsert = null;
        }

        if (orders.Contains(order))
        {
            dbContext.Remove<Order>(order);

            // For demo purposes only
            orders.Remove(order);

            // For production
            //dbContext.SaveChanges();

            await ordersGrid.Reload();
        }
        else
        {
            ordersGrid.CancelEditRow(order);
        }
    }

    Order orderToInsert;

    async Task InsertRow()
    {
        orderToInsert = new Order();
        await ordersGrid.InsertRow(orderToInsert);
    }

    void OnCreateRow(Order order)
    {
        dbContext.Add(order);

        // For demo purposes only
        order.Customer = dbContext.Customers.Find(order.CustomerID);
        order.Employee = dbContext.Employees.Find(order.EmployeeID);

        // For production
        //dbContext.SaveChanges();
    }

    int count;
    void LoadData(LoadDataArgs args)
    {
        var query = dbContext.Orders.Include("Customer").Include("Employee").AsQueryable();

        if (!string.IsNullOrEmpty(args.Filter))
        {
            query = query.Where(args.Filter);
        }

        if (!string.IsNullOrEmpty(args.OrderBy))
        {
            query = query.OrderBy(args.OrderBy);
        }

        count = query.Count();

        orders = query.Skip(args.Skip.Value).Take(args.Top.Value).ToList();
    }
}

The demo is ok for small datasets where you can load all the data into the frontend... the DataLoad method is necessary when that dataset is too large and you should only load the data for the current page.
I guess I'll take a look at what the code in the Repo looks like.

Radzen DataGrid works with IQueryble so the data source size doesn’t matter plus I’ve reworked the demo with LoadData just to test. Check the code and my screenshot.

IQueryable works great if the Blazor project connects straight to the DB... in our case though it connects to a backend through MVC controllers and then from there to the DB.

I was able to work around it by making the following changes:
In InsertRow, I check if we already do have data, if not, I add the data to the collection by hand and then do an edit on it:

public async Task InsertRow(string currentUserName)
{
    salaryCategoryToInsert = new SalaryCategory();
    salaryCategoryToInsert.LastUpdatedOn = DateTime.Now;
    salaryCategoryToInsert.LastUpdatedBy = currentUserName;

    if (salaryCategories.Count > 0)
    {
        await salaryCategoryGrid.InsertRow(salaryCategoryToInsert);
    }
    else
    {
        salaryCategories.Add(salaryCategoryToInsert);
        count = 1;
        await salaryCategoryGrid.EditRow(salaryCategoryToInsert);
    }
}

and then in SaveRow, I check if the Id is 0, if so, I call the create, otherwise I call the update method.

public async Task SaveRow(SalaryCategory salaryCategory)
{
    if (salaryCategory == salaryCategoryToInsert)
    {
        salaryCategoryToInsert = null;
    }

    if (salaryCategory.SalaryCategoryId == 0)
    {
        int salaryCategoryId = await apiService.CreateNewSalaryCategoryAsync(salaryCategory);
        salaryCategory.SalaryCategoryId = salaryCategoryId;
        salaryCategoryGrid.CancelEditRow(salaryCategory);
        await salaryCategoryGrid.Reload();
    }
    else
    {
        await salaryCategoryGrid.UpdateRow(salaryCategory);
    }
}

and finally, in CancelEdit, I reload the grid to make sure the data I've added by hand is being removed

protected async Task CancelEdit(SalaryCategory salaryCategory)
{
    if (salaryCategory == salaryCategoryToInsert)
    {
        salaryCategoryToInsert = null;
    }

    salaryCategoryGrid.CancelEditRow(salaryCategory);
    await salaryCategoryGrid.Reload();
}