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
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 to ActorName 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:
Public Property BrandName() As String
Get
Return _brandName
End Get
Set
_brandName = value.Trim()
If _brandName <> String.Empty Then
Dim b As Brand = _brands.ToList().Find(Function(br) br.BrandName.ToUpper() = _brandName.ToUpper())
If b Is Nothing Then
Brand = New Brand()
Brand.BrandName = _brandName
Else
Brand = b
End If
Else
Brand = Nothing
End If
CheckIsDirty()
RaisePropertyChanged("BrandName")
End Set
End Property
And on the view:
<ComboBox Grid.Row="1" Grid.Column="0" Height="28" HorizontalAlignment="Stretch" Margin="110,0,28,0" VerticalAlignment="Top" TabIndex="1" ItemsSource="{Binding Brands}" DisplayMemberPath="BrandName" Text="{Binding BrandName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"/>
Related
I'm creating a WPF data access layer that probably doesn't require full MVVM at this stage (but I might implement it).
I've successfully created a ComboBox that data binds to a foreign key value of a related table using a CollectionViewSource as the data source (See my XAML below, the combo box works fine but the TextBlock doesn't).
I only want to display the ComboBox as the cell editing template and use a TextBlock for displaying the data when it is not being edited. I can get the TextBlock to almost work (it displays data from the table related in the Foreign Key) but I can't find the equivalent property for "SelectedValuePath" so the TextBlock always displays the first value from the related table, rather than the value that corresponds to the ID in the Foreign Key field.
Is there a way (there must be) to get an equivalent behaviour from the TextBlock as I have in the ComboBox? Is there an equivalent property for SelectedValuePath?
The answer to this question would be hugely useful as there are some other fields I want to display in my data grid without providing the user any ability to edit but I still want to display a field from a related table rather than the Foreign Key ID.
Thanks so much in advance for your help and have a great day!
<CollectionViewSource x:Key="QGradeLookup"/>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Source={StaticResource QGradeLookup}, Path=QGrade}"
>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
IsEditable="False"
ItemsSource="{Binding Source={StaticResource QGradeLookup}}"
DisplayMemberPath="QGrade"
SelectedValuePath="ID"
SelectedValue="{Binding Path=OfficeQualityGradeID}"
IsSynchronizedWithCurrentItem="False"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
As requested here's the code for the item's source (thanks NIT):
Dim QGradeLookup As CollectionViewSource
Dim QGradeList = From q In OMRInterfaceEntities.OfficeQualityGrades
Dim QGsource = CType(Me.FindResource("QGradeLookup"), CollectionViewSource)
QGsource.Source = QGradeList.ToList()
I used Beth Massi's post as a template for the above - Beth Massi's Template
And here's the codebehind
Public Class WinPropertyDataEntry
Dim QGradeLookup As CollectionViewSource
Private Function GetOMRMarketsQuery(OMRInterfaceEntities As OMRInterfaceCustomCode.OMRInterfaceEntities) As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket)
Dim OMRMarketsQuery As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket) = OMRInterfaceEntities.OMRMarkets
'To explicitly load data, you may need to add Include methods like below:
'OMRMarketsQuery = OMRMarketsQuery.Include("OMRMarkets.OMRMarketType").
'For more information, please see http://go.microsoft.com/fwlink/?LinkId=157380
'Update the query to include Properties data in OMRMarkets. You can modify this code as needed.
OMRMarketsQuery = OMRMarketsQuery.Include("Properties")
'Update the query to include OMRBuildingSurveys data in OMRMarkets. You can modify this code as needed.
OMRMarketsQuery = OMRMarketsQuery.Include("Properties.OMRBuildingSurveys").Where("it.ID = 12")
'Returns an ObjectQuery.
Return OMRMarketsQuery
End Function
Private Sub Window_Loaded_1(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
Dim OMRInterfaceEntities As OMR.OMRInterfaceCustomCode.OMRInterfaceEntities = New OMR.OMRInterfaceCustomCode.OMRInterfaceEntities()
'Load data into OMRMarkets. You can modify this code as needed.
Dim OMRMarketsViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("OMRMarketsViewSource"), System.Windows.Data.CollectionViewSource)
Dim OMRMarketsQuery As System.Data.Objects.ObjectQuery(Of OMR.OMRInterfaceCustomCode.OMRMarket) = Me.GetOMRMarketsQuery(OMRInterfaceEntities)
OMRMarketsViewSource.Source = OMRMarketsQuery.Execute(System.Data.Objects.MergeOption.AppendOnly)
Dim QGradeList = From q In OMRInterfaceEntities.OfficeQualityGrades
Dim QGsource = CType(Me.FindResource("QGradeLookup"), CollectionViewSource)
QGsource.Source = QGradeList.ToList()
End Sub
The requirement you put here can be handled in the following manner. First of all binding the TextBlock here to Collection source and giving Path is not going to give you anything.
Each row in your DataGrid represents object of type OMRBuildingSurvey which has property OfficeQualityGradeID which I assume is of type string or int. Now what you want here is whenever the ID for OfficeQualityGrade is changed, the non-editable template i.e TextBlock should display the Grade name for the selected OfficeQualityGrade.
Problem here is you are not capturing the selected OfficeGrade here. So what you need to do is to first bind SelectedItem of the Combobox to the property of type "OfficeQualityGrade" (lets say SelectedOfficeGrade). The property should raise the property change notifications. And then you can bind your textblock to this property as Text = {Binding SelectedOfficeGrade.Grade}. you will have to define this property in your entity and implement INotifyPropertychanged.
Hope this will help.
Thanks
Okay, thanks so much Nit for your help. Your solution would absolutely work in a different scenario but each building survey has it's own unique Quality Grade. The answer is that I was referencing the object incorrectly. I was using the name of the column (OfficeQualityGradeID) Rather than the name of the object OfficeQualityGrade.
The following code provides a text block for the display of Quality Grades and a Combo Box for editing Quality Grades:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding OfficeQualityGrade.QGrade}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
x:Name="QGradeSelector"
IsEditable="False"
ItemsSource="{Binding Source={StaticResource QGradeLookup}}"
DisplayMemberPath="QGrade"
SelectedValuePath="ID"
SelectedValue="{Binding Path=OfficeQualityGradeID}"
IsSynchronizedWithCurrentItem="False"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
Hoping someone can point me in the right direction. I've got a program that allows the user to select various feeds from various forms, the feeds they can select come from a datatable from an Access Database that is put into a CollectionViewSource in WPF.
Such as:
<CollectionViewSource x:Key="BrandsViewSource" Source="{Binding Path=brands,
Source={StaticResource DatabaseDataSetall}}"/>
All the feeds they choose have a unique id attached to them under the column 'id', and as they select from the various lists in the program the id of their selections get added to an arraylist called 'feedselections'.
I have the array list being passed inside the program no worries, but what i need to do is populate a listbox in WPF with the contents of the arraylist 'feedselection' then it needs to fill in all the extra fields in the listbox.
The code for the listbox is:
<Grid Width="800" Height="80" Background="#FFE7AE4A">
<Label Content="{Binding Type}" HorizontalAlignment="Left" Height="27" Margin="8,8,0,0" VerticalAlignment="Top" Width="100" Name="typelabel" Foreground="#FFA4CE52" FontWeight="Bold"/>
<Label Content="{Binding Name}" HorizontalAlignment="Left" Height="27" Margin="115,8,0,0" VerticalAlignment="Top" Width="133"/>
<TextBox HorizontalAlignment="Left" Margin="169,35,0,17" TextWrapping="Wrap" Text="" VerticalAlignment="Stretch" Width="78" Name="foodamount"/>
</Grid>
I wasn't sure if it would be best to populate the listbox with all of the selections and then filter out the choices with a collectionviewsource.filter that arn't in the array. And if i take that option i'm not to sure if i should be using an array, arraylist, list(of t) or a few other options.
Alternativly I could populate the listbox with all the items in the array, and then somehow bind the other boxes to fetch their data from the collectionviewsource using the 'id' to find the rest of the data from the datatable.
Honestly I have no idea how to do that though. I've spent a long time looking for a way of doing both options, but the first seems the most promising except for not being able to find someone who has filtered from a list!
I found someone's code on this website that i was trying to use filter, just as a trial, my data but i keep getting the error:
An error occurred creating the form. See Exception.InnerException for details. The error is: 'Initialization of 'System.Windows.Data.CollectionViewSource' threw an exception.' Line number '22' and line position '55'
Which is caused when i add Filter="WorkerFilter" to the collectionviewsource as such:
<CollectionViewSource x:Key="FeedsViewSource" Source="{Binding Path=Feeds,
Source={StaticResource DatabaseDataSetall}}" Filter="WorkerFilter"/>
The filter handler is:
Private Sub WorkerFilter(ByVal sender As Object, ByVal e As FilterEventArgs)
Dim value As Object = CType(e.Item, System.Data.DataRow)("ID")
If (Not value Is Nothing) And (Not value Is DBNull.Value) Then
If (value = "4") Or (value = "5") Then
e.Accepted = True
Else
e.Accepted = False
End If
End If
End Sub
Sorry for the large amount of code and writing, i was just trying to give as much information as possible.
Any suggestions would be much appreciated, even if i have to make a new table in the database that gets wiped everytime it's used, that i can then use a relationship built inside the database, i'm pretty desperate and willing to try anything!
my question is, how do I sort related items of an entity.
In an invoice editing window, I have one ComboBox displaying all customers:
XAML:
<UserControl.Resources>
<CollectionViewSource x:Key="cvsCustomers"
d:DesignSource="{d:DesignInstance local:Customer, CreateList=True}" />
...
</UserControl.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource cvsCustomers}}" .../>
Code-behind:
Dim cvsCustomers As System.Windows.Data.CollectionViewSource
cvsCustomers = CType(Me.FindResource("cvsCustomers"), System.Windows.Data.CollectionViewSource)
Dim qryCustomers = _
From c In myEntities.Customers _
Order By c.CustomerCode
Select c
cvsCustomers.Source = qryCustomers
Now I have a 2nd ComboBox displaying all contact persons of the selected customer. This works fine, but the entries in this second ComboBox are unsorted / sorted by ID.
XAML:
<ComboBox ItemsSource="{Binding Path=myInvoice.Customer.Contacts}" .../>
;
How do I get the list on the 2nd ComboBox sorted?
King regards,
and thanks in advance for tips/suggestions,
Nico
I think there are two possible things.
When loading the data: It sounds like you are using the lazyloading feature of EF to load the Contacts related to the Customer. To sort Contacts, you would need to set what is sort option, were it's and when it's, but with the lazyloading these cannot be set. So you can use the Eagerly loading or the Explicitly loading to get the Contacts ordered. Please check to Using DbContext in EF 4.1 Part 6: Loading Related Entities, based on EF4.1. Something like this, the Explicitly loading case:
//Oh, I'm not familiar with VB.NET, so it's C# code
var customer; // it's assumed to be initialized.
context.Entry(customer).Collection(c => w.Contacts)
.Query().Orderby(c =>c.Name).Load();
Through CollectionView after loading the data unordered: you can let the CollectionView order the Contacts. Please check to CollectionViewSource Class(MSDN).
My WPF4 combobox dropdown list is incorrectly displaying the class name of my EF4 entity. Here is the relevant XAML:
<Window.Resources>
<CollectionViewSource x:Key="myEntitiesViewSource"/>
</Window.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource myEntitiesViewSource}}" DisplayMemberPath="CategoryDescription" SelectedValuePath="CategoryID" />
Here is the code in my Window_Loaded event:
var categoryList = from p in _context.Categories
orderby p.CategoryNumber
select p;
System.Windows.Data.CollectionViewSource myEntitiesViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("myEntitiesViewSource")));
// Load data by setting the CollectionViewSource.Source property:
myEntitiesViewSource.Source = categoryList;
My database has a many to many relationship between Projects and Categories with a join table called ProjectCategories. The Categories entity was automagically created as a single entity to represent two database tables:
1) the lookup table of Categories containing an ID, CategoryDescription and CategoryNumber and
2) the join table ProjectCategories containing only two fields - the IDs from the tables Projects and Categories. The entity model lives in a separate project from my WPF window.
My goal is to allow the user to select a CategoryDescription from the dropdown list, then click an Add Category button to add the selected Category to a separate list of ProjectCategories. With the current code I see the correct CategoryDescription in the combobox text area but the dropdown list displays only the entity class name Categories (preceded by it's namespace) multiple times!
How do I make this simple lookup combobox bind correctly and display a list of CategoryDescriptions and a SelectedValue of CategoryID? Note: I'd accept a code only approach leaving out the CollectionViewSource in XAML if it's possible.
Thanks!
Nevermind. I asked this question and have answered it myself. There was nothing wrong with my code or XAML. The problem was caused by the use of a third party theme to style my controls. Once I removed the theme the combobox binding problem went away. For more details see this post.
What about something like this?
<ComboBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding Category}" DisplayMemberPath="Description" />
Instead of using a Selected Value, I would store the whole object. The selected value approach is old ASP style for my taste.
SelectedItem="{Binding Category}" is your Category object. Basically it has stored the selected item of the ComboBox.
When the user clicks a button for example, you can fire a Command from the ViewModel and you will have the corresponding selected Category object.
I have 2 comboboxes which we will call cbo1 and cbo2. Now, there is a relationship between cbo1 and cbo2. when i select an item at cbo1, the cbo2 ItemsSource is updated (since it is bound to the SelectedItem) anyway, below is the sample XAML code for it.
<ComboBox x:Name="cbo1" Grid.Row="0" Grid.Column="1" Margin="5" SelectedItem="{Binding Path=Brand}"></ComboBox>
<ComboBox x:Name="cbo2" Grid.Row="1" Grid.Column="1" Margin="5" SelectedItem="{Binding Path=Model}" ItemsSource="{Binding ElementName=cbo1, Path=SelectedItem.Models}" DisplayMemberPath="Name"></ComboBox>
the objects used are Brand and Model. Brand has a property named Models which contain a collection of Model objects ( typeof IList ). So basically, a one-many relationship between the 2 classes.
By the way, those 2 classes are used in NHibernate. Now, when I run the app, cbo1 which contains a collection of Brand objects is loaded with the items first. When I select a Brand item, the cbo2 with the Model collection is populated. As you have noticed, both Comboboxes have SelectedItem property bound to the current object properties Brand and Model specifically. When I select a Model on the cbo2, it does not reflect to the current object's Model property. Anything i missed?
typo: the first combo is called cbo1, but the second combo is binding to cbxBrand; but since you say the Models do appear, I'm guessing this is OK in your actual source code, and you renamed it for the Question here?
Anyway, your code completely worked for me, I put a breakpoint on the setter of the Model property and it hit it no probs, so the only thing I can possibly guess at is the Window's DataContext maybe incorrect?
Can you post your code-behind (or ViewModel) ?