Actually, it's worse than I thought.  (Which is why I should add more
assertions to my tests...)  The real problem is that inserting and
updating only inserts/updates the immediate object, and not all
referenced objects.  Thus, this (paraphrased from WriteTest.cs):

        Category c = new Category {...};
        Product  p = new Product {...};
        c.Products.Add(p);
        db.Categories.InsertOnSubmit(c);
        db.SubmitChanges();
        
        // This works: the Category IS inserted
        Assert.AreEqual(1, db.Categories.Where(c => c.CategoryName == 
...).Count());
        // This fails: the referenced Product is NOT inserted
        Assert.AreEqual(1, db.Products.Where(p => p.ProductName == 
...).Count());

Looks like QueryBuilder.GetInsertQuery() and
QueryBuilder.GetUpdateQuery() are at fault, especially considering that
they appear to assume that only one statement will be required (while
more than one may be required when updating an entire object graph, as
the above example requires).

 - Jon

On Mon, 2009-05-11 at 01:10 -0400, Jonathan Pryor wrote:

> After more debugging, I'm heading to bed, but this is what I've found
> out: our change tracking isn't tracking changes. :-)
> 
> Specifically, when you have code like this (using something
> NUnit-testable instead of from NerdDinner): 
> 
>         Category c = GetExistingCategory();
>         Product p = new Product {...};
>         c.Products.Add(p);
>         db.SubmitChanges();
> 
> the newly added product isn't actually added to the database.  The
> problem appears to be because of MemberModificationHandler: it only
> tracks INotifyPropertyChanged and not INotifyPropertyChanging (see
> MemberModificationHandler.RegisterNotification()).
> 
> So, what happens is c is a tracked object (yay).  As such, it is
> registered with the EntityTracker (double yay).  However, for change
> tracking to work, MemberModificationHandler uses the
> INotifyPropertyChanged interface.  In this case, c.Products.Add(p)
> doesn't actually invoke
> MemberModificationHandler.OnPropertyChangedEvent(), because it isn't
> registered.
> 
> Specifically, the registered Category fires the
> INotifyPropertyChanging.PropertyChanging event, because (as far as it
> knows) c.Products will be changing.  However, c doesn't know what, if
> anything, will change.  c.Products.Add(p) eventually calls into
> Category.attach_Products(), which then effectively does 'p.Category =
> c'.  This last expression is what fires the
> INotifyPropertyChanged.PropertyChanged event, but only on p, which
> hasn't been registered with anything, and thus there are no handlers
> registered.
> 
> Oops.
> 
> The only solution I can think of is that MemberModificationHandler
> needs to listen to the INotifyPropertyChanging.PropertyChanging event
> (if available), and if it's fired it needs to check for changes on all
> properties of the changing object.
> 
> So I did this, and it still fails (see attached patch, which includes
> the unit test).  It properly detects that 'c' may have changed, but
> QueryRunner.Update() doesn't do a "deep" update; that is, it only
> updates the Category properties (CategoryName, Description, etc.) but
> not the values of anything attached to it (like the Category.Products
> collection).
> 
> Any ideas on how to resolve this?
> 
> Thanks,
> - Jon
> 
> On Sun, 2009-05-10 at 22:02 -0400, Jonathan Pryor wrote:
> 
> > I already fixed this, and it has been committed to svn.
> > DataContext.SetEntitySetQueries() was being called because of
> > DataContext._GetOrRegisterEntity(), which in turn was called by
> > DataContext.Register(), which was called by
> > DataContext.SubmitChanges().  (Just check the callstack in the
> > original message...)  The problem was twofold:  (1) DbLinq's
> > EntitySet<T> was screwy (though I blame a lot of that screwiness
> > on .NET's EntitySet<T> -- see the new EntitySetTest.cs file, and
> > laugh hysterically when you see when the EntitySet<T>.ListChanged
> > event is raised), and (2) calling dinner.RSVPs.Add(rsvp) caused the
> > EntitySet<RSVP> source to be created, and EntitySet<T>.SetSource()
> > cannot be called again once EntitySet<T>.HasLoadedOrAssignedValues
> > is true.  Since DataContext.SetEntitySetsQueries() tried to do just
> > that, it threw an exception.
> > 
> > Now I'm working on a related bug: subsequent updates aren't tracked.
> > To wit: 
> > 
> >         Dinner dinner = new Dinner {...};
> >         RSVP rsvp = new RSVP();
> >         rsvp.AttendeeName = User.Identity.Name;
> >         dinner.RSVPs.Add(rsvp);
> >         db.Dinners.InsertOnSubmit(dinner);
> >         db.SubmitChanges();
> >         
> >         // The above now works.  Now...
> >         
> >         RSVP rsvp2 = new RSVP();
> >         rsvp2.AttendeeName = "whatever";
> >         dinner.RSVPs.Add(rsvp2);
> >         db.SubmitChanges();
> >         // rsvp2 is NOT submitted to the database.
> > 
> > I'm still tracking down why this happens.  The attached patch file
> > recreates this same scenario within the unit tests, and also
> > (thankfully) fails.
> > 
> > What I would appreciate is if you could look into the cache issues
> > that I'm seeing. :-)
> > 
> > Thanks,
> > - Jon
> > 
> > On Sun, 2009-05-10 at 23:41 +0100, Giacomo Tesio wrote:
> > 
> > > I think this is due to the "dinner.RSVPs.Add(rsvp);"  line where
> > > you actually fill the EntitySet.
> > > 
> > > The DataContext.SetEntitySetQueries() should not run on just
> > > inserted entity, since they are yet filled.
> > > 
> > > 
> > > Tomorrow I'll try to fix this...  
> > > 
> > > 
> > > Giacomo
> > > 
> > >  
> > > On Fri, May 8, 2009 at 4:59 AM, Jonathan Pryor <[email protected]>
> > > wrote:
> > > 
> > >         Still continuing with my "NerdDinner on Mono" effort, and
> > >         hitting the following issue:
> > >         
> > >         When I attempt to add a new Dinner, NerdDinner effectively
> > >         does: 
> > >         
> > >                 Dinner dinner = new Dinner {...};
> > >                 RSVP rsvp = new RSVP();
> > >                 rsvp.AttendeeName = User.Identity.Name;
> > >                 dinner.RSVPs.Add(rsvp);
> > >                 db.Dinners.InsertOnSubmit(dinner);
> > >                 db.SubmitChanges();
> > >         
> > >         This fails, with: 
> > >         
> > >                 System.Reflection.TargetInvocationException: Exception 
> > > has been thrown by the target of an invocation. ---> 
> > > System.InvalidOperationException: The EntitySet is already loaded and the 
> > > source cannot be changed.
> > >                   at 
> > > System.Data.Linq.EntitySet`1[NerdDinner.Models.RSVP].SetSource 
> > > (IEnumerable`1 entitySource) [0x00000] 
> > >                   at (wrapper managed-to-native) 
> > > System.Reflection.MonoMethod:InternalInvoke 
> > > (object,object[],System.Exception&)
> > >                   at System.Reflection.MonoMethod.Invoke (System.Object 
> > > obj, BindingFlags invokeAttr, System.Reflection.Binder binder, 
> > > System.Object[] parameters, System.Globalization.CultureInfo culture) 
> > > [0x000ca] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/corlib/System.Reflection/MonoMethod.cs:169
> > >  
> > >                   --- End of inner exception stack trace ---
> > >                   at System.Reflection.MonoMethod.Invoke (System.Object 
> > > obj, BindingFlags invokeAttr, System.Reflection.Binder binder, 
> > > System.Object[] parameters, System.Globalization.CultureInfo culture) 
> > > [0x000e5] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/corlib/System.Reflection/MonoMethod.cs:179
> > >  
> > >                   at System.Reflection.MethodBase.Invoke (System.Object 
> > > obj, System.Object[] parameters) [0x00000] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/corlib/System.Reflection/MethodBase.cs:111
> > >  
> > >                   at System.Data.Linq.DataContext.SetEntitySetsQueries 
> > > (System.Object entity) [0x00173] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/DataContext.cs:608
> > >  
> > >                   at System.Data.Linq.DataContext._GetOrRegisterEntity 
> > > (System.Object entity) [0x00015] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/DataContext.cs:468
> > >  
> > >                   at System.Data.Linq.DataContext.Register (System.Object 
> > > entity) [0x0000d] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/DataContext.cs:675
> > >  
> > >                   at System.Data.Linq.DataContext.SubmitChanges 
> > > (ConflictMode failureMode) [0x000a6] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/DataContext.cs:378
> > >  
> > >                   at System.Data.Linq.DataContext.SubmitChanges () 
> > > [0x00000] in 
> > > /home/jon/Development/mono-HEAD/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/DataContext.cs:339
> > >  
> > >                   at NerdDinner.Models.DinnerRepository.Save () [0x00000] 
> > >                   at NerdDinner.Controllers.DinnersController.Create 
> > > (NerdDinner.Models.Dinner dinner) [0x00000] 
> > >         
> > >         Suffice it to say, this doesn't happen under .NET's
> > >         System.Data.Linq.  I'm wondering if anyone has seen a
> > >         similar error to this before, and/or knows what an
> > >         appropriate fix would be.
> > >         
> > >         Thanks,
> > >         - Jon
> > >         
> > >         
> > >         
> > >         
> > > 
> > > 
> > > 
> > > 
> > 
> > 
> > 
> 
> 
> > 

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"DbLinq" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/dblinq?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to