This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Hi. We are currently evaluating the LightSpeed ORM, using evaluation build 20091202, and have encountered a strange issue when pushing changes to the database.
The application in question is a small ASP.NET MVC application and uses the repository/per request design pattern described here. The issue arrises with a many-to-many table relationship, in this case 'users' and 'files'. A user is given permission to a file, which can be given to many users. To produce the issue, we: 1. Enumerate all the Files a given user has permissions for, using a LINQ query. 2. Update one of these files. Rather than updating the database, LightSpeed fails silently (the EntityState of the file entity remains Modified, and the database is not updated). The these steps are executed across multiple action requests, in between which the UnitOfWorkScope is disposed automatically. The issue can be circumvented if step (1) is performed using a seperate unit of work (by default the repo UnitOfWorkScope unit is used).
We have ASP.NET mvc test code which reliably reproduces the problem, and demonstrates the workaround. Any help would be greatly appreciated. Cheers. |
|
|
Hello Ben, Thanks for reporting this. You mention that the steps are executed across multiple requests. Is it possible that the File instance you are modifying was loaded through a different unit of work than the one you are saving? Since the Files are coming from the User, they will be loaded into the User's unit of work. The PerRequestUnitOfWorkScope should be returning the same UOW for the duration of the HTTP request but if you are keeping stuff in session state then you might have a stale UOW around. Try Debug.WriteLine-ing the hash code of the unit of work through which you load the User, and also the hash code of the unit of work on which you call SaveChanges, and check they're the same. If it is the same UOW, we'll be happy to investigate this for you. It would be great if you could post that test code for us (you can attach a zip file to a post via the Options tab). Also, could you attach a logger to the LightSpeedContext (context.Logger = new TraceLogger()) and let us know whether you see anything being logged during SaveChanges in the "silent fail" case, i.e. is LightSpeed sending nothing to the database, or is it sending an UPDATE to the database but it's somehow getting rolled back? |
|
|
Hi Ivan. Thanks for the prompt reply!
I've run through your suggestions. Results as follows: The TraceLogger() output shows that only SELECT queries are being sent to the database (Sqlite in this case, but also reproduced with SQL Server 2005). Stepping over the update procedure I see that no database activity takes place at all when .SaveChanges() is called. Now, I am keeping a User component in session. However, before that object is used I have been careful to use Repo.Attach(CurrentUser) to attach it to the current UnitOfWorkScope. Stepping carefully through and comparing the hashcodes for UnitOfWork(s) I see that the hashcodes for the UnitOfWorkScope.Current and CurrentUser.UnitOfWork are still different despite the Attach() call. A possible answer? I added an explicit call to .Detach() the user at the end of every request, just before the UnitOfWorkScope .Dispose() is called. CurrentUser.UnitOfWork is set to null. Now, after Repo.Attach(CurrentUser) I see that the hashcodes for UnitOfWorkScope.Current and the CurrentUser.UnitOfWork are the same, however .SaveChanges() call still fails (no query sent to db, EntityState unchanged).
Still stumped. I've attached the demo code for your perusal. It should be compile-and-go. Controllers/BaseController.cs holds most of the Repo creation/disposal code. Models/LightSpeedTestUser.cs holds the 'workaround' code.
Cheers
|
|
|
Some fodder for thought:
If the call to Repo.Attach(CurrentUser) is moved before the call to Repo.GetFile(id) (rather than after), then the .SaveChanges() call succeeds. I stumbled across this when I added some code to automatically .Attach() the CurrentUser when it is first requested. This always occurs first because of a null check in OnActionExecuting().
Problem (Technically) solved, but this is not what I would call expected behaviour. Am I missing something important? Cheers |
|
|
Aaaaahhhhhhhh......... I think I see what's going on. Yes, there is a key detail you're missing, but it involves understanding the subtleties of the implementation at a level you really shouldn't have to worry about. What's happening is that when you do the Repo.Attach(CurrentUser), it registers CurrentUser and any associated entities in the current unit of work. Now the unit of work works by maintaining an identity map, that is type + id => object instance. So let's see what happens to File #2 as your code runs (this is in the TestController.Third POST handler if you want to follow along). First, Repo.GetFile(2) is called and returns a LSTFile instance I'll call Instance A (which you store in the local variable 'file'). In the identity map, File #2 is mapped to Instance A. Second, Repo.Attach(CurrentUser) is called. In the identity map, User #1 (say) is mapped to CurrentUser. Third, the unit of work recursively sweeps CurrentUser's associations, writing them into the identity map. Eventually comes to a LSTFile entity with ID 2; I'll call this Instance B. It's not the same as Instance A because it wasn't in the identity map when Repo.GetFile(2) was called. It writes this into the identity map, overwriting the current entry. That is, File #2 is now mapped to Instance B. (Instance A still thinks it's associated with the UOW, which just adds to the confusion. It doesn't know it's been usurped from the identity map.) Fourth, the code modifies the Size property of file, which refers to Instance A. Finally, Repo.SaveChanges() is called. It runs over the identity map looking for Modified entities. When it comes to File #2, it goes and looks at Instance B, since that's what's mapped. But Instance B never got modified. It was Instance A that got modified. So the unit of work concludes that there's nothing to save. Hopefully this explains the various workarounds you've found. For example, if you do the Attach before the GetFile, then LightSpeed retrieves the existing File #2 from the identity map. (What I called Instance B above; no Instance A ever gets instantiated.) So Instance B gets modified, and Instance B remains in the identity map, and so File #2 is modified when SaveChanges gets to it. Similarly with using a separate unit of work. Unfortunately I don't have an elegant solution for this. We could make Attach throw an exception rather than overwrite an identity map entry, though this would be a bit tricky for us to implement, and I'm not certain it's always desirable behaviour. For the time being I can only offer the guidance "don't call Attach on a non-empty unit of work" (or at least do so only with caution). In fact we normally go even further than that and say "don't keep entities around between requests" -- instead, keep the ID, and reload on demand. Use eager loading and caching to mitigate performance concerns if it becomes a problem. In most cases the cost of reloading from the database is not critical, and it really does help to avoid lifetime issues and keep the code simple and clean! Anyway, sorry this has been a problem for you and that the explanation is so involved -- I hope at least it makes sense to you now! |
|
|
All has become clear! Thanks for your help. I've already moved the .Attach() command to an earlier stage in the request lifecycle, solving the immediate problem. But given your explanation I think I'll follow your advice and ditch stashing the User object in the Session (Just store an ID instead). Unfortunately there is too much I could be pushing into the session at the same time that I don't know about (or, more accurately, haven't thought about). LightSpeed has proven itself a great tool so far, and so has the support at Mindscape. Saw your talk on WPF MVVM at TechEd which was also great!
Cheers again, Ben |
|