2014-03-30 100 views
3

因此,我正在構建我的第一個更大的應用程序,並且我正在使用Windows的WPF和東西以及用於檢索,更新和存儲數據的實體框架。到目前爲止,使用類似於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> 

而這一切奇妙的作品。我可以添加和刪除沒有問題的項目,只需從上下文中刪除它們並保存更改即可。由於實體集是ObservableCollections,所以添加或刪除它們會自動引發更新數據網格的適當事件。我還可以修改處任何nonnavigation財產,可以刷新數據CurrentDepartment像這樣:

Context.Entry(CurrentDepartment).Refresh(); 

,並在DataGrid自動刷新數據。

當我更改其中一個導航屬性時,就會出現問題。假設我打開了一個編輯部門的窗口,在那裏我將當前的經理從Bob Bobington改爲Dave Daveston。當我回到這個窗口電話:

Context.Entry(CurrentDepartment).Refresh(); 

它只會刷新非導航性能,以及第一和姓列仍將說鮑勃Bobington。但這是刷新功能按預期工作。但是,如果我正確的數據加載到這樣的背景下:

Context.Entry(CurrentDepartment).Reference(d=>d.Manager); 
    Context.Entry(CurrentDepartment.Manager).Reference(m=>m.PersonalInfo); 

仍不會改變姓氏和名字列的內容,因爲它們仍結合老經理。只有在BobBinington實例的PersonalInfo發生變化時,它們纔會刷新。

我可以通過將列直接綁定到Manager屬性並通過ValueConverter或通過覆蓋管理器的ToString將Manager轉換爲文本來解決此問題。但這不會有幫助,因爲WPF永遠不會被通知管理器屬性已更改,因爲對該屬性的更改不會引發PropertyChanged事件。

導航性能無法提升該事件,因爲即使我編輯的TT模板,它的導航屬性,像這樣生成的代碼:

Manager _manager; 
public virtual Manager Manager { get{return _manager;} 
     set{ 
      _manager=value; 
      OnPropChange("Manager"); 
     } 
} 

所有這些代碼可能會由EF框架本身覆蓋。

Sooo,在這些情況下最好的做法是什麼?請不要告訴我,傳統的看法是將EF Poco課程的數據複製到您自己的課程中並使用它們。 :(

UPDATE:。

這裏去了這個問題的潛在愚蠢的方案,但它的工作原理

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

如果修改'ManagerId',調用'context.ChangeTracker.DetectChanges'應該保持屬性與'Manager'同步。 –

+0

我不認爲這會有所幫助,因爲從我瞭解的DetectChanges檢查天氣實體已經改變,因爲上次數據庫調用/同步。它不會引發任何事件,也不會告訴您數據庫中有什麼變化。在管理器中保持正確的值並不是真正的問題,問題是如果Manager更改它,則不會引發WPF可以掛接的事件並知道它何時發生更改。 –

+0

不,它不會引發事件,但會執行*關係修正*,這是EF確保外鍵和引用同步的內部過程。這將導致在處理引用時從數據庫中提取正確的管理器。 –

回答

1

那麼,傳統智慧將數據複製到另一個POCO,或至少使您的ViewModel類窺視到底層Model類。你已經結合了你的Model和ViewModel類,使得基於模型的約束(ORM需要的虛擬方法)干擾你的基於ViewModel的約束(允許數據綁定,你必須能夠從屬性設置器中引發事件)。

如果您的Model和ViewModel被正確分離(Separation of Concerns),那麼您可以在模型(數據庫可持久對象)和純粹的基於視圖的函數(PropertyChanged事件)上擁有虛擬方法和數據庫必需字段你的ViewModel。無論如何,你的數據庫代碼不應該關心你的PropertyChanged事件。

您可以通過視圖模型更容易查找通過類所以每一個屬性的getter,setter方法是這樣的:

public string PropertyThing 
{ 
    get { return _myModel.PropertyThing; } 
    set { _myModel.PropertyThing = value; PropChanged("PropertyThing"); } 
} 

如果你已經做代碼生成這不應該是一個重大的苦差事。

或者,您可以使用類似於AutoMapper的東西來複制所有值,以將模型和視圖模型分離爲單獨的類。

這不是你想聽到的,但是你的ORM和你的用戶界面發生了衝突,這就是MVVM架構(特別是分離模型和視圖模型)應該做得更好的事情。

+0

我認爲在此期間,我想通過CollectionView.Refresh()來刷新列表,而不用刷新列表。 至於其餘,是的,​​我有點意識到我的MVVM並不是真正完全真正的MVVM。我這樣做,因爲我真的不明白爲什麼我應該基本上模型類的副本,因爲他們會公開相同的屬性。這隻會不必要地使代碼庫變得複雜,並且對數據庫進行任何修改都會讓我們感到痛苦,但我確實承認我可能犯了一個可怕的錯誤,但到目前爲止它已經運行良好,現在代碼庫並不是很小。 –

+0

我確實使用View的fiew時間,在顯示數據之前,我實際上需要對數據進行轉換,所以我正確地做了。加載數據,生成視圖將視圖發送到接口。 –