NHibernate Tooling Review: LLBLGen Pro 3.0
This is part of what looks to be long series of posts about tooling relating to NHibernate.
In this case, I want to talk about LLBLGen Pro.
LLBLGen Pro 3.0 is an OR/M tool that also comes with an NHibernate option. Because it is the GUI tool to manage NHibernate mapping that I am testing, I am going to show a lot of screenshots. Also, please note that I am using this without any foreknowledge, reading the docs, or anything. I am also not going to do an in depth discussion, just use it to see how those things are working.
After installation, we start a new project:
Which give us the following (scary) dialog. Note all the tabs in the bottom. There is a huge number of options and knows that you can tweak.
The next step that I wanted to do is to see how it handles an existing data model, so I decided to add the test model that I use for NHibernate:
This takes about zero time and gives us the entities. It properly recognize 1:m associations, but I had to define the m:n associations myself. I am not sure if this is what I am supposed to do or if I just missed something. What is impressive is that the schema in question is actually being generated from NHibernate through the SchemaExport tool. So we went a full cycle, NHibernate mapping, schema, extracting everything to the following model:
I wonder if I can get LLBLGen Pro to read a set of NHibernate mapping as well, because this view of the mapping can be very useful.
But enough talking about the UI, there are a lot of tools that have great UI, I want to see what code it generates.
As an aside, I like this part of the code generation:
Not so much because what there is there, but because it implies how the code generation process itself is structured.
I was surprised when I saw this:
It brought back flashbacks of 1.0, but they are empty, I assume that this is vestigial remain in the template.
I don’t like that CategoriesPost is there, but I couldn’t figure out how to remove it, this isn’t an entity, it is an association table.
Looking at the code, there is one thing that really bothers me, take a look at the property definitions. How do you set that?
I am pretty sure that I did something to cause that, but I really don’t have any idea what.
Update: (This is written before the post is published, but after I had time to review what I did). It appears that I played with too many settings, there is an option called EmitFieldSetter that I accidently set to false. The idea behind this setting that you want to allow setting properties via methods, not via direct properties.
I’ll ignore that for now, and look at the rest of the code. In addition to the Model project, LLBLGen Pro also generates the Persistence project:
I was quite surprised to see that it uses class maps, but then I looked at the code:
/// <summary>Represents the mapping of the 'Blog' entity, represented by the 'Blog' class.</summary> public class BlogMap : ClassMap<Blog> { /// <summary>Initializes a new instance of the <see cref="BlogMap"/> class.</summary> public BlogMap() { Table("[dbo].[Blogs]"); OptimisticLock.Version(); LazyLoad(); Id(x=>x.Id) .Access.CamelCaseField(Prefix.Underscore) .Column("Id") .GeneratedBy.Identity(); Map(x=>x.AllowsComments).Access.CamelCaseField(Prefix.Underscore); Map(x=>x.CreatedAt).Access.CamelCaseField(Prefix.Underscore); Map(x=>x.Subtitle).Access.CamelCaseField(Prefix.Underscore); Map(x=>x.Title).Access.CamelCaseField(Prefix.Underscore); HasMany(x=>x.Posts) .Access.CamelCaseField(Prefix.Underscore) .Cascade.AllDeleteOrphan() .Fetch.Select() .AsSet() .Inverse() .LazyLoad() .KeyColumns.Add("BlogId"); HasManyToMany(x=>x.Users) .Access.CamelCaseField(Prefix.Underscore) .Cascade.AllDeleteOrphan() .Fetch.Select() .AsSet() .Table("[dbo].[UsersBlogs]") .ParentKeyColumns.Add("BlogId") .ChildKeyColumns.Add("UserId"); } }
That is highly readable, and a lot of people prefer that, and I guess it is easier than generating the XML.
Did you see the SessionManager class? It also handles all the wiring of NHibernate, so you can start using it by just getting the session:
SessionManager.OpenSession()
Overall, very impressive. From installation to working code (except that annoying no setter thingie), it took mere minutes, and the code is clean. That is one of the things that I always hated with code generators, they generally produce crappy code, so I was very happy to see how the code looked like.
Now, after the whirlwind tour, let me see if I can look at more of the options.
Oh, it looks like it can generate HBM files, let us give that a try:
<class name="Blog" table="[dbo].[Blogs]" optimistic-lock="version" > <id name="Id" column="Id" access="field.camelcase-underscore" > <generator class="identity"/> </id> <property name="AllowsComments" column="AllowsComments" access="field.camelcase-underscore"/> <property name="CreatedAt" column="CreatedAt" access="field.camelcase-underscore"/> <property name="Subtitle" column="Subtitle" access="field.camelcase-underscore"/> <property name="Title" column="Title" access="field.camelcase-underscore"/> <set name="Posts" access="field.camelcase-underscore" cascade="all-delete-orphan" inverse="true" fetch="select"> <key> <column name="BlogId"/> </key> <one-to-many class="Post"/> </set> <set name="Users" access="field.camelcase-underscore" table="[dbo].[UsersBlogs]" cascade="all-delete-orphan" fetch="select"> <key> <column name="BlogId"/> </key> <many-to-many class="User"> <column name="UserId"/> </many-to-many> </set> </class>
There is one main thing to note here, this looks pretty much like it was written by a human. There are still some things to improve, but compare that to the output of Fluent NHibernate, and you’ll see that this is generating something that is much easier to work with.
Some things that still bothers me:
- Kick all the access=”field.camelcase-underscore” to the default-access on the <hibernate-mapping> element, that would remove a lot of duplication.
- The table=”[dbo].[UsersBlogs]” should be table=”`UsersBlogs`” schema=”`dbo`”, and I would skip the catalog if it is dbo already.
- The class contains an optimistic-lock=”version”, but there is no version column, so this should go away as well. The same error appears in the fluent nhibernate mapping as well.
Let us see what else I can play with.
There is something called Typed Views, which I didn’t understand at first:
It appears that instead of generating an entity (which has associations, collections, references, etc), this build just a bare bone class to hold the data. The purpose, I believe, is to use this for reporting/binding scenarios, in which case a view model is much more appropriate. This is one features that the CQRS guys are probably going to like.
Oh, I found the m:n option:
With that, I get:
I probably need to change the name generation pattern, though :-)
I would suggest running the labels on the UI through PascalCaseToSentence, though. That is, instead of having to read AutoAddManyToManyRelationships, turn that to “Auto add many to many relationships”.
There is another feature, called TypedList, which gives you a query designer for building NHibernate queries (they are translated to HQL) which looked nice, but I haven’t looked at too closely. The way I usually work, queries are too context sensitive to allow to create the sort of query library that would make this feature useful.
This has been a very short run through LLBLGen Pro in its NHibernate mode. Overall, it looks very impressive, and it generates the best code that I have seen yet out of a tool. Good enough to take and use as is.
The only detraction that I could find is the name, (yes, I know that I talked about this before), LLBLGen Pro just doesn’t roll off the tongue.
Comments
Short comments:
"* Kick all the access=”field.camelcase-underscore” to the default-access on the <hibernate-mapping element, that would remove a lot of duplication."
Yes, we'll work on that next week
"* The table=”[dbo].[UsersBlogs]” should be table=”
UsersBlogs
” schema=”dbo
”, and I would skip the catalog if it is dbo already."the idea is to generate catalog + schema name in the table name, as you can have multiple catalogs in the project and map a single entity model onto it. For a single catalog/schema setup, it probably makes sense to use the schema attribute instead. We went for the name of the full target into the target name. This also produces queries with the catalog and schema name embedded, which is more efficient for RDMS caches
"* The class contains an optimistic-lock=”version”, but there is no version column, so this should go away as well. The same error appears in the fluent nhibernate mapping as well."
That's indeed a little bug we'll fix the coming week. In the settings, you can set it to 'none' to work around it.
V3 is the first llblgen pro designer which also has model first functionality instead of the database first functionality we had before that: there's a rich range of model first tools, including a text-DSL based system allowing you to quickly model a set of entities and create mappings and relational data from that, and much more.
I've used LlblgenPro for many years with great success. Now v3 supports not only the LlblgenPro runtime but NHibernate, Entity Framework v1 and v4 and Linq2Sql. I'm starting to work with NHibernate for the first time. Looks like my long time tool will help me as I do.
Guys at my office often forget the name of the product and refer to it as the "LL Bean thingy"
"There are still some things to improve, but compare that to the output of Fluent NHibernate, and you’ll see that this is generating something that is much easier to work with."
Disagreed. I don't know how people can still tolerate working with XML. Fluent is the only reason I ever went back to NH, because it was absolutely painful having to write XML.
I prefer to write XML, I don't know why but it feels like a more natural way to map classes
@Jesus
It's about maintainability and what's the 'source' of the entity class + table: the abstract entity definition. Everything is derived from that: the entity class and the target table(s). As they're reflecting the same definition, one can define a mapping between them and allow transportation of an instance of such an abstract entity definition (== data) flow from class instance to table and back. changing the entity definition therefore has its reflection on class and table and thus mappings. that's why using a tool for that is beneficial: it keeps these elements in sync for you.
Nice review. This is actually the first time I've had a good look at LLBLGen, so thanks for that.
Being that Fluent NHibernate is my doing, I feel I have to comment on this:
"There are still some things to improve, but compare that to the output of Fluent NHibernate, and you’ll see that this is generating something that is much easier to work with."
To be fair, it's was never the intention of Fluent to create XML for user consumption, it purely does it to satisfy NHibernate. Minor point though, and I'm only being pedantic.
Good review, I'm looking forward to the other tools.
LLBLGen seems to have a huge and loyal following. Thought, I've personally never seen anything that made me feel like it justified its steep price. This post, while interesting, has not changed that.
James,
The problem with FNH mapping is that when there is a problem, that is how you debug it.
That is why I find it annoying that FNH is outputting every single option
@mattmc3:
As it can do things in a minute you have to spend hours on manually by writing the mapping files/classes by hand, creating the DB by hand (if you're working model first), you're saving a lot of time, you can spend on other parts of the project. This makes you spend less time on a project so you're less expensive for your client. As you spend less time on the project, you can do more projects, and make more money. Also, if your competition uses tooling for plumbing (because that's what mapping / entity classes are) and you don't, you have a disadvantage.
In the end it comes down to: do you want to invest a little money to save money. Whether that's worth it is of course up to you, but it's not as if spending the money on it is not giving any financial gain, on the contrary. Similar to tools like Oren's *Prof tools: they make it way easier to get insight in what's going on in your software and with that cutting down time spend on tracking down issues you'd otherwise spend considerable time on and always on the least convenient moments.
Ayende: I'll admit debugging is a bit of an issue with a more verbose XML output; however, "outputting every single option" is a bit of an exaggeration.
@Frans - Thankfully, the choice isn't between expensive tools or no tools at all as you suggest.
@mattmc3:
I don't know how much you make per hour, but even if it's in the range of 20$/hour it's easy to gain it back. The thing is: if you for example have a 50 entity system, you need to create the DB, all 50 classes and 50 mapping files (or less if you use inheritance), debug them, and then you can start working on your client's system functionality. I don't know how much code you can write in an hour, but I bet you can't write 50 mapping files, 50 class files and create the DB without a single error in 1 hour, so it will likely take you a while, say a couple of hours.
Or do you think the time you have to spend on all that is 'not time spend on the client's project' ?
I'm not your client, so if you want to spend many hours doing dull work, which can be taken over by a machine, be my guest. if your competition can do it quicker with less problems due to advanced tooling, you're not competitive anymore and will lose work. Maybe not overnight, but everything counts: the more time you have to spend on overhead, the less time you can spend on the actual project unless you file more billable hours.
I don't care whether you buy my work or not, I just find it wrong to state that writing a lot of plumbing code is 'free'. It's not.
Frans,
I am not sure that I agree with your math.
I certainly agree on the value of tools :-), but writing 50 entities up front is something that is usually common in BDUF scenarios.
If you are doing that, it isn't going to matter how you are doing it. You are going to waste a lot of time without a lot of knowledge of the application itself.
It is much more common to do 1 - 3 entities at a time.
The cost is still real, but it is amortized over a lot longer.
I am less concerned about the cost, actually, what I care about is the frustration factor of having to go through seven different steps to get something done.
Tools can cut that much more easily. And considering the sub 500$ that most tools cost, it is only a few days of work even if you make 20$ - 30$ / hr.
yeah, I wasn't suggesting one should hammer out the classes up front, but along the way the time spent on the overhead adds up. :) If you start out with a couple of entities and it grows and grows, you will even more run into time consumed with changing plumbing code/mappings/tables.
Though an interview with a domain expert already gives you a lot of ideas and insights in what entities you can recognize in the problem domain and how they likely relate to each other. How they look in detail (which fields, which types these fields might have etc.) that's the detail which is filled in along the way.
That's also the focus of quickmodel, the text-based DSL system in llblgen pro v3: you can sit down with the domain expert, interview him/her and type in facts, like customer 1n order. The model grows visually and in the project and the domain expert can see how things relate. How things are filled in in detail, can be done later, e.g. grouping of fields in value types, that's not something you'd want to do directly up front, so a more top-down approach but that alone already forces you to focus on what the problem domain is all about and what you can recognize in it. I don't think that's a bad thing.
Comment preview