For those that care, this has been fixed in r1073 (along with the longest commit message in project history).
- Jon On Mon, 2009-05-11 at 17:18 -0400, Jonathan Pryor wrote: > Attached is my current patch to make this work. It doesn't work. > > The approach looks at all properties of the object we're > inserting/updating, looking for EntitySet<T>'s, and if any are found > it then inserts/updates the items found within the EntitySet<T>'s. > This appears to work properly. > > The current problem is that after the Category is inserted, > Category.CategoryID is updated. However, the Product doesn't see the > new CategoryID value, so it tries to insert using 0 as the CategoryID, > which fails due to a Foreign Key constraint violation. > > What I'm currently wondering is how the Products attached to the > Category are notified that the CategoryID has changed. The > DbLinq-generated Category and Product classes seem to be similar > enough, and INotifyPropertyChanged/etc. aren't used within the file, > so I'm not sure what mechanism is used to update all Products attached > to the Category. > > Thoughts? > - Jon > > On Mon, 2009-05-11 at 20:22 +0100, Giacomo Tesio wrote: > > > Than probably its related with the InsertOnSubmit(). > > > > Table<T> refer to the datacontext and surely register the entity. > > While registering it's probably deeply visited to locate any > > EntitySet and EntityRef filled. > > The entities filled are then registered (note that entity > > registration is idempotent: you should be able to register an entity > > many time without any problem) > > > > I try to take a look at it, now... > > > > > > Giacomo > > > > On Mon, May 11, 2009 at 6:29 PM, Jonathan Pryor <[email protected]> > > wrote: > > > > It's not a dumb question, and yes it passes on linq to sql. > > > > WriteTest.cs (which contains this test) is part of the > > DbLinq.SqlServer_test_strict project, which builds against > > SQL Server. These tests pass within that project. > > > > I'm not sure how Linq to SQL does it (is it EntitySet<T> > > that does it, or something else), but it is supported. I > > doubt that EntitySet<T> is directly involved, though, since > > there isn't any connection between EntitySet<T> and > > DataContext (so there'd be no way afaik for > > EntitySet<T>.Add() to implicitly register values). > > > > - Jon > > > > > > > > On Mon, 2009-05-11 at 17:26 +0200, Giacomo Tesio wrote: > > > > > I know it's a dumb question, but... do such a test pass on > > > linq to sql? > > > > > > I would imagine that you should add the product to the > > > product list too, to make it submitted. > > > I mean you should > > > > > > db.Products.InsertOnSubmit(p); > > > > > > > > > > > > The alternative would be the EntitySet keeping track of > > > entities added... (or asking to the datacontext to > > > register each added entities... > > > > > > > > > If it should work, it should not be too complex to do.. > > > > > > > > > Giacomo > > > > > > > > > On Mon, May 11, 2009 at 4:55 PM, Jonathan Pryor > > > <[email protected]> wrote: > > > > > > 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 -~----------~----~----~----~------~----~------~--~---
