none
MVVM EF binding not saving changes RRS feed

  • Question

  • While trying to debug a different problem I have stumbled into a binding issue from my view model where changes made to my view do not update the DB. All things being equal, these code pieces work:

    <CollectionViewSource x:Key="cvsClients" Source="{Binding Path=ClientList}"/>
    
    <DataGrid ItemsSource="{Binding Source={StaticResource cvsClients}}" 
                      AutoGenerateColumns="False" >
    
    
    
        Sub New(ByVal db As Context2)
            _context = db
            _rep = New Repository2(_context)
            _context.ClientList.Load()
            ClientList = _context.ClientList.Local
        End Sub
    
    

    But if I change the view model code above to call a local function that queries for active records, the data displays and the call to save appears to work but no changes are made to the DB.

        Sub New(ByVal db As Context2)
            _context = db
            _rep = New Repository2(_context)
            ClientList = New ObservableCollection(Of Client)(_rep.GetAllActiveClient())
        End Sub
    
    


    In both cases the DbContext SaveChanges() methods returns a integer count that appears to be the number of rows in the table, but only the first method actually commits the changes to the DB.

    The goal is to bind the XAML to a property in the view model which I can load with some LINQ queries. I thought this was possible but I'm not seeing why one method works and the other does not.
     


     

    Monday, November 21, 2011 5:01 PM

Answers

  • Hi,

    It's a bit difficult to exactly say whats your problem are since you don't show how you are modifying/adding/removing entities in your code. But here is what I suspect your problem is if you are modifying your data using standard DataGrid logic.

    The problem is that in your second function you don't work with the DbContext directly as you do in the first function since you are attaching your ClientList to a new ObservableCollection with all entities from your function.

    This causes that no changes (at least adding/removing, but maybe modifications too (not 100% sure)) are tracked by your _context.ClientList since you are actually working against your ObservableCollection and not the DbSets which holds track for you against the DbContext.

    To be able to track changes you either need to work directly against your _context.ClientList DbSet or you need to update it with ClientList before you call SaveChanges.

    Based on how GetAllActiveClient() looks like you may connect to the result of this function to to get a similar result. But the important thing then is that GetAllActiveClient() does NOT call ToList() or other functions that materializes a resultset.

     

     


    --Rune

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful" if the post helped you to a solution of your problem.
    • Marked as answer by pretzelb Wednesday, November 23, 2011 2:19 PM
    Monday, November 21, 2011 5:19 PM

All replies

  • Hi,

    It's a bit difficult to exactly say whats your problem are since you don't show how you are modifying/adding/removing entities in your code. But here is what I suspect your problem is if you are modifying your data using standard DataGrid logic.

    The problem is that in your second function you don't work with the DbContext directly as you do in the first function since you are attaching your ClientList to a new ObservableCollection with all entities from your function.

    This causes that no changes (at least adding/removing, but maybe modifications too (not 100% sure)) are tracked by your _context.ClientList since you are actually working against your ObservableCollection and not the DbSets which holds track for you against the DbContext.

    To be able to track changes you either need to work directly against your _context.ClientList DbSet or you need to update it with ClientList before you call SaveChanges.

    Based on how GetAllActiveClient() looks like you may connect to the result of this function to to get a similar result. But the important thing then is that GetAllActiveClient() does NOT call ToList() or other functions that materializes a resultset.

     

     


    --Rune

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful" if the post helped you to a solution of your problem.
    • Marked as answer by pretzelb Wednesday, November 23, 2011 2:19 PM
    Monday, November 21, 2011 5:19 PM
  • I went back and looked at the LINQ and I'm not sure why I had this extra step but I think this caused part of the problem:

        Public Function GetAllActiveClient() As ObservableCollection(Of Client)
            Dim results = From r In _context.ClientList
                Order By r.ClientName
                Select r
            Dim rc As IEnumerable(Of Client) = From x In results.AsEnumerable
                                               Select New Client With
                                                      {
                                                        .ClientID = x.ClientID,
                                                        .ClientName = x.ClientName,
                                                        .InsertDate = x.InsertDate,
                                                      }
            Return New ObservableCollection(Of Client)(rc)
        End Function
    

    Switching to this code allows updates to work but not ADD or DELETE:

        Public Function GetAllActiveClient2() As ObservableCollection(Of Client)
            Dim results = From r In _context.ClientList
                Order By r.ClientName
                Select r
            Return New ObservableCollection(Of Client)(results)
        End Function
    
    

    I forgot to include the declaration in the DbContext in the first post:

    Public Class Context2
        Inherits DbContext
        Public Property ClientList As DbSet(Of Client)
    
    

    Moving forward, is it correct to assume that the only way to have a DataGrid object peform all CRUD operations is to bind directly to DbSet declaration? The goal would be to have a DataGrid on a page with some filters to allow a user to modify or delete rows on the DataGrid and perhaps even add from that same filtered list. I've bound the DataGrid to a property on the view model and introduced commands to interact with the user to filter the data being pumped into the bound property.


    Monday, November 21, 2011 7:48 PM
  • Well,

    Yes, for your scenario. That is correct, otherwise you have to add/remove the entities for your DbSet yourself.

    You can listen to the CollectionChanged event of the ObservableCollection you create and manually add/remove entities to the DbSet based on the arguments to the CollectionChanged event.

    That would solve most of your problems without to much code.

    I don't have any other good suggestion on this one,


    --Rune

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful" if the post helped you to a solution of your problem.
    Monday, November 21, 2011 8:44 PM
  • Btw, forgot that earlier.. Local is actually a ObservableCollection that does the same.. It adds or removes entities on the DbSet on the actions Add, Remove and Replace.
    --Rune

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful" if the post helped you to a solution of your problem.
    Monday, November 21, 2011 9:21 PM
  • I found this blog post (http://blogs.msdn.com/b/adonet/archive/2011/02/01/using-dbcontext-in-ef-feature-ctp5-part-7-local-data.aspx) from the ADO.Net team helpful in explaining the details. It also points out that Local is an ObservableCollection which can be bound to, yet it slow and not advisible for large numbers of entities.

    So at a high level, you might have this framework:

    DbContext - Repository - View Model - View

    The property types being used to pass the data along might be something like this:

    DbSet (.Local) - ObservableCollection - ObservableCollection - ObservableCollection

    All of which is supported by LINQ in the DbContext (probably called via the Repository) that filters or queries for the entities you want in Local of the DbSet.

    I think what has me most confused is how the blogs on MSDN show different ways to accomplish binding or data retrieval without really giving concrete examples of best practices. For example, there is the note in the MSDN that "if you are dealing with thousands of entities in your context it may not be advisable to use Local" yet there is no example of what you SHOULD (or even can) do in the case of thousands of entities.

    I'd like to continue the conversation and get more advice on working without binding to Local on the DbSet and how much work it would take to manage the CRUD events with MVVM and a DataGrid. Each time I explore that option it seems like the amount of work that goes into the View and View Model is overly problematic and error prone. But I think that is probably off topic so I will just mark this one complete.

    Wednesday, November 23, 2011 2:18 PM