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

如何将Candidate与Candimmigration指定属性绑定到DataGridView并自动更新?

解决方案:绑定指定属性并自动更新DataGridView

核心思路

通过**自定义DTO(数据传输对象)+ INotifyPropertyChanged**实现指定属性绑定,同时监听EF Core实体的属性变化和集合变化,同步更新DTO列表,让DataGridView自动刷新,无需每次重建列表。

步骤1:实现带属性变更通知的DTO

创建包含目标属性的DTO类,实现INotifyPropertyChanged,并监听源实体的属性变化,同步更新自身属性:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

public class CandidateDto : INotifyPropertyChanged
{
    private int _id;
    private string _firstName;
    private string _lastName;
    private string _visaNo;
    private readonly Candidate _sourceCandidate;

    public CandidateDto(Candidate candidate)
    {
        _sourceCandidate = candidate;
        // 初始化映射实体属性
        _id = candidate.Id;
        _firstName = candidate.FirstName;
        _lastName = candidate.LastName;
        _visaNo = candidate.Candimmigration?.VisaNo;

        // 监听实体及关联实体的属性变化
        candidate.PropertyChanged += Candidate_PropertyChanged;
        candidate.Candimmigration.PropertyChanged += Candimmigration_PropertyChanged;
    }

    // 属性定义,带变更通知
    public int Id
    {
        get => _id;
        set => SetField(ref _id, value);
    }

    public string FirstName
    {
        get => _firstName;
        set => SetField(ref _firstName, value);
    }

    public string LastName
    {
        get => _lastName;
        set => SetField(ref _lastName, value);
    }

    public string VisaNo
    {
        get => _visaNo;
        set => SetField(ref _visaNo, value);
    }

    // 监听Candidate实体属性变化,同步更新DTO
    private void Candidate_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case nameof(Candidate.Id):
                Id = _sourceCandidate.Id;
                break;
            case nameof(Candidate.FirstName):
                FirstName = _sourceCandidate.FirstName;
                break;
            case nameof(Candidate.LastName):
                LastName = _sourceCandidate.LastName;
                break;
        }
    }

    // 监听Candimmigration实体的VisaNo变化,同步更新DTO
    private void Candimmigration_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(Candimmigration.VisaNo))
        {
            VisaNo = _sourceCandidate.Candimmigration.VisaNo;
        }
    }

    // 通用属性变更通知方法
    private bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

步骤2:让实体类支持属性变更通知

EF Core默认生成的实体类没有实现INotifyPropertyChanged,需要手动添加,这样DTO才能监听实体的属性变化:

修改Candidate类:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

public partial class Candidate : INotifyPropertyChanged
{
    private int _id;
    private string _firstName;
    private string? _middleName;
    private string _lastName;
    // 其他私有字段对应原有属性

    [Key]
    [Column("ID")]
    public int Id
    {
        get => _id;
        set => SetField(ref _id, value);
    }

    [StringLength(20)]
    public string FirstName
    {
        get => _firstName;
        set => SetField(ref _firstName, value);
    }

    [StringLength(20)]
    public string? MiddleName
    {
        get => _middleName;
        set => SetField(ref _middleName, value);
    }

    [StringLength(20)]
    public string LastName
    {
        get => _lastName;
        set => SetField(ref _lastName, value);
    }

    // 其他属性同理修改setter,替换原有自动属性

    [InverseProperty("Candidate")]
    public virtual Candimmigration Candimmigration { get; set; } = null!;
    // 其他原有导航属性

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

修改Candimmigration类:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

public partial class Candimmigration : INotifyPropertyChanged
{
    private int _id;
    private int _candidateId;
    private string? _visaNo;
    // 其他私有字段对应原有属性

    public int Id
    {
        get => _id;
        set => SetField(ref _id, value);
    }

    public int CandidateId
    {
        get => _candidateId;
        set => SetField(ref _candidateId, value);
    }

    public string? VisaNo
    {
        get => _visaNo;
        set => SetField(ref _visaNo, value);
    }

    // 其他属性同理修改setter,替换原有自动属性

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

步骤3:初始化绑定并监听集合变化

在窗体或业务逻辑中,初始化DTO的BindingList,绑定到DataGridView,并监听EF Core Local集合的增删变化:

using System.ComponentModel;
using System.Linq;
using System.Data;

// 假设_dbContext是你的DbContext实例
// 预加载Candimmigration导航属性,避免延迟加载问题
await _dbContext.Candidates.Include(c => c.Candimmigration).LoadAsync();

// 创建DTO的BindingList,用于DataGridView绑定
var candidateDtos = new BindingList<CandidateDto>(
    _dbContext.Candidates.Local.Select(c => new CandidateDto(c)).ToList()
);

// 监听Local集合的增删变化,同步更新DTO列表
_dbContext.Candidates.Local.CollectionChanged += (sender, e) =>
{
    if (e.NewItems != null)
    {
        foreach (Candidate candidate in e.NewItems)
        {
            // 确保导航属性已加载
            _dbContext.Entry(candidate).Reference(c => c.Candimmigration).Load();
            candidateDtos.Add(new CandidateDto(candidate));
        }
    }

    if (e.OldItems != null)
    {
        foreach (Candidate candidate in e.OldItems)
        {
            var dto = candidateDtos.FirstOrDefault(d => d.Id == candidate.Id);
            if (dto != null)
            {
                // 取消事件监听,防止内存泄漏
                candidate.PropertyChanged -= dto.Candidate_PropertyChanged;
                candidate.Candimmigration.PropertyChanged -= dto.Candimmigration_PropertyChanged;
                candidateDtos.Remove(dto);
            }
        }
    }
};

// 绑定到DataGridView
dataGridView1.DataSource = candidateDtos;

// 配置DataGridView列(可选,也可在设计器中设置)
dataGridView1.Columns.Clear();
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn 
{ 
    DataPropertyName = nameof(CandidateDto.Id), 
    HeaderText = "候选人ID",
    ReadOnly = true // ID一般不可编辑
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn 
{ 
    DataPropertyName = nameof(CandidateDto.FirstName), 
    HeaderText = "名" 
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn 
{ 
    DataPropertyName = nameof(CandidateDto.LastName), 
    HeaderText = "姓" 
});
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn 
{ 
    DataPropertyName = nameof(CandidateDto.VisaNo), 
    HeaderText = "签证号" 
});

关键说明

  1. 导航属性预加载:必须用Include加载Candimmigration,否则DTO无法获取VisaNo,也无法监听其变化。
  2. 内存泄漏防范:删除实体时要取消DTO对实体的事件监听,避免内存无法释放。
  3. 自动更新逻辑:当实体属性修改、新增/删除实体时,DTO会同步变化,BindingList会通知DataGridView自动刷新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 19:40:30