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上下文无法识别对象已修改,进而无法恢复原始值。
原因分析
- 上下文生命周期不匹配:每次调用
Cancel都会创建新的DbContext实例,而当前编辑的OrderDetail对象来自之前查询的上下文(或无跟踪查询),和新上下文无关联,因此状态为Detached。 - 无跟踪查询:如果
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
相关产品推荐
相关产品推荐

