You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server中Radzen DataGrid取消更新时实体状态为Detached的问题

Blazor Server中Radzen DataGrid取消编辑后无法恢复原始值的问题

我在Blazor Server应用里使用Radzen DataGrid实现行编辑功能,遇到的问题是:用户编辑行时点击取消按钮,表格没能恢复显示原始值,仍保留修改后的内容。


现有代码

仓储层取消逻辑

public class OrderDetailRepository : IOrderDetailRepository
{
    private readonly IDbContextFactory<IMSContext> _db;

    public OrderDetailRepository(IDbContextFactory<IMSContext> db)
    {
        _db = db;
    }
    // 其他方法省略...

    public void Cancel(OrderDetail orderDetail)
    {
        using var ctx = _db.CreateDbContext();

        var orderDetailEntry = ctx.Entry(orderDetail);
        if (orderDetailEntry.State == EntityState.Modified)
        {
            orderDetailEntry.CurrentValues.SetValues(orderDetailEntry.OriginalValues);
            orderDetailEntry.State = EntityState.Unchanged;
        }
    }
}

用例层代码

public class CancelOrderDetailUseCase : ICancelOrderDetailUseCase
{
    private readonly IOrderDetailRepository _orderDetailRepository;

    public CancelOrderDetailUseCase(IOrderDetailRepository orderDetailRepository)
    {
        this._orderDetailRepository = orderDetailRepository;
    }

    public void Execute(OrderDetail orderDetail)
    {
        _orderDetailRepository.Cancel(orderDetail);
    }
}

页面组件代码

Radzen DataGrid简化代码

<RadzenDataGrid @ref="_grid" AllowFiltering="true" AllowPaging="true" PageSize="20" AllowSorting="true" RowClick="RowClick" ExpandMode="DataGridExpandMode.Single"
        Data="@_orders" TItem="Order" EditMode="DataGridEditMode.Single" RowUpdate="@OnUpdateRow" RowCreate="@OnCreateRow" @bind-Value="@SelectedOrders"
        ShowExpandColumn="false" ShowPagingSummary="true" AllowColumnResize="true">
<Template Context="order">
    <RadzenCard Style="margin-bottom: 20px">
        Customer:
        <b>@order?.Customer?.Name</b>
    </RadzenCard>
    <RadzenTabs>
        <Tabs>
            <RadzenTabsItem Text="Order Details">
               <RadzenDataGrid @ref="_gridDetail" AllowFiltering="@(_detailToInsert == null)" AllowPaging="true" PageSize="15" AllowSorting="@(_detailToInsert == null)" Data="@order.OrderDetails" TItem="OrderDetail" EditMode="DataGridEditMode.Multiple" RowUpdate="@OnUpdateRowDetail" RowCreate="@OnCreateRowDetail" AllowColumnResize="true" AllowColumnPicking="true" ShowPagingSummary="true" ColumnWidth="150px" Density="Density.Compact">
                    <Columns>
<RadzenDataGridColumn TItem="OrderDetail" Property="CustomerOrderNumber" Title="Customer Order" OrderIndex="20">
        <EditTemplate Context="orderDetail">
            <RadzenTextBox @bind-Value="orderDetail.CustomerOrderNumber" Style="width: 100%; display: block" Name="CustomerOrderNumber" />
        </EditTemplate>
    </RadzenDataGridColumn>
    <AuthorizeView Roles="Administrators">
        <RadzenDataGridColumn TItem="OrderDetail" Property="PaymentStatus" Title="Payment Status" OrderIndex="21">
            <EditTemplate Context="orderDetail">
                <RadzenDropDown AllowClear="true" TValue="string" @bind-Value="orderDetail.PaymentStatus" Class="w-100" Data=@paymentStatus Name="PaymentStatus" />
            </EditTemplate>
        </RadzenDataGridColumn>
    </AuthorizeView>
    <RadzenDataGridColumn TItem="OrderDetail" Property="OrderId" Title="Order Id" OrderIndex="22" />
    <RadzenDataGridColumn TItem="OrderDetail" Context="orderDetail" Filterable="false" Sortable="false" TextAlign="TextAlign.Center" Width="200px" OrderIndex="22">
        <Template Context="detail">
            <RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Primary" Class="m-1" Click="@(args => EditRowDetail(detail))" @onclick:stopPropagation="true" Size="ButtonSize.Small">
            </RadzenButton>
        </Template>
        <EditTemplate Context="detail">
            <RadzenButton Icon="check" ButtonStyle="ButtonStyle.Primary" Class="m-1" Click="@(args => SaveRowDetail(detail))" Size="ButtonSize.Small">
            </RadzenButton>
            <RadzenButton Icon="close" ButtonStyle="ButtonStyle.Light" Class="m-1" Click="@(args => CancelEditDetail(detail))" Size="ButtonSize.Small">
            </RadzenButton>
            <RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Class="m-1" Click="@(args => ShowInlineDialog(detail))" Size="ButtonSize.Small">
            </RadzenButton>
        </EditTemplate>
    </RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>

页面初始化与编辑方法

Enumerable<Order?> _orders = new List<Order?>();
RadzenDataGrid<Order?> _grid;
RadzenDataGrid<OrderDetail> _gridDetail;
    
protected override async Task OnInitializedAsync()
{
    user = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User;

    if (!user.Identity.IsAuthenticated)
    {
        NavigationManager.NavigateTo("/Identity/Account/Login", false);
    }
    if (DataLoading)
    {
        return;
    }

    try
    {
        DataLoading = true;
        await UpdateOrdersIsCompletedUseCase.ExecuteAsync();
        await UpdateOrdersIsContinuesUseCase.ExecuteAsync();
        _vendors = await ViewAllVendorsUseCase.ExecuteAsync();
        _customers = await ViewAllCustomersUseCase.ExecuteAsync();
        _orders = await ViewAllOrdersUseCase.ExecuteAsync(user);
        
    }
    catch (Exception e)
    {
        throw;
    }
    finally
    {
        DataLoading = false;
    }
    SelectedOrders = new List<Order?> { _orders.FirstOrDefault() };
    SelectedStatus = "";
}

async Task EditRowDetail(OrderDetail orderDetail)
{
    await _gridDetail.EditRow(orderDetail);
}

private void CancelEditDetail(OrderDetail orderDetail)
{
    if (orderDetail == _detailToInsert)
    {
        _detailToInsert = null;
    }

    _gridDetail.CancelEditRow(orderDetail);
    CancelOrderDetailUseCase.Execute(orderDetail);
}

问题核心

调试时发现,Cancel方法中orderDetailEntry.State的状态是Detached,而非预期的Modified,导致EF Core上下文无法识别对象已修改,进而无法恢复原始值。


原因分析

  1. 上下文生命周期不匹配:每次调用Cancel都会创建新的DbContext实例,而当前编辑的OrderDetail对象来自之前查询的上下文(或无跟踪查询),和新上下文无关联,因此状态为Detached。
  2. 无跟踪查询:如果ViewAllOrdersUseCase中的查询使用了AsNoTracking,或者查询后上下文已被释放,OrderDetail对象不会被任何上下文跟踪,自然无法检测到Modified状态。

解决方案

方案1:从数据库重新加载原始值

修改仓储层Cancel方法,直接查询数据库获取原始值覆盖当前对象:

public void Cancel(OrderDetail orderDetail)
{
    using var ctx = _db.CreateDbContext();
    var originalDetail = ctx.OrderDetails.Find(orderDetail.Id);
    if (originalDetail != null)
    {
        ctx.Entry(orderDetail).CurrentValues.SetValues(originalDetail);
    }
}

方案2:编辑前保存原始值副本

在开启编辑时为对象创建深拷贝,取消时用副本覆盖编辑后的内容:

// 页面组件中添加字典存储原始副本
private Dictionary<int, OrderDetail> _originalDetails = new Dictionary<int, OrderDetail>();

async Task EditRowDetail(OrderDetail orderDetail)
{
    // 序列化实现深拷贝
    var original = JsonSerializer.Deserialize<OrderDetail>(JsonSerializer.Serialize(orderDetail));
    if (original != null)
    {
        _originalDetails[orderDetail.Id] = original;
    }
    await _gridDetail.EditRow(orderDetail);
}

private void CancelEditDetail(OrderDetail orderDetail)
{
    if (orderDetail == _detailToInsert)
    {
        _detailToInsert = null;
    }

    if (_originalDetails.TryGetValue(orderDetail.Id, out var original))
    {
        // 复制原始属性值
        orderDetail.CustomerOrderNumber = original.CustomerOrderNumber;
        orderDetail.PaymentStatus = original.PaymentStatus;
        // 其他属性依次复制
        _originalDetails.Remove(orderDetail.Id);
    }

    _gridDetail.CancelEditRow(orderDetail);
}

方案3:利用Radzen内置机制强制刷新

Radzen的CancelEditRow本身应恢复原始状态,若界面未更新,可调用StateHasChanged()强制刷新:

private void CancelEditDetail(OrderDetail orderDetail)
{
    if (orderDetail == _detailToInsert)
    {
        _detailToInsert = null;
    }

    _gridDetail.CancelEditRow(orderDetail);
    StateHasChanged();
}

推荐方案

优先选择方案1或方案3:

  • 方案1适合需要与数据库最新值同步的场景;
  • 方案3适合无需额外数据库查询、追求性能的场景,尤其适用于编辑期间数据不会被其他用户修改的情况。

内容的提问来源于stack exchange,提问作者raysefo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 23:10:43