Occasionally I find myself struggling with what I expected to be a simple solution. I write my code, but when I run it something is wrong. I usually have the gut feeling that it’s a simple configuration change, data issue, or missing function call. I first consult the internets or a book. I then fall back on .NET Reflector, mindless random changes to source code, friends, caffeine or distractions.
This weekend I found myself struggling with such an issue on a “just for fun” project that I started. I spent about four hours researching and trying different things. The whole time I knew that I could re-design my library around this problem, but I was more interested in understanding the problem and in preserving my original design.
My data access layer implements a stateless repository pattern. I’ve chosen this pattern, because I may want to use this project in the future to experiment with other data stores such as cloud storage or writing my own lightweight database engine. The physical database is a SQL Express database and I use LINQ to SQL to handle ORM and concurrency. With this approach I was able to quickly implement Inserts and Selects. However when I added Update I received the following NotSupportedException.
An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.
Being familiar with LINQ to SQL, I somewhat expected this issue. The DataContext objects in LINQ to SQL act as a unit of work. They provide change tracking, lazy loading, and concurrency checking. For a given data object, you are expected to submit the changes on the same DataContext that created the object. Since my repository class is stateless, I Dispose() the DataContext object immediately after returning the Select or Insert results.
I didn’t expect this to be a big deal because LINQ to SQL provides a mechanism for attaching a data object to a new DataContext. The Attach() method has several overloads each providing for a different way to enforce concurrency. I took the approach of adding a Timestamp field to each of my tables and configuring LINQ to SQL to use those fields as row versions. I quickly made this change, but received the same error. This is when I started spinning my wheels.
Thinking that I was simply overlooking something, I reverted my changes (side note: ask me why I love SVN) and tried both of the other Attach() strategies without success. I searched the internets and found reports of similar issues; those solutions didn’t appear to help either.
I decided to simplify the problem. Like any good programmer, I created a very simple “Foo” table with one record in it. I then selected that record > Disposed() it’s DataContext > Created a new DataContext > Attached the record > and successfully submitted the change. Now I had something. What was different?
The difference was that the non-Foo tables all had relationships to other tables. Although I was attaching the entity that I wanted to update, LINQ to SQL was unable to attach the related entities. I found solutions for detaching the related entities and for creating DataContexts specific to the CRUD operations without the relationships. I settled on a simpler solution of setting DeferredLoadingEnabled = false when I created my DataContext.
The whole time I was struggling with this I could have quickly re-designed my way out of the problem by:
- Extending the life of my DataContext
- Dumping the Repository Pattern
- Dumping LINQ to SQL
Any of these would have been acceptable workarounds, but each would have had a cost associated with it:
- Degrade the loose coupling provided by the Repository pattern by forcing consumers to be aware of the lifetime of data objects
- Would lose the ability to easily reuse this application to explore other data stores in the future
- Missed out on learning something new; probably would have avoided LINQ to SQL for similar solutions in the future > forgoing the productivity benefits of LINQ to SQL.
In the end, I feel that sticking with this problem made me a better developer by exercising my troubleshooting skills and by building better understanding LINQ to SQL. It also gave me a solution that better matched my goals for this project. I am however aware that I spent way too much time effectively writing one line of code. A client or employer may not have been very happy had I done this on their time, although I suspect that an employer wouldn’t mind as much since they also benefit from my knowledge++.
What do you think?
As developers we all get stuck on these types of things. What are your thoughts? Is it best to always push through the problem or is it better to work around the problem? Obviously tight time constraints can control the decision, but how do you handle the time-consuming problems in your day-to-day development?