Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
af964e5
wip: 临时提交
ArgoZhang Apr 17, 2026
8e7dd05
doc: 更新示例
ArgoZhang Apr 17, 2026
e0ed3e0
wip: 临时提交
ArgoZhang Apr 17, 2026
ffcd7bc
refactor: 更新获得 Text 属性值逻辑未设置 Text 时使用 FieldName 值
ArgoZhang Apr 17, 2026
737d112
refactor: 增加 IsDeletedOrDetached 扩展方法
ArgoZhang Apr 17, 2026
eabd08b
refactor: 更新动态类型获得值逻辑
ArgoZhang Apr 17, 2026
f404d8a
refactor: 精简代码提高性能
ArgoZhang Apr 18, 2026
d8ced79
refactor: 重构 DataTableDynamicContext 逻辑
ArgoZhang Apr 18, 2026
c464858
feat: 增加新建数据处理逻辑防止 DBNull 值
ArgoZhang Apr 18, 2026
143e764
refactor: 增加可为空数据类型兼容处理
ArgoZhang Apr 18, 2026
613d7ce
doc: 恢复示例
ArgoZhang Apr 18, 2026
b37bfd6
refactor: 增加重新查询逻辑
ArgoZhang Apr 18, 2026
ee5b12e
feat: 增加数据撤销功能支持
ArgoZhang Apr 18, 2026
b3d8de8
feat: 创建组件增加动态数据判断
ArgoZhang Apr 18, 2026
292c717
refactor: 增加 CreateValueChangedLambda 提高可读性
ArgoZhang Apr 18, 2026
7750e8f
feat: 增加 GenerateOnValueChanged 方法
ArgoZhang Apr 18, 2026
83756ba
refactor: 删除操作后增加重新查询逻辑
ArgoZhang Apr 18, 2026
b9aaad0
refactor: 优化性能
ArgoZhang Apr 18, 2026
38898f6
feat: 增加可见列控制逻辑
ArgoZhang Apr 18, 2026
94f5006
chore(Table): bump version 10.5.1-beta09
ArgoZhang Apr 18, 2026
0002dd9
Merge branch 'main' into fix-table-mmo
ArgoZhang Apr 25, 2026
8a1082a
refactor: 恢复部分代码
ArgoZhang Apr 25, 2026
ca211af
revert: 撤销部分代码
ArgoZhang Apr 25, 2026
f8285db
revert: 撤销部分代码
ArgoZhang Apr 25, 2026
8f83b24
revert: 撤销部分代码更改
ArgoZhang Apr 25, 2026
332b160
revert: 撤销部分代码
ArgoZhang Apr 25, 2026
e175eba
revert: 恢复部分代码
ArgoZhang Apr 25, 2026
af13c77
revert: 撤销部分代码
ArgoZhang Apr 25, 2026
00a36ca
revert: 撤销代码
ArgoZhang Apr 25, 2026
60d7de1
revert: 撤销 Cancel/Accept 方法
ArgoZhang Apr 25, 2026
2366d12
refactor: 撤销更改
ArgoZhang Apr 25, 2026
2e1c546
revert: 撤销更改
ArgoZhang Apr 25, 2026
b74fc7d
refactor: 恢复代码
ArgoZhang Apr 25, 2026
29ef028
refactor: 精简代码
ArgoZhang Apr 25, 2026
ee0cc79
doc: 删除注释
ArgoZhang Apr 25, 2026
8650bfa
refactor: 更新示例
ArgoZhang Apr 25, 2026
08a9a60
test: 更新单元测试
ArgoZhang Apr 25, 2026
2805305
refactor: 恢复原逻辑
ArgoZhang Apr 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
Name="Edit">
<section ignore>@((MarkupString)Localizer["TablesDynamicEditDescription"].Value)</section>
<Table TItem="DynamicObject" DynamicContext="DataTableDynamicContext" ModelEqualityComparer="ModelEqualityComparer"
IsMultipleSelect="true" IsBordered="true" IsStriped="true" @bind-SelectedRows="SelectedItems"
IsMultipleSelect="true" IsBordered="true" IsStriped="true" @bind-SelectedRows="SelectedItems" IsKeepSelectedRowAfterAdd="true"
ShowToolbar="true" ShowExtendButtons="true" />
<div class="mt-3">
@foreach (var item in SelectedItems)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ protected override void OnInitialized()

// 初始化 DataTable
InitDataTable();

// 初始化分页表格
InitPageDataTable();
}
Expand Down
22 changes: 13 additions & 9 deletions src/BootstrapBlazor/Components/Table/Table.razor.Toolbar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,9 @@ public async Task AddAsync()
// <para lang="zh">数据源为 DataTable 新建后重建行与列</para>
// <para lang="en">Data source is DataTable, rebuild rows and columns after adding</para>
await DynamicContext.AddAsync(SelectedRows.OfType<IDynamicObject>());
ResetDynamicContext();

// 重新查询数据
await QueryAsync(false);

if (!IsKeepSelectedRowAfterAdd)
{
Expand Down Expand Up @@ -664,8 +666,6 @@ public async Task EditAsync()
}
EditModalTitleString = EditModalTitle;

// <para lang="zh">显示编辑框</para>
// <para lang="en">Show Edit Dialog</para>
if (EditMode == EditMode.Popup)
{
await ShowEditDialog(ItemChangedType.Update);
Expand All @@ -691,8 +691,6 @@ public async Task EditAsync()
}
else
{
// <para lang="zh">不选或者多选弹窗提示</para>
// <para lang="en">Toast if not selected or multiple selected</para>
var content = SelectedRows.Count == 0 ? EditButtonToastNotSelectContent : EditButtonToastMoreSelectContent;
await ShowToastAsync(EditButtonToastTitle, content);
}
Expand Down Expand Up @@ -958,6 +956,8 @@ private void AppendOptions(ITableEditDialogOption<TItem> option, ItemChangedType
protected async Task ShowEditDialog(ItemChangedType changedType)
{
var saved = false;

// 用于判断是否未保存数据直接点击关闭取消数据保存操作
var triggerFromSave = false;
var option = new EditDialogOption<TItem>()
{
Expand All @@ -983,7 +983,9 @@ protected async Task ShowEditDialog(ItemChangedType changedType)
OnEditAsync = async context =>
{
saved = await OnSaveEditCallbackAsync(context, changedType);
triggerFromSave = true;

// 已保存数据
triggerFromSave = saved;
return saved;
}
};
Expand Down Expand Up @@ -1043,8 +1045,8 @@ private async Task OnCloseEditDialogCallbackAsync(bool saved)

if (!saved)
{
var d = DataService ?? InjectDataService;
if (d is IEntityFrameworkCoreDataService ef)
var dataService = DataService ?? InjectDataService;
if (dataService is IEntityFrameworkCoreDataService ef)
{
// EFCore
await ToggleLoading(true);
Expand Down Expand Up @@ -1133,11 +1135,13 @@ protected async Task DeleteAsync()
if (DynamicContext != null)
{
await DynamicContext.DeleteAsync(SelectedRows.OfType<IDynamicObject>());
ResetDynamicContext();

// 触发删除回调方法
await TriggerDeleteCallback();

// 重新查询数据
await QueryAsync(SelectedRowsChanged.HasDelegate);

SelectedRows.Clear();
await OnSelectedRowsChanged();
}
Expand Down
126 changes: 69 additions & 57 deletions src/BootstrapBlazor/Dynamic/DataTableDynamicContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,17 @@ namespace BootstrapBlazor.Components;
public class DataTableDynamicContext : DynamicObjectContext
{
/// <summary>
/// <para lang="zh">获得/设置 相关联的 DataTable 实例</para>
/// <para lang="en">Gets or sets the associated DataTable instance</para>
/// <para lang="zh">获得 相关联的 DataTable 实例</para>
/// <para lang="en">Gets the associated DataTable instance</para>
/// </summary>
[NotNull]
public DataTable? DataTable { get; set; }

private Type DynamicObjectType { get; }

private IEnumerable<ITableColumn> Columns { get; }

private List<IDynamicObject>? Items { get; set; }

private Action<DataTableDynamicContext, ITableColumn>? AddAttributesCallback { get; set; }
public DataTable DataTable { get; }

/// <summary>
/// <para lang="zh">获得/设置 是否启用内部缓存 默认 true 启用</para>
/// <para lang="en">Gets or sets whether to enable internal caching. Default is true.</para>
/// </summary>
public bool UseCache { get; set; } = true;

/// <summary>
/// <para lang="zh">负责将 DataRow 与 Items 关联起来方便查找提高效率</para>
/// <para lang="en">Responsible for associating DataRow with Items to facilitate lookup and improve efficiency</para>
/// </summary>
private readonly ConcurrentDictionary<Guid, (IDynamicObject DynamicObject, DataRow Row)> _dataCache = new();

/// <summary>
/// <para lang="zh">添加行回调委托</para>
/// <para lang="en">Add row callback delegate</para>
Expand All @@ -54,6 +39,17 @@ public class DataTableDynamicContext : DynamicObjectContext
/// </summary>
public Func<IEnumerable<IDynamicObject>, Task<bool>>? OnDeleteAsync { get; set; }

/// <summary>
/// <para lang="zh">负责将 DataRow 与 Items 关联起来方便查找提高效率</para>
/// <para lang="en">Responsible for associating DataRow with Items to facilitate lookup and improve efficiency</para>
/// </summary>
private readonly ConcurrentDictionary<Guid, DataTableDynamicObject> _dataCache = new();

private readonly List<ITableColumn> _columns;
private readonly Action<DataTableDynamicContext, ITableColumn>? _addAttributesCallback;
private List<IDynamicObject>? _items;
private Type _dynamicObjectType;

/// <summary>
/// <para lang="zh">构造函数</para>
/// <para lang="en">Constructor</para>
Expand All @@ -74,17 +70,18 @@ public class DataTableDynamicContext : DynamicObjectContext
public DataTableDynamicContext(DataTable table, Action<DataTableDynamicContext, ITableColumn>? addAttributesCallback = null, IEnumerable<string>? invisibleColumns = null, IEnumerable<string>? shownColumns = null, IEnumerable<string>? hiddenColumns = null)
{
DataTable = table;
AddAttributesCallback = addAttributesCallback;
table.AcceptChanges();

_addAttributesCallback = addAttributesCallback;
OnValueChanged = OnCellValueChanged;

// 获得 DataTable 列信息转换为 ITableColumn 集合
var cols = InternalGetColumns();

// 生成动态类型 DataTableDynamicObjectType 继承 DataTableDynamicObject 并添加属性
DynamicObjectType = EmitHelper.CreateTypeByName($"BootstrapBlazor_{nameof(DataTableDynamicContext)}_{GetHashCode()}", cols, typeof(DataTableDynamicObject), OnColumnCreating);
_dynamicObjectType = EmitHelper.CreateTypeByName($"BootstrapBlazor_{nameof(DataTableDynamicContext)}_{GetHashCode()}", cols, typeof(DataTableDynamicObject), OnColumnCreating);

// 获得显示列
Columns = Utility.GetTableColumns(DynamicObjectType, cols).Where(col => GetShownColumns(col, invisibleColumns, shownColumns, hiddenColumns));
_columns = Utility.GetTableColumns(_dynamicObjectType, cols).Where(col => GetShownColumns(col, invisibleColumns, shownColumns, hiddenColumns)).ToList();
}

private static bool GetShownColumns(ITableColumn col, IEnumerable<string>? invisibleColumns, IEnumerable<string>? shownColumns, IEnumerable<string>? hiddenColumns)
Expand Down Expand Up @@ -117,36 +114,38 @@ public override IEnumerable<IDynamicObject> GetItems()
{
if (UseCache)
{
Items ??= BuildItems();
_items ??= BuildItems();
}
else
{
Items = BuildItems();
_items = BuildItems();
}
return Items;
return _items;
}

private List<IDynamicObject> BuildItems()
{
// 同步 DataRow 值到 DataTableDynamicObject 实例中并建立缓存
_dataCache.Clear();
var ret = new List<IDynamicObject>();
foreach (DataRow row in DataTable.Rows)
{
if (row.RowState != DataRowState.Deleted)
if (!row.IsDeletedOrDetached())
{
var dynamicObject = Activator.CreateInstance(DynamicObjectType);
var dynamicObject = Activator.CreateInstance(_dynamicObjectType);
if (dynamicObject is DataTableDynamicObject d)
{
d.Row = row;
foreach (DataColumn col in DataTable.Columns)
{
if (!row.IsNull(col))
{
// 值不为 DBNull 时才设置属性值
Utility.SetPropertyValue<object, object?>(d, col.ColumnName, row[col]);
}
}

d.Row = row;
_dataCache.TryAdd(d.DynamicObjectPrimaryKey, (d, row));
_dataCache.TryAdd(d.DynamicObjectPrimaryKey, d);
ret.Add(d);
}
}
Expand All @@ -157,7 +156,7 @@ private List<IDynamicObject> BuildItems()
/// <summary>
/// <inheritdoc/>
/// </summary>
public override IEnumerable<ITableColumn> GetColumns() => Columns;
public override IEnumerable<ITableColumn> GetColumns() => _columns;

private List<ITableColumn> InternalGetColumns()
{
Expand All @@ -174,7 +173,7 @@ private List<ITableColumn> InternalGetColumns()
/// </summary>
protected internal override IEnumerable<CustomAttributeBuilder> OnColumnCreating(ITableColumn col)
{
AddAttributesCallback?.Invoke(this, col);
_addAttributesCallback?.Invoke(this, col);
return base.OnColumnCreating(col);
}

Expand All @@ -188,41 +187,52 @@ public override async Task AddAsync(IEnumerable<IDynamicObject> selectedItems)
{
await OnAddAsync(selectedItems);
}
else if (Activator.CreateInstance(DynamicObjectType) is DataTableDynamicObject dynamicObject)
else if (Activator.CreateInstance(_dynamicObjectType) is DataTableDynamicObject dynamicObject)
{
var row = DataTable.NewRow();
var indexOfRow = 0;
var item = selectedItems.FirstOrDefault();

if (item != null && _dataCache.TryGetValue(item.DynamicObjectPrimaryKey, out var c))
{
indexOfRow = DataTable.Rows.IndexOf(c.Row);
}

// DataTable 数据源增加数据
DataTable.Rows.InsertAt(row, indexOfRow);

// 新建动态类型属性赋值
// 原始表格增加新数据
var row = DataTable.NewRow();
foreach (DataColumn col in DataTable.Columns)
{
if (col.DefaultValue != DBNull.Value)
// 自增长主键跳过
if (col.AutoIncrement)
{
continue;
}

var v = Utility.GetPropertyValue<object, object?>(dynamicObject, col.ColumnName);
if (v != null)
{
Utility.SetPropertyValue<object, object?>(dynamicObject, col.ColumnName, col.DefaultValue);
row[col] = v;
}
}

// DataTable 数据源增加数据
DataTable.Rows.InsertAt(row, indexOfRow);

// 新建动态类型属性赋值
dynamicObject.Row = row;

// 触发 Changed 回调
if (OnChanged != null)
{
await OnChanged(new(new[] { dynamicObject }, DynamicItemChangedType.Add));
await OnChanged(new DynamicObjectContextArgs([dynamicObject]));
}

// Table 组件数据源更新数据
Items?.Insert(indexOfRow, dynamicObject);

// 缓存更新数据
_dataCache.TryAdd(dynamicObject.DynamicObjectPrimaryKey, (dynamicObject, row));
_dataCache.TryAdd(dynamicObject.DynamicObjectPrimaryKey, dynamicObject);

// 更新 UI 数据
if (_items != null)
{
_items.Insert(indexOfRow, dynamicObject);
}
}
}

Expand All @@ -235,37 +245,36 @@ public override async Task<bool> DeleteAsync(IEnumerable<IDynamicObject> items)
if (OnDeleteAsync != null)
{
ret = await OnDeleteAsync(items);
Items?.RemoveAll(i => items.Any(item => item == i));
}
else
{
var changed = false;
foreach (var item in items)
{
if (_dataCache.TryGetValue(item.DynamicObjectPrimaryKey, out var row))
if (_dataCache.TryRemove(item.DynamicObjectPrimaryKey, out var row))
{
changed = true;

// 删除数据源
DataTable.Rows.Remove(row.Row);

// 清理缓存
_dataCache.TryRemove(item.DynamicObjectPrimaryKey, out _);

// 清理 Table 组件数据源
Items?.Remove(item);
if (row.Row != null)
{
DataTable.Rows.Remove(row.Row);
}
}
}

// 检查是否有数据更新
if (changed)
{
DataTable.AcceptChanges();
if (OnChanged != null)
{
await OnChanged(new(items, DynamicItemChangedType.Delete));
await OnChanged(new DynamicObjectContextArgs(items, DynamicItemChangedType.Delete));
}
}
ret = true;
}
_items = null;
return ret;
}

Expand All @@ -281,8 +290,11 @@ private Task OnCellValueChanged(IDynamicObject item, ITableColumn column, object
// 更新内部 DataRow
if (_dataCache.TryGetValue(item.DynamicObjectPrimaryKey, out var cacheItem))
{
cacheItem.Row[column.GetFieldName()] = val;
Items = null;
// 更新原始 DataTable
if (cacheItem.Row != null)
{
cacheItem.Row[column.GetFieldName()] = val;
}
}
return Task.CompletedTask;
}
Expand Down
Loading
Loading