Example T1259172
Visible to All Users

DevExpress Blazor TreeList – Implement batch data editing using Entity Framework Core

This example introduces batch data editing support when using Microsoft Entity Framework Core in your DevExpress-powered Blazor app.

Our sample uses DbContext to obtain and update DevExpress Blazor TreeList data. When a user creates a new row or modifies/deletes an existing row, a DbContext instance tracks changes made to underlying data. End users can press Save to record all changes made within this context or press Cancel to dispose the context and discard accumulated changes.

The CustomizeElement event handler uses the DbContext.ChangeTracker property to identify and highlight modified cells.

Files to Review

Documentation

More Examples

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

Example Code

TreeListBatchEditing/Components/Pages/Index.razor
Razor
@page "/" @using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore.ChangeTracking @using TreeListBatchEditing.Services; @using TreeListBatchEditing.Data; @implements IDisposable @rendermode InteractiveServer @inject CitiesService CitiesService @inject IDbContextFactory<CitiesContext> CitiesContextFactory <div> <DxTreeList @ref="TreeList" Data="@Data" KeyFieldName="ID" ParentKeyFieldName="ParentID" HasChildrenFieldName="HasChildren" AutoExpandAllNodes="true" ValidationEnabled="false" EditMode="TreeListEditMode.EditCell" EditModelSaving="TreeList_EditModelSaving" CustomizeElement="TreeList_CustomizeElement" CustomizeEditModel="TreeList_CustomizeEditModel"> <Columns> <DxTreeListDataColumn Caption="Location" FieldName="Name" /> <DxTreeListDataColumn FieldName="CityType" /> <DxTreeListDataColumn FieldName="Year" DisplayFormat="d" /> <DxTreeListDataColumn FieldName="RecordType" /> <DxTreeListDataColumn FieldName="Population" /> <DxTreeListCommandColumn Width="90px"> <HeaderTemplate> <DxButton IconCssClass="treelist-icon treelist-icon-add" RenderStyle="ButtonRenderStyle.Link" aria-label="New" Click="@(() => TreeList.StartEditNewRowAsync())" /> </HeaderTemplate> <CellDisplayTemplate> <div class="treelist-cell-align-center"> <DxButton IconCssClass="treelist-icon treelist-icon-add" RenderStyle="ButtonRenderStyle.Link" aria-label="New" Click="@(() => TreeList.StartEditNewRowAsync(context.VisibleIndex))" /> <DxButton IconCssClass="treelist-icon treelist-icon-delete" RenderStyle="ButtonRenderStyle.Link" aria-label="Delete" Click="@(() => Delete_Click((Location)context.DataItem))" /> </div> </CellDisplayTemplate> <CellEditTemplate> <div class="treelist-cell-align-center"> <DxButton Enabled="false" CssClass="treelist-disabled-button" IconCssClass="treelist-icon treelist-icon-add" aria-label="New" RenderStyle="ButtonRenderStyle.Link" /> <DxButton Enabled="false" CssClass="treelist-disabled-button" IconCssClass="treelist-icon treelist-icon-delete" aria-label="Delete" RenderStyle="ButtonRenderStyle.Link" /> </div> </CellEditTemplate> </DxTreeListCommandColumn> </Columns> <ToolbarTemplate> <DxToolbar ItemRenderStyleMode="ToolbarRenderStyleMode.Plain"> <DxToolbarItem Text="Save" Click="Save_Click" IconCssClass="treelist-toolbar-save" Enabled="BatchItemsEnabled" /> <DxToolbarItem Text="Cancel" Click="Cancel_Click" IconCssClass="treelist-toolbar-cancel" Enabled="BatchItemsEnabled" /> </DxToolbar> </ToolbarTemplate> </DxTreeList> </div> @code { ITreeList TreeList { get; set; } IList<Location> Data { get; set; } CitiesContext DbContext { get; set; } bool BatchItemsEnabled => DbContext.ChangeTracker.HasChanges() || TreeList.IsEditing(); protected override async Task OnInitializedAsync() { await ReloadData(); } void IDisposable.Dispose() { DbContext?.Dispose(); } async Task New_Click() => await TreeList.StartEditNewRowAsync(); async Task Save_Click() => await DbContext.SaveChangesAsync(); async Task Cancel_Click() => await ReloadData(); void Delete_Click(Location dataItem) { DbContext.Remove(dataItem); Data.Remove(dataItem); TreeList.Reload(); } void TreeList_CustomizeElement(TreeListCustomizeElementEventArgs e) { if (e.ElementType == TreeListElementType.DataCell) { var employee = (Location)TreeList.GetDataItem(e.VisibleIndex); var column = (ITreeListDataColumn)e.Column; if (TryGetChangedEntityEntry(employee, out var entityEntry)) { if (entityEntry.State == EntityState.Added || entityEntry.Property(column.FieldName).IsModified) e.CssClass = "treelist-modified-cell"; } } } void TreeList_CustomizeEditModel(TreeListCustomizeEditModelEventArgs e) { if (e.IsNew) { var newLocation = (Location)e.EditModel; newLocation.ID = Data.Max(x => x.ID) + 1; if (e.ParentDataItem != null) newLocation.ParentID = ((Location)e.ParentDataItem).ID; } else e.EditModel = e.DataItem; } async Task TreeList_EditModelSaving(TreeListEditModelSavingEventArgs e) { if (e.IsNew) { var editModel = (Location)e.EditModel; await DbContext.AddAsync(editModel); Data.Add(editModel); } } bool TryGetChangedEntityEntry<T>(T entity, out EntityEntry<T> entry) where T : class { entry = null; if (!DbContext.ChangeTracker.HasChanges()) return false; entry = DbContext.ChangeTracker.Entries<T>().FirstOrDefault(x => x.Entity == entity); return entry != null && entry.State != EntityState.Unchanged && entry.State != EntityState.Detached; } async Task ReloadData() { DbContext?.Dispose(); DbContext = await CitiesContextFactory.CreateDbContextAsync(); Data = await DbContext.Locations.ToListAsync(); TreeList?.Reload(); } }
TreeListBatchEditing/Components/Pages/Index.razor.css
CSS
::deep .treelist-toolbar-save, ::deep .treelist-toolbar-cancel { background-size: contain; mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat; background-position: center center; background-color: currentColor; height: 16px; width: 16px; opacity: 0.7; } ::deep .treelist-toolbar-save { -webkit-mask-image: url("images/icons/save.svg"); mask-image: url("images/icons/save.svg"); } ::deep .treelist-toolbar-cancel { -webkit-mask-image: url("images/icons/undo.svg"); mask-image: url("images/icons/undo.svg"); } ::deep .treelist-modified-cell { background-color: rgba(var(--bs-primary-rgb), 0.15) !important; } ::deep .treelist-icon { display: inline-block; width: 1rem; height: 1rem; min-width: 1rem; min-height: 1rem; background-size: contain; mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat; background-position: center center; background-color: currentColor; vertical-align: middle; } ::deep .treelist-icon-add { -webkit-mask-image: url("../images/icons/add.svg"); mask-image: url("../images/icons/add.svg"); } ::deep .treelist-icon-delete { -webkit-mask-image: url("../images/icons/delete.svg"); mask-image: url("../images/icons/delete.svg"); } ::deep .treelist-cell-align-center { display: flex; justify-content: center; align-items: center; margin-top: -4px; margin-bottom: -4px; }

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.