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

Blazor DataGrid同一列展示Entity子类不同属性的实现方案

Blazor DataGrid中根据实体子类类型动态显示名称列

问题描述

我持有一个List<Entity>集合,其中包含Monster和Player两个继承自Entity的子类实例。需要在Blazor的DataGrid组件中展示该集合,并且“名称”列要根据实体类型显示对应值:

  • 当实体为Monster时,显示Monster.EntityName
  • 当实体为Player时,显示Player.Name(父类Entity的Name属性)

想确认:

  1. 是否可以直接实现该需求,无需额外创建EntityDataModel?
  2. 如果必须创建EntityDataModel,能否保留List<Entity>作为核心数据源?

相关模型代码

Entity模型

public class Entity
{
    public string Name { get; set; } = string.Empty;
    public int Id { get; set; } = -1;
    public int MaxHp { get; set; } = 0;
    public int Hp { get; set; } = 0;
    public List<int> Damage { get; set; } = new List<int> { };

    public int Init = 0;
    public int Ac { get; set; } = 10;
    public string ArmorType { get; set; } = "None";
    public string[] Speed { get; set; } = new string[0];
    public string[] DmgVul { get; set; } = new string[0];
    public string[] DmgRes { get; set; } = new string[0];
    public string[] DmgImm { get; set; } = new string[0];
    public string[] CondImm { get; set; } = new string[0];
    public string[] Senses { get; set; } = new string[0];
    public string[] Languages { get; set; } = new string[0];
    public List<NameValueCondition> Conditions { get; set; } = new List<NameValueCondition> { };
    public EntityHealthState HealthState => GetHealthState();
    private EntityHealthState GetHealthState()
    {
        if (Hp > MaxHp) return EntityHealthState.Overhealed;
        else if (Hp <= 0) return EntityHealthState.Dead;
        else if (Hp <= MaxHp / 4.0) return EntityHealthState.Critical;
        else if (Hp <= MaxHp / 2.0) return EntityHealthState.Bloody;
        else if (Hp < MaxHp) return EntityHealthState.Wounded;
        else return EntityHealthState.Healthy;
    }
}
public enum EntityHealthState
{
    Overhealed,
    Healthy,
    Wounded,
    Bloody,
    Critical,
    Dead
}

Player模型

public class Player : Entity
{
    public bool InParty { get; set; }
    public int InitiativeBonus { get; set; }
}

Monster模型

public class Monster : Entity
{
    public string EntityName { get; set; } = string.Empty;
    public string Type { get; set; } = string.Empty;
    public string Alignment { get; set; } = string.Empty;
    public int HitDiceNumber { get; set; } = 0;
    public int HitDiceSize { get; set; } = 0;
    public int HitDiceHp { get; set; } = 0;
    public int Str { get; set; } = 10;
    public int Dex { get; set; } = 10;
    public int Con { get; set; } = 10;
    public int Int { get; set; } = 10;
    public int Wis { get; set; } = 10;
    public int Cha { get; set; } = 10;
    public string[] SavThrProf { get; set; } = new string[0];
    public string[] SkillProf { get; set; } = new string[0];
    public ChallengeRating Challenge { get; set; } = new ChallengeRating();
    public NameValuePair[] Traits { get; set; } = new NameValuePair[0];
    public NameValuePair[] Actions { get; set; } = new NameValuePair[0];

    public bool isSpellCaster { get; set; } = false;
    public int SpellSlots1 { get; set; } = 0;
    public List<NameValuePair> Spells1 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots2 { get; set; } = 0;
    public List<NameValuePair> Spells2 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots3 { get; set; } = 0;
    public List<NameValuePair> Spells3 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots4 { get; set; } = 0;
    public List<NameValuePair> Spells4 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots5 { get; set; } = 0;
    public List<NameValuePair> Spells5 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots6 { get; set; } = 0;
    public List<NameValuePair> Spells6 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots7 { get; set; } = 0;
    public List<NameValuePair> Spells7 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots8 { get; set; } = 0;
    public List<NameValuePair> Spells8 { get; set; } = new List<NameValuePair> { };
    public int SpellSlots9 { get; set; } = 0;
    public List<NameValuePair> Spells9 { get; set; } = new List<NameValuePair> { };

    public bool isLegendary { get; set; } = false;
    public NameValuePair[] LegendaryActions { get; set; } = new NameValuePair[0];
    public string LegendaryDesc { get; set; } = string.Empty;
    public string LairActions { get; set; } = string.Empty;
    public string RegionalEffects { get; set; } = string.Empty;

    public bool IsKnown = true;
    public bool IsUnseen = false;
    public bool IsAlly = false;
}

已尝试的实现方式

<GridView Data="@entities">
    <Columns>
        <GridViewColumn>
            <Header>Name</Header>
            <RenderFragment Context="context">
                @if (context is Monster monster)
                {
                    @monster.EntityName
                }
                else if (context is Player player)
                {
                    @player.Name
                }
            </RenderFragment>
        </GridViewColumn>
    </Columns>
</GridView>

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Remove</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var entity in entities)
        {
            <tr>
                @if (entity is Monster)
                {
                    var monster = entity as Monster;
                    <td>@monster.EntityName</td>
                }
                else
                {
                    <td>@entity.Name</td>
                }

                <td>
                    <button @onclick="() => RemoveMonster(entity)"> Remove </button>
                </td>
            </tr>
        }
    </tbody>
</table>

普通表格可以实现需求,但希望用DataGrid组件完成。


解决方案

1. 直接实现(无需EntityDataModel)

绝大多数Blazor DataGrid组件(如Radzen DataGrid、MudBlazor MudDataGrid、DevExpress Blazor Grid)都支持模板列功能,你可以在列的模板中直接进行类型判断,动态渲染对应的值,无需额外创建数据模型。

以你使用的GridView组件为例,修正模板写法即可:

<GridView Data="@entities">
    <Columns>
        <GridViewColumn Title="名称">
            <CellTemplate Context="entity">
                @{
                    if (entity is Monster monster)
                    {
                        @monster.EntityName
                    }
                    else if (entity is Player player)
                    {
                        @player.Name
                    }
                    else
                    {
                        @entity.Name
                    }
                }
            </CellTemplate>
        </GridViewColumn>
        <!-- 其他列可以直接绑定Entity的公共属性,例如: -->
        <GridViewColumn Title="生命值" Field="@nameof(Entity.Hp)" />
        <GridViewColumn Title="最大生命值" Field="@nameof(Entity.MaxHp)" />
    </Columns>
</GridView>

如果是其他组件(比如MudBlazor),写法类似:

<MudDataGrid Items="@entities">
    <Columns>
        <MudDataGridColumn HeaderText="名称">
            <CellTemplate>
                @{
                    var entity = context.Item as Entity;
                    if (entity is Monster m)
                    {
                        @m.EntityName
                    }
                    else if (entity is Player p)
                    {
                        @p.Name
                    }
                    else
                    {
                        @entity.Name
                    }
                }
            </CellTemplate>
        </MudDataGridColumn>
    </Columns>
</MudDataGrid>

2. 若需创建EntityDataModel,仍可保留原List作为核心数据源

如果因为排序、过滤或统一数据结构的需求必须创建EntityDataModel,你可以通过包装原Entity引用的方式,保留原List<Entity>作为核心数据源:

步骤1:创建EntityDataModel

public class EntityDataModel
{
    // 保留原Entity的引用,确保核心数据源不变
    public Entity OriginalEntity { get; set; } = null!;

    // 动态计算显示名称
    public string DisplayedName => OriginalEntity switch
    {
        Monster monster => monster.EntityName,
        Player player => player.Name,
        _ => OriginalEntity.Name
    };

    // 其他需要展示的属性,直接从原Entity映射
    public int Hp => OriginalEntity.Hp;
    public int MaxHp => OriginalEntity.MaxHp;
    public EntityHealthState HealthState => OriginalEntity.HealthState;
    // ... 按需添加其他属性
}

步骤2:在组件中动态生成DataGrid数据源

@code {
    // 核心数据源,保持原List<Entity>不变
    private List<Entity> entities = new();

    // 动态投影成DataGrid需要的模型,无需复制数据,仅包装引用
    private List<EntityDataModel> DataGridDataSource => 
        entities.Select(e => new EntityDataModel { OriginalEntity = e }).ToList();
}

<!-- 使用DataGrid绑定投影后的数据源 -->
<GridView Data="@DataGridDataSource">
    <Columns>
        <GridViewColumn Title="名称" Field="@nameof(EntityDataModel.DisplayedName)" />
        <GridViewColumn Title="生命值" Field="@nameof(EntityDataModel.Hp)" />
        <!-- 其他列绑定DataModel的属性 -->
    </Columns>
</GridView>

这种方式下,所有对实体的修改仍直接操作原List<Entity>,DataGrid仅通过DataModel做显示层的映射,既满足了DataGrid的需求,也保留了核心数据源的完整性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 21:42:02