如何将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 = "签证号" });
关键说明
- 导航属性预加载:必须用
Include加载Candimmigration,否则DTO无法获取VisaNo,也无法监听其变化。 - 内存泄漏防范:删除实体时要取消DTO对实体的事件监听,避免内存无法释放。
- 自动更新逻辑:当实体属性修改、新增/删除实体时,DTO会同步变化,
BindingList会通知DataGridView自动刷新。
内容的提问来源于stack exchange,提问作者Unis
相关产品推荐
相关产品推荐

