The current client I am working with is using the LLBLGen Pro code generator by Solutions Design, and it rocks! After seeing this product in action I'm not sure why everyone isn't using it. If you are not you really owe it to yourself to check out the demo. (Thanks Robbie for showing me the light)
So far I have started one new application using LLBLGen, one of the other developers has started a new application using it, and we just finished converting an existing app from using datasets to using a generated DAL. I'd estimate that we removed 60-75% of the code that was in the application while we were converting it. Whole subroutines were nuked because they were no longer needed. Plus we got to redesign and improve the front end since it was so much easier to find related entities and use them, so even the business users are going to be impressed with the changes. The lazy-loading feature also provided a noticable performance boost.
I'd guess that we deleted at least a month's worth of work that was setting up the dataset and datatables that the application used. And it took us only an hour or so to setup the replacement! Modifying the rest of the code to use the entities and collections instead of dataset and datatables took about a week for this application (a fairly simple windows application with a couple of main data forms and a few maintenance forms). I think we could have been done faster if we hadn't taken the opportunity to clean up the database at the same time (adding foreign keys, removing unused columns, etc).
But all is not roses. Now that we have a few applications that are using this thing over here at Ledcor the "can we do this?" questions are starting to pop up. Most of them are pretty simple, but every once in a while something isn't so easy. Or it is easy and the documentation isn't terribly clear. Or it's really easy and I'm just having a slow day. Still, I figure any day that you learn something is a good day, so...
Today I learned:
The guy building the other new application using a LLBL data layer was having a problem getting one of his entities to update. He setup his entity, loaded it from the database, modified a couple of values and then fired the .Save method. Seems pretty simple, right?
aEntity.Save()
However, the changes were not getting written back to the database.
We added some debug.prints to his code to check the IsDirty flag on the entity before and after the Save method was called. Sure enough, it was dirty before we told it to save the changes, and it came out the other side claiming to be completely up to date. Hmm...
System.Diagnostics.Debug.Print("IsDirty flag before save: {0}", aEntity.IsDirty)
aEntity.Save()
System.Diagnostics.Debug.Print("IsDirty flag after save: {0}", aEntity.IsDirty)
IsDirty flag before save: true
IsDirty flag after save: false
So we checked the return value of the .Save method. Since LLBL is so nice to pass one back surely it would tell us if something went wrong during the update. WRONG. It returned true. We checked the documentation to make sure it wasn't supposed to be returning false for some reason, but nope. The entity was convinced it was clean now.
After a bit more headbashing and an uncountable number of mutterings and un-printable rants, we finally figured out what the problem was. We had gotten this message when we refreshed the LLBL project from the database, but hadn't paid it too much attention. Oops.
“WARNING! There are one or more tables which don’t have a primary key set and when used as an entity will probably not result in the code you want.”
"probably not result in the code you want" is a serious understatement. This causes the LLBL Gen code to completely skip the updates on tables that don’t have Primary Keys. Instead of comparing all of the database fields to their original values (a la Access), it just refuses to even try. You can configure LLBL to output its SQL scripts as it runs them, and the .Save method didn't result in any kind of update statement being run at all.
Ok, I can understand why it doesn't work, and I agree with adding primary keys to the table so you can conclusively determine which row you are trying to update or delete. However, the definition of "probably not result in the code you want" could have been a little clearer. How about "These entities will not be able to update or delete their data"?
In yet another odd turn of events, the Insert statements fire properly. Obviously you don't need a Primary Key in order to insert a new row, but why make the entity able to insert when it can’t update or delete? Wouldn't it be more consistent to just make the entity read-only? At least then it just reads data, and you know it just reads data. Having it able to insert new rows but not push update makes it look broken. And why, oh why, would you have the .Save method return true when it can't run the update? That sounds like a pretty big frickin error to me! Well, maybe not an error since it is expected behavior, but some warning would have been nice. And even if you argue that it shouldn’t return false, why the heck would it flip its dirty flag when it is incapable of updating the database? Isn't that the definition of dirty for an entity, when the data in the entity doesn't match the data in the database? An entity that is changed, but which cannot push those changes to the database, can only become clean by re-reading the database, not by calling an .Save method that does nothing.
So, what I learned today is...
- Always have a primary key on your tables (duh)
- If you see this message when refreshing your catalogue in LLBL Gen Pro, see #1
- A return value of true doesn't always mean all is ok, sometimes it just means "don't you worry your pretty little head about it"