Freedom in Form

Mac Libby

Evan Howlett

It's with great pleasure we announce Form 2.0. This release contains 3 major features:

  • Transaction Support
  • A Refactor
  • Performance Improvements

Transaction Support

Transactions ensure all operations performed inside one either all happen or none happen. This is the A, or Atomic, in ACID and therefore is critical to support. Adding support for this also brings some performance benefits around inserting, updating, and deleting multiple records at a time.

A Refactor

Form was built in a traditional functional style with the state at the tail position of the parameter list. This was the inspiration of the "fluent" API because the function would always return the modified state and therefore would be easily piped (or method-access syntax'd) into more commands. However, after heavy usage, we've decided to swap that to be in the front. The API now reads:

Orm.func{< ^T >} : OrmState -> Option< DbTransaction > -> { queryModifier(s) } -> { ^S }

Where ^S is either ^T or a sequence of ^T, depending on the function.

As the state isn't being modified and isn't the main subject in most of the API, this refactor allows for better piping and makes end-use code less noisy. Here's an example of a very simple data pipeline:

Form 1:

Orm.selectAll< ^T > state1
|> Result.map (fun data -> Orm.insertMany< ^T > false data state2)

vs.

Form 2:

Orm.selectAll< ^T > state1 None
|> Result.map (Orm.insertMany< ^T > state2 None false)

Performance Improvements

Performance over Form 1 has been significantly improved. Our goal was to be within 30% of Dapper - the veritable standard in C# - and we're thrilled to say that we have not only attained that goal, but even surpass them in several instances!


BenchmarkDotNet v0.13.7, Windows 11 (10.0.22621.2134/22H2/2022Update/SunValley2)
12th Gen Intel Core i9-12900K, 1 CPU, 24 logical and 16 physical cores
.NET SDK 7.0.306
  [Host]     : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2 DEBUG
  Job-LKANEO : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2

InvocationCount=1  UnrollFactor=1

Tables showing performance comparisons for select, insert, and update SQL commands between Form, Dapper, and Microsoft's ADO.Net.

The nerdy bit

Additionally, as many may know, Form depends heavily on the use of reflection, which is a runtime cost. In our pursuit of optimizing Form, we have managed to reduce the amount of runtime reflection used in the ingestion of data by memoizing the reflection results for use whenever data is being ingested - this additions bears remark here because it provided us with a massive performance increase that really blew our socks off. Like mother always use to say, "Remember little HCRD, memoize your expensive runtime calculations in order to optimize performance."

Fans of Form can expect many more optimizations in the coming weeks, to the end of polishing up our reflection memoization in order to ensure long running processes - such as servers - don't gobble up memory over time.

Wrapping up

We have achieved all this while still providing the type-safety and correctness we did in version 1 - to a greater extent, even. There is more to come with Form. Support for joins declared on your records is currently in-the-works and will be released as a minor version to Form 2 soon. Stay tuned for more updates!