数据绑定实体框架导航属性 - 处理更改
本文关键字:处理 属性 实体 框架 导航 数据绑定 | 更新日期: 2023-09-27 17:55:34
所以我正在构建我的第一个更大的应用程序,我正在使用WPFfor Windows和Stuff以及实体框架来检索,更新和存储数据。到目前为止,使用类似于 MVVM 模式的模式,我遇到了一些问题,但能够解决这些问题,并且设计得很远。另外,我正在使用数据库优先方法。
但我刚刚遇到了一堵我应该预料到的砖墙。它与实体中的嵌套属性以及处理它们更改的方式有关。让我们解释一下。
为了简单起见,我将不使用我的实际类名。假设我的 EF 模型中有三个实体:部门、经理和个人信息。我修改了我的 *.tt 模板文件,以便我的所有实体也实现 INotifyPropertyChanged 接口,但仅适用于它们的非导航属性,因为导航属性被声明为虚拟,并且在设置其日期时将被 EF 覆盖。
因此,假设我生成的类如下所示:
public partial class Department : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChange(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public Department() { }
int _id;
public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }
int _someproperty;
public int SomeProperty { get { return _someproperty; } set { _someproperty= value; OnPropChange("SomeProperty"); } }
int _managerid;
public int ManagerID { get { return _managerid; } set { _managerid = value; OnPropChange("ManagerID"); } }
public virtual Manager Manager { get; set; }
}
public partial class Manager : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChange(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public Manager() { }
int _id;
public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }
public virtual PersonalInfo PersonalInfo { get; set; }
}
public partial class PersonalInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChange(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public PersonalInfo() { }
int _id;
public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }
string _firstname;
public string FirstName { get { return _firstname; } set { _firstname = value; OnPropChange("FirstName"); } }
string _lastname;
public string LastName { get { return _lastname; } set { _lastname = value; OnPropChange("LastName"); } }
}
现在,如果我想说显示部门及其经理的列表,这效果很好。首先,我像这样将数据加载到 EF 上下文中
Context.Departments.Include(d => d.Manager.PersonalInfo).Load();
Departments = Context.Deparments.Local;
而且比在 XAML 中我可以做到:
<DataGrid ItemsSource="{Binding Departments}" SelectedItem="{Binding CurrentDepartment, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}"/>SomeProperty
<DataGridTextColumn Binding="{Binding SomeProperty }" Header="Property"/>
<DataGridTextColumn Binding="{Binding Manager.PersonalInfo.FirstName}" Header="FirstName"/>
<DataGridTextColumn Binding="{Binding Manager.PersonalInfo.LastName}" Header="LastNameName"/>
</DataGrid.Columns>
</DataGrid>
所有这些都非常有效。我可以通过简单地从上下文中删除项目并保存更改来毫无问题地添加和删除项目。由于实体集是可观察集合,因此从实体集中添加或删除任何内容都会自动引发更新数据网格的相应事件。我还可以修改部门的任何非导航属性,并可以刷新当前部门中的数据,如下所示:
Context.Entry(CurrentDepartment).Refresh();
它会自动刷新数据网格中的数据。
当我更改其中一个导航属性时,问题就开始了。假设我打开了一个窗口,在其中编辑了部门,我将现任经理从鲍勃·博宾顿改为戴夫·达维斯顿。当我返回此窗口调用时:
Context.Entry(CurrentDepartment).Refresh();
它只会刷新非导航属性,"名字"和"姓氏"列仍将显示 Bob Bobington。但这是刷新功能按预期工作。但是,如果我像这样将正确的数据加载到上下文中:
Context.Entry(CurrentDepartment).Reference(d=>d.Manager);
Context.Entry(CurrentDepartment.Manager).Reference(m=>m.PersonalInfo);
仍然不会更改名字和姓氏列的内容,因为它们仍绑定到 OLD 管理器。仅当更改发生在 Personalinfo 的 Bob Bobington 实例上时,它们才会刷新。
我可以通过将列直接绑定到管理器属性,并通过 ValueConverter 或重写管理器的 ToString 将管理器转换为文本来解决此级别的问题。但这无济于事,因为 WPF 永远不会收到管理器属性已更改的通知,因为对该属性的更改不会引发 PropertyChanged 事件。
导航属性无法引发该事件,因为即使我编辑了 tt 模板,它也会为导航属性生成代码,如下所示:
Manager _manager;
public virtual Manager Manager { get{return _manager;}
set{
_manager=value;
OnPropChange("Manager");
}
}
所有这些代码都可能被 EF 框架本身重写。
那么,在这些情况下最好的办法是什么?请不要告诉我,传统智慧是将EF Poco类中的数据复制到您自己的类中并使用它们。:(
更新:
这是一个潜在的愚蠢的解决方案。但它有效。
ObservableCollection<Department> tempd = Departments;
Department temp = CurrentDepartment;
Departments = null;
CurrentDepartment = null;
Context.Entry(temp).Refresh();
Context.Entry(temp).Reference(d=>d.Manager).Load();
Context.Entry(temp.Manager).Reference(m=>m.PersonalInfo).Load();
Departments = tempd;
CurrentDepartment = temp;
正如您可以清楚地看到的那样,关键在于强制 DataGrid 从头开始重新绑定自身。这样,它将不使用快捷方式,并将正确重新绑定自己。但是这种方法很愚蠢。一想到必须对具有数百行的数据网格执行此操作,我就不寒而栗。
所以我仍在等待一个合适的解决方案,但我将继续使用它。有总比没有好。
好吧,传统智慧是将数据复制到另一个 POCO,或者至少让您的 ViewModel 类透视到底层模型类。您已经组合了模型和视图模型类,以便基于模型的约束(ORM 所需的虚拟方法)干扰基于 ViewModel 的约束(要允许数据绑定,您必须能够从属性资源库引发事件)。
如果您的模型和视图模型已正确分离(关注点分离),那么您可以在模型(数据库持久对象)上拥有虚拟方法和数据库必填字段,在视图模型上拥有纯基于视图的函数(PropertyChanged 事件)。无论如何,您的数据库代码永远不应该关心您的 PropertyChanged 事件。
您可以通过使 ViewModel 成为透视类来简化它,以便每个属性获取器设置器如下所示:
public string PropertyThing
{
get { return _myModel.PropertyThing; }
set { _myModel.PropertyThing = value; PropChanged("PropertyThing"); }
}
如果您已经在进行代码生成,这不应该是一件大事。
或者,您可以使用自动映射器之类的东西复制所有值,以分离出模型和视图模型以分隔类。
这不是你想听到的,但是你的ORM和你的UI正在冲突,这就是MVVM架构(特别是分离模型和ViewModel)应该做得更好的事情。