ADO.NET 实体框架:相关对象的更新问题
我正在 SQL Express-DB 上使用 WPf/MVVM 和 ADO.NET EF 玩一个小型 VB.NET 应用程序,在尝试更新相关对象时遇到问题:
我的数据库有三个表“tb_Actors”, “tb_Movies”和连接表“tb_movies_actors”。 EF 设计者创建两个实体“Actors”和“Movies”,并根据外键正确设置它们的导航属性。因此能够提出一个绑定到具有所有“Movies.Actors”的 viewModels 属性的视图。
我视图中的 DataGrid 正确显示所有演员,并且 - 如果它是不在我的数据库中的新演员 - 我可以将新演员添加到电影中并将更改正确保存到数据库中。
但是,如果我想向数据库中已有的电影添加演员,我将在 tb_actors 表中得到一个双重条目。首先,我将主键字段(名称和 ID)设置为 UNIQUE,但随后我的代码中断了。然后,我添加了一个小更新例程,检查电影的每个相关演员是否是已知演员,并将“新演员”id 更改为“老演员”id - 这也会中断。
有没有办法告诉 EF 它必须确定添加的相关对象(=添加到电影中的已知演员)是否已在数据库中,因此它必须仅向连接表插入一个新条目,而不是向相关对象表?
我的下一步将是分离相关对象,并在我自己的数据访问代码中执行所有更新/插入……但由于我相信我的问题是围绕典型的 EF 用例,因此必须有一种更优雅的方法来处理相关对象的更新。
任何想法、答案、提示都将受到高度赞赏!
* 编辑这里是相关的代码片段 *
1) 我的 MovieRepository 数据访问类中有以下 LoadMovies 函数:
Private Function LoadMovies() As List(Of Movies)
movs = From m In dc.Movies.Include("Actors") Select m
Return movs.ToList
End Function
2) 我的 viewModel 的以下属性公开与特定电影相关的演员:
Public ReadOnly Property actors() As ICollectionView
Get
If evs Is Nothing Then
evs = New CollectionViewSource
evs.Source = _movie.Actors
End If
Return evs.View
End Get
End Property
3)在我的 MovieDetail 视图中,我有一个绑定到属性的数据网格:
<DataGrid Name="ActTestGrid" HorizontalAlignment="Left" VerticalAlignment="Stretch" ItemsSource="{Binding actors}" AutoGenerateColumns="False" Width="150" Height="120" Style="{StaticResource dgTemplate}" RowStyle="{StaticResource dgRowTemplate}" CellStyle="{StaticResource dgCellTemplate}" CanUserSortColumns="True" CanUserAddRows="True" CanUserDeleteRows="True" HeadersVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=name, UpdateSourceTrigger=PropertyChanged}" CanUserSort="true"/>
</DataGrid.Columns>
</DataGrid>
4)这是我的 MovieRepository 的 updateMovie 函数(到目前为止):
Public Sub UpdateMovie(ByVal movie As Movies)
If movie Is Nothing Then
Throw New ArgumentNullException("Movie")
Else
dc.SaveChanges()
End If
End Sub
I’m playing around with a little VB.NET app with WPf/MVVM and ADO.NET EF on a SQL Express-DB and I’m running into problems while trying to update related objects:
My DB has three tables “tb_Actors”, “tb_Movies” and a junction table “tb_movies_actors”. The EF designer creates two entities "Actors" und "Movies" and sets their navigation properties based on the foreign keys correctly. So was able to come up with a view which binds to a viewModels property which has all “Movies.Actors”.
The DataGrid in my view displays all actors correctly, and – if it’s a new actor which isn’t in my DB already- I am able to add new actors to a movie and persist the changes to the DB correctly.
However, if I want to add an actor to a movie who is already in my DB, I’ll get a double entry in my tb_actors table. First I’ve set the primary key fields (name and id) to UNIQUE, but then my code breaks. Then, I’ve added a little update routine which checks for each related actor of a movie if it’s a known actor, and changes the “new actors” id to the “old actors” id – this also breaks.
Is there a way to tell EF that it has to determine if an added related object (= already known actor added to movie) is already in the DB, and it therefore has to insert only a new entry to to junction table, but not to the related objects table?
My next step would be detaching the related objects and do all updates/inserts in my own data access code … but since I believe my problem is around a typical EF use case, there must be a more elegant way to deal with updates on related objects.
Any thoughts, answers, hints are highly appreciated!
* EDIT Here are the relevant code snippets *
1) I have the following LoadMovies Function in my MovieRepository data access class:
Private Function LoadMovies() As List(Of Movies)
movs = From m In dc.Movies.Include("Actors") Select m
Return movs.ToList
End Function
2) The following property of my viewModel exposes the actors related to a specific movie:
Public ReadOnly Property actors() As ICollectionView
Get
If evs Is Nothing Then
evs = New CollectionViewSource
evs.Source = _movie.Actors
End If
Return evs.View
End Get
End Property
3) In my MovieDetail view, I've a datagrid binding to the property:
<DataGrid Name="ActTestGrid" HorizontalAlignment="Left" VerticalAlignment="Stretch" ItemsSource="{Binding actors}" AutoGenerateColumns="False" Width="150" Height="120" Style="{StaticResource dgTemplate}" RowStyle="{StaticResource dgRowTemplate}" CellStyle="{StaticResource dgCellTemplate}" CanUserSortColumns="True" CanUserAddRows="True" CanUserDeleteRows="True" HeadersVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=name, UpdateSourceTrigger=PropertyChanged}" CanUserSort="true"/>
</DataGrid.Columns>
</DataGrid>
4) This is my updateMovie Function of my MovieRepository (as by now):
Public Sub UpdateMovie(ByVal movie As Movies)
If movie Is Nothing Then
Throw New ArgumentNullException("Movie")
Else
dc.SaveChanges()
End If
End Sub
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
EF 会做它该做的事情,您不能再告诉它做它,它不会验证您的数据。 EF 将插入您告诉它插入(或尝试插入)的内容。在调用保存更改之前,数据验证是您的责任。
为了解决这个问题,请考虑在视图中提供一个包含演员姓名列表的组合框。组合框的
IsEditable
设置为 true,并且文本属性绑定到 ViewModel 中的ActorName AS String
。如果用户选择现有的 actor,EF 将不会尝试插入新的 actor。如果用户输入新名称,EF 将创建一个新角色。这是一些使用品牌名称的代码:
在视图上:
EF does what it does and you can't tell it to do anymore, that is it won't validate your data. EF will insert what you tell it to insert (or try to). Data validation is your responsibility before you call save changes.
In order to get around this problem consider providing a combobox in the view with the list of actors names. The combobox has
IsEditable
set to true and the text property is bound toActorName AS String
in your ViewModel. If the user selects an existing actor EF will not try to insert a new one. If the user types in a new name EF will create a new actor.Here is some code using a Brand Name:
And on the view: