所以我正在构建我的第一个更大的应用程序,我正在使用WPF for Windows和东西以及Entity Framework来检索,更新和存储数据。到目前为止,使用类似于MVVM模式的模式,我有几个问题但能够解决它们并且我的设计相当远。 另外,我正在使用数据库优先方法。
但我刚碰到一堵我应该预料到的砖墙。它与实体中的嵌套属性有关,并且处理对它们的更改方式。我们来解释一下。
为了简单起见,我不会使用我的实际类名。 因此,假设我的EF模型中有三个实体:Department,Manager和PersonalInfo。 我修改了我的* .tt模板文件,以便我的所有实体也实现了INotifyPropertyChanged接口,但仅限于它们的NON NAVIGATION属性,因为导航属性被声明为虚拟,并且当它们的日期设置时将被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中删除它们并保存更改即可添加和删除没有问题的项目。由于实体集是ObservableCollections,因此对它们的任何添加或删除都会自动引发更新datagrid的相应事件。我也可以修改部门的任何非导航属性,并可以刷新CurrentDepartment中的数据,如下所示:
Context.Entry(CurrentDepartment).Refresh();
并自动刷新数据网格中的数据。
当我更改其中一个导航属性时,问题就开始了。假设我打开了一个窗口,我在其中编辑了部门,在那里我将现任经理从Bob Bobington改为Dave Daveston。当我回到这个窗口时呼叫:
Context.Entry(CurrentDepartment).Refresh();
它只会刷新非导航属性,而First和Lastname列仍然会说Bob Bobington。但那就是刷新功能按预期工作。但是,如果我将正确的数据加载到上下文中:
Context.Entry(CurrentDepartment).Reference(d=>d.Manager);
Context.Entry(CurrentDepartment.Manager).Reference(m=>m.PersonalInfo);
仍然不会更改名字和姓氏列的内容,因为它们仍然绑定到OLD管理器。只有在PersonalInfo的Bob Bobington实例发生更改时,它们才会刷新。
我可以通过将列直接绑定到Manager属性,并通过ValueConverter或重写ToString for Manager将Manager转换为文本来解决此级别的问题。但这无济于事,因为WPF将不会被通知Manager属性已更改,因为对该属性的更改不会引发PropertyChanged事件。
导航属性无法引发该事件,因为即使我编辑了tt模板,它也会生成导航属性的代码,如下所示:
Manager _manager;
public virtual Manager Manager { get{return _manager;}
set{
_manager=value;
OnPropChange("Manager");
}
}
所有这些代码都可能被EF框架本身覆盖。
Sooo,在这些情况下最好的做法是什么?请不要告诉我,传统的智慧是将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从头开始重新绑定。这样它就不会使用快捷方式,并且会正确地重新绑定。但这种方法很愚蠢。我想到必须对数百行的数据网格执行此操作时会感到颤抖。
所以我还在等待一个合适的解决方案,但我会继续使用它。有些东西总比没有好。