数据绑定实体框架导航属性 - 处理更改

时间:2014-03-30 15:43:08

标签: c# wpf entity-framework xaml mvvm

所以我正在构建我的第一个更大的应用程序,我正在使用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从头开始重新绑定。这样它就不会使用快捷方式,并且会正确地重新绑定。但这种方法很愚蠢。我想到必须对数百行的数据网格执行此操作时会感到颤抖。

所以我还在等待一个合适的解决方案,但我会继续使用它。有些东西总比没有好。

0 个答案:

没有答案