16 years of LLBLGen Pro!

On September 7th, 2003, we released the first version of LLBLGen Pro, v1.0.2003.1. 16 years later we're still here and have just released LLBLGen Pro v5.6. This long period of time between these two versions, a lifetime in software land, marks a tremendous journey. Let's look at a couple of differences between that first iconic release from 16 years ago and the latest release of today.

Getting v1.0.2003.1 up and running

For this article we had to dig up LLBLGen Pro v1.0.2003.1, and that turned out to be a bit of a challenge: we didn't keep binaries of versions before v1.0.2004.1, released almost a year after 1.0.2003.1. In our sourcecode control system (Subversion) we could go back to v1.0.2003.3 for the runtime framework and templates and a later revision of v1.0.2003.1 of the designer and drivers. The original v1.0 release wasn't obtainable anymore as we switched from Visual Sourcesafe to SVN a couple of months after the initial release. We did keep the original sourcesafe databases around in history backups but no sourcesafe service code to access them.

On the VM created from the original developer box running XP, frozen in time, we could build the sources targeting .NET 1.1 using Visual Studio 2003 and run them. To use the builds we needed a database and the old XP VM contained a SQL Server 2005 installation which was a bit of a challenge for v1.0.2003.1, as SQL Server 2005 introduced different meta-data schemas, but after a bit of debugging and copying some queries back to the old SQL Server driver it worked!

What changed in those 16 years?

To compare features is a tad long so let's focus on a few big parts:

User interface

The first version was built with the 'Magic' User Interface Library from Crownwood Consulting Ltd. It still exists, it nowadays goes by the name 'Krypton' and is open source. The screenshot below is taken on .NET 1.1 on Windows XP.

The latest version is built with the skinnable Winforms UI control set of DevExpress:

The user interface of v1.0.2003.1 suggests it's more than it really is: there's just one way of working: database first modeling, with just one database type supported: SQL Server, and for that database just one catalog per project. Additionally, there's not a lot of freedom with respect to modeling and editing: for instance in the entity editor you can't pick which field is mapped onto what target field.

In contrast, LLBLGen Pro v5.6's designer offers a flexible environment where you can model your domain model however you want: model first, database first, or a mix of both. You can choose what maps onto what, there's support for a variety of database types and if the database type supports it, multiple catalogs and schemas are supported. Additionally you can map your model to multiple database types at the same time (e.g. SQL Server and PostgreSQL), with e.g. one using model first modeling, and another using database first modeling.

The original user interface allowed limited editing and there was no undo/redo. We rewrote the designer and its object model from scratch in the v3.0 timeframe when model first and undo/redo were introduced. Undo/redo required a command structure to operate on the data in a fine grained manner to be able to undo a made change across an object graph in a single action. The base for this is the Algorithmia class library, which is open sourced on GitHub and which you can use in your own applications too to add undo/redo across an object graph.

The modeling experience

We started with just an entity model and support for calling stored procedures, supporting a single O/R mapper framework, our own LLBLGen Pro Runtime Framework. During the past 16 years a lot has changed in the .NET ORM landscape, one thing being that it's now OK to support other ORMs in your designer. Nowadays LLBLGen Pro supports a variety of ORMs (Entity Framework, Entity Framework Core, NHibernate, Linq to SQL and our own LLBLGen Pro Runtime Framework) and it's easy to add your own if you want to. Switching between these frameworks is a single click on a button.

Besides the already mentioned addition of model-first development, we also added another model type in the past few years: Derived Models. Derived Models are (potentially denormalized) models derived from the entity model and which can be used with document databases or as DTO models in service APIs. The generated code for these models comes with Linq projections to materialize the derived elements at runtime without any extra coding.

Model-first modeling required a user-friendly way to create and maintain the models and we achieved that through our Quick model technology, which allows you to quickly add or alter model elements with short text commands, paired with a visual representation of what you just changed. With typing just a few short commands you'll see your model grow visually in front of you.

The querying experience

LLBLGen Pro Runtime Framework v1.0.2003.1 had a typed query system, and entity classes derived from a base type, using the 'selfservicing' paradigm. The query system used generated objects to build your query with. This system is still part of the LLBLGen Pro Runtime Framework, but has been enhanced a lot to make life easier. Nowadays we call this the 'low level query API' and it's the foundation of the other query systems we offer, Linq and QuerySpec.

While it has the advantage that a model change, say a field got renamed, was picked up by the C# or VB.NET compiler, the price for that was that the queries became verbose. Let's look at an example query to compare the query systems of 16 years ago with the ones we have today: a query where we fetch all Customer entities which have an Order filed by the employee with ID 2, sorted by country and then city. This requires a join and a predicate as well as an orderby expression. The queries will use SelfServicing, as v1.0.2003.1 didn't support the more flexible and powerful Adapter paradigm yet.

// This query doesn't use the generated predicate/sorterclause factory which made life a little easier
// back then, but which aren't supported anymore for a long time. The query below still runs on today's runtime
// as backwards compatibility is a feature.
var customers = new CustomerCollection();
var joins = new RelationCollection();
joins.Add(CustomerEntity.Relations.OrderEntityUsingCustomerId);
var orderBy = new SortExpression();
orderBy.Add(new SortClause(EntityFieldFactory.Create(CustomerFieldIndex.Country), SortOperator.Ascending));
orderBy.Add(new SortClause(EntityFieldFactory.Create(CustomerFieldIndex.City), SortOperator.Ascending));
customers.GetMulti(new FieldCompareValuePredicate(EntityFieldFactory.Create(OrderFieldIndex.EmployeeId), 
                                                  ComparisonOperator.Equal, 2), 0, orderBy, joins);
var metaData = new LinqMetaData();
var q = from c in metaData.Customer
        join o in metaData.Order on c.CustomerId equals o.CustomerId
        where o.EmployeeId == 2
        orderby c.Country ascending, c.City ascending
        select c;
var results = q.ToList();
var qf = new QueryFactory();
var q = qf.Customer
          .From(QueryTarget.InnerJoin(qf.Order)
                .On(CustomerFields.CustomerId.Equal(OrderFields.CustomerId)))
          .Where(OrderFields.EmployeeId.Equal(2))
          .OrderBy(CustomerFields.Country.Ascending(), CustomerFields.City.Ascending());
var results = new CustomerCollection();
results.GetMulti(q);

Both the Linq and QuerySpec queries are converted by the runtime into the same objects created by the first query.

Performance

So how does the LLBLGen Pro 1.0.2003 runtime compare to today's runtime with respect to performance? To test that, we added a 1.0.2003.3 (the oldest templates / runtime source code we could trace back) codebase of AdventureWorks to our RawDataAccessBencher ORM fetch benchmarks. The results are below, where we compare it with a couple of other well known frameworks.

Non-change tracking fetches, sets

Non-change tracking fetches, set fetches (10 runs), no caching
------------------------------------------------------------------------------
Handcoded materializer using DbDataReader                        : 91,27ms (0,56ms)     Enum: 0,82ms (0,09ms)
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 107,32ms (1,22ms)    Enum: 1,05ms (0,09ms)
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco typed view with QuerySpec    : 111,72ms (1,70ms)    Enum: 0,89ms (0,12ms)
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco with Raw SQL                 : 112,84ms (1,00ms)    Enum: 0,89ms (0,13ms)
Dapper v1.60.0.0                                                 : 119,56ms (0,78ms)    Enum: 0,94ms (0,07ms)
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 133,50ms (0,90ms)    Enum: 1,12ms (0,09ms)
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3), DataTable based TypedList : 163,90ms (1,22ms)    Enum: 2,07ms (0,07ms)

Memory usage, per iteration
------------------------------------------------------------------------------
Handcoded materializer using DbDataReader                        : 15.211 KB (15.576.504 bytes)
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco with Raw SQL                 : 15.213 KB (15.578.472 bytes)
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 20.184 KB (20.668.592 bytes)
Dapper v1.60.0.0                                                 : 30.888 KB (31.629.968 bytes)
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco typed view with QuerySpec    : 31.920 KB (32.686.784 bytes)
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 32.536 KB (33.317.504 bytes)
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3), DataTable based TypedList : 42.089 KB (43.099.744 bytes)

Change tracking fetches, sets

Change tracking fetches, set fetches (10 runs), no caching
------------------------------------------------------------------------------
LLBLGen Pro v5.6.0.0 (v5.6.0)                                    : 178,41ms (1,31ms)    Enum: 6,21ms (0,31ms)
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 313,24ms (3,19ms)    Enum: 1,34ms (0,07ms)
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3)                            : 935,22ms (21,18ms)   Enum: 12,98ms (0,14ms)
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 1.637,94ms (26,73ms) Enum: 1,80ms (0,04ms)

Memory usage, per iteration
------------------------------------------------------------------------------
LLBLGen Pro v5.6.0.0 (v5.6.0)                                    : 52.868 KB (54.137.128 bytes)
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 82.851 KB (84.839.656 bytes)
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3)                            : 266.559 KB (272.957.160 bytes)
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 358.822 KB (367.434.256 bytes)

Non-change tracking fetches, individual element fetches

Non-change tracking individual fetches (100 elements, 10 runs), no caching
------------------------------------------------------------------------------
Handcoded materializer using DbDataReader                        : 0,11ms (0,00ms) per individual fetch
Dapper v1.60.0.0                                                 : 0,12ms (0,00ms) per individual fetch
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco with Raw SQL                 : 0,13ms (0,00ms) per individual fetch
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3), DataTable based TypedList : 0,20ms (0,01ms) per individual fetch
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco typed view with QuerySpec    : 0,22ms (0,00ms) per individual fetch
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 0,36ms (0,01ms) per individual fetch
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 0,52ms (0,01ms) per individual fetch

Memory usage, per individual element
------------------------------------------------------------------------------
Handcoded materializer using DbDataReader                        : 08 KB (8.192 bytes)
Dapper v1.60.0.0                                                 : 16 KB (16.384 bytes)
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco with Raw SQL                 : 16 KB (16.384 bytes)
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 56 KB (57.344 bytes)
LLBLGen Pro v5.6.0.0 (v5.6.0), Poco typed view with QuerySpec    : 64 KB (65.536 bytes)
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3), DataTable based TypedList : 96 KB (98.304 bytes)
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 120 KB (122.880 bytes)

Change tracking fetches, individual element fetches

Change tracking individual fetches (100 elements, 10 runs), no caching
------------------------------------------------------------------------------
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3)                            : 0,14ms (0,01ms) per individual fetch
LLBLGen Pro v5.6.0.0 (v5.6.0)                                    : 0,17ms (0,00ms) per individual fetch
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 0,40ms (0,02ms) per individual fetch
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 0,57ms (0,02ms) per individual fetch

Memory usage, per individual element
------------------------------------------------------------------------------
LLBLGen Pro v5.6.0.0 (v5.6.0)                                    : 48 KB (49.152 bytes)
LLBLGen Pro v1.0.2003.3 (v1.0.2003.3)                            : 56 KB (57.344 bytes)
Entity Framework Core v2.2.6.0 (v2.2.6.19169)                    : 56 KB (57.344 bytes)
Entity Framework v6.0.0.0 (v6.2.61023.0)                         : 136 KB (139.264 bytes)

Overall we made great progress in eliminating performance bottlenecks and memory consumption in the framework, while staying compatible with existing code bases and avoiding extensive rewrites.

What stayed the same

We could go on for hours to highlight advancements in the vast array of features and options the LLBLGen Pro system offers but instead we'd like to highlight something that stayed the same all those years: our fast and solid customer support. From the get-go we wanted fast and accurate support, close communications between the development team and our users we have maintained that throughout the years. For instance if you run into a bug, it's likely you'll get a fix for it within a day or two, maybe even sooner. This has been the case since day 1 and it is still accurate today.

Although we're 16 years older, we're still here and more alive than ever! If you have been using LLBLGen Pro in the past or are totally new to our system, it's never too late to get productive: join our large and loyal group of customers and experience that using databases in your code can be easy and efficient.

Happy coding!

Why wait?

Become more productive today.

Buy now    Download Trial