Introducing Mighty: A new micro-ORM for .NET Core
Based on, and highly compatible with, Massive
There have been articles for years presenting yet another new .NET data access layer or micro-ORM and then drily asking, “Do we need yet another new .NET data access layer?”
So do we? Still? Really?
I believe we do, and this is the story of how I’ve come to put a new one — or rather, a re-write and update to one of the best — on the open-source ‘market’ (well it’s free, but you see what I mean!).
Just Quickly, What is a Micro-ORM?
A micro-ORM is a very lightweight object-relational mapping tool.
It should convert the output of SQL calls into objects in your server-side language of choice (in this case C#):
And it should convert C# objects to the input to SQL calls:
It should do all this with no setup required (the above examples are complete), unlike traditional ORMs such as Microsoft Entity Framework, which require very considerable initial set up and configuration.
The Micro-ORM Marketplace
There are currently two major .NET micro-ORM contenders: Dapper and PetaPoco, with Dapper having the lion’s share of the market. Rob Conery’s Massive is cited as an inspiration by many other micro-ORMs and previously was absolutely one of the top contenders in terms of numbers of users too, though it is much less actively developed now.
Dapper and Massive were both originally written at about the same time, and kicked off the .NET micro-ORM boom. PetaPoco was inspired by Massive and Dapper; and of course Massive itself didn’t come from nowhere, but was inspired by — amongst others — ideas from WebMatrix and Rails as well as by Mark Rendle’s Simple.Data and Rob Conery’s own earlier Subsonic project.
Rob Conery, the author and original maintainer of Massive, has moved on now. He discusses the whole issue of when and whether to hand over an open source project, and of whether an open source can ever be ‘just done’, elsewhere, in regards to his equally influential, earlier Subsonic project.
When the current maintainer, Frans Bouma, took over Massive, he cleaned and refactored the code and added some very useful new features (including, but not limited to, async support and better cross database support) but since then, and especially more recently, he has basically wanted to keep Massive where it is now (and where Rob Conery saw it as belonging, too): namely a small codebase, designed to serve as an inspiration for other micro-ORMs, and not as a growing repository for all the latest and greatest features.
That’s fine, except for people who were using Massive as their active development tool… and — perhaps mistakenly — treating it as a library rather than a starting point. Of all the features I’ve added in the codebase I describe below, that aren’t present in Massive, it seems clear — at least judging by the discussions on the Issues pages — that by far the biggest one that would have caused people to change away from Massive is ongoing lack of .NET Core support. I’m guessing, here, but I suppose this must have mainly involved users changing to Dapper.
The Origins of Mighty
I got involved in Massive because I’d already written my own class to hide away the messy ADO.NET data access code necessary to call stored procedures and return their results — in my original code using anonymous rather than C# 𝚍𝚢𝚗𝚊𝚖𝚒𝚌 objects.
When the time came to update more of our company’s legacy data access code, including dealing with result sets — and having, in the dim and distant past, used and worked on ORMs before — I started to look into the currently available options to get rid of more of that messy and repetitive ADO.NET code, but in a reasonably lightweight way… and came — rather late in the day! — across the world of micro-ORMs.
Looking at the active options in the marketplace at the time, about four years ago now, Massive seemed the nicest to me; really lightweight, lovely to use, and easy to get started with.
Importantly for me — and unlike Dapper — Massive doesn’t just add helper methods to the 𝚂𝚢𝚜𝚝𝚎𝚖.𝙳𝚊𝚝𝚊 classes — rather, it completely replaces them (or rather, wraps them), so that you get a whole new, refreshing, simple way of doing exactly what you were doing before — but far more easily!
In the above, you never even need to specify what object type to return. Massive returns C# 𝚍𝚢𝚗𝚊𝚖𝚒𝚌 objects by default, with fields containing… whatever came back from the database!
Having worked in C# for a while, I’d just recently started doing more JavaScript and was enjoying the convenience of dynamic typing. Lo and behold! Here was a library using what were then — to me — obscure corners of C# (i.e. its 𝚍𝚢𝚗𝚊𝚖𝚒𝚌 type support) to achieve the same quick’n’easy coding which I was getting used to in JavaScript. How nice to use! How quick to prototype in!
Is that the whole story? No. Because it turns out there’s been a to and fro in the popularity of dynamically vs. statically typed programming languages for years, with users of each one eventually discovering the advantages of the other. Is dynamic typing the whole solution to programming productivity? No! With a bit more hindsight, I’d say that dynamic typing is great for prototyping, but that static typing is much more helpful for long-term maintainable code. Fortunately, Mighty makes it easy to prototype ideas quickly with dynamically typed variables, then easily switch to C#’s default static typing when you’re ready:
I pretty soon realised that the stored procedure code I had written was very similar in spirit to Massive and could be fairly easily added to it. I got this working — and working really well for me — as per this simple example:
so I offered it to the Massive project.
Though I’d coded for years, I hadn’t ‘open-sourced’ before, so this was the start of learning time! Apologies to Frans Bouma (who continued to be helpful throughout!) that it was the project I learnt on.
Frans was worried that:
- My code was written for SQL Server only
- That I’d ignored cursor parameters (which, he suggested, couldn’t be handled in a lightweight library like a micro-ORM anyway)
- That I’d ignored null handling
The first was true, though the required change was simple: I hadn’t realised that parameter names start with an `@` in some ADO.NET database providers and a `:` in others. So I fixed that.
But the general point stood: I hadn’t actually fired up all the databases which Massive supported, and tested out what my change would mean for those. In my defence, given the structure of Massive (separate files for each supported database), I had actually assumed that this would be something that keen users of the other supported databases would take on. I even think it’s fair to say that, earlier in Massive’s history, PRs were often taken on on that kind of basis (and I did have some knowledge of Massive’s Issue history by then).
I did eventually prove to my own satisfaction (by doing it!) that my stored procedure code would port fairly easily to other DBs. Then again I also realise now, with the benefit of hindsight, that that isn’t how most mature projects would take on PRs either, these days!
As to the rest of Frans’s comments on my Pull Request, they sounded intriguing — and a challenge. I couldn’t see any reason in principle why I couldn’t get those things working as well! But then, who was I to claim to know? Still, I couldn’t resist trying…
Time to Work
There was a lot to do. For a start I had to get a full development environment installed, with all the databases which Massive supported.
Then there was some amount of work to do in deducing the undocumented changes which had been made to the various open source test databases, in order to get the Massive test suite to run. But hey, it’s open source; and, as I now know, trying to write open source software — to a standard where it’s ready for LOTS of people to use, not just for you and your team — is a LOT of work, and nothing is ever perfect. That’s just the way it is.
Then I started to play. I did — as I say — a LOT of work. And then a lot more.
And I got typed null parameter handling working (relatively simply — possible at all because typed null values are supported in C# anonymous objects):
And I got cursor support working (not relatively simple, but fully working!), as in this example:
And along the way, I added support for MySQL (not least to check that I’d done everything right, and that my assumptions survived adding another ADO.NET provider; they did).
I back-ported my MySQL support (i.e. stripping away the smaller part of it which was only relevant to my new updates), and that was accepted back into Massive. Along with several other larger and smaller changes which came out of what I was doing. I also learnt a lot, fast (though often not fast enough!), about what is and isn’t useful as an update to an open source project!
A key objective for me was to get Massive working in .NET Core (this had been requested on the Massive Issues list for years) since this was clearly going to be essential to our team if we were to continue to use Massive, as we still wanted to do. Though it sounds as if it should be simple, there were actually some fundamental — though not insoluble — issues as regards getting .NET Core support working in Massive.
Having got this working too, I offered my version of .NET Core support back to Massive as well; but it became clear that the approach I’d taken — despite seeming reasonably clean to me — was probably too far from what Frans Bouma, as current maintainer, wanted for the project. (Although I’m not the only person to have tried and failed, here.) It’s certainly true both that some kind of breaking changes were needed to support .NET Core, and that I still had some things to learn — at the time — about all the different ways that you potentially can break things for users of a publicly published API.
Having got all of the above working, I tried offering all of it back. Which was too much, all offered in a big lump. Oh dear. As I said before, I’ve hopefully lived and learnt much more about how to go about making, or offering, changes on someone else’s code. However, even ignoring my — let’s say — overly eager approach to offering those changes (if we may!), I eventually understood that, taken together or taken separately, all of the things I had done simply didn’t fit with where Frans Bouma wanted Massive to be; namely: a single-file(-ish) project and an inspiration to other micro-ORMs, but not a repository for the latest and greatest (definitely not just in someone else’s opinion!) new features.
Time to re-think.
As I’ve said, not all of the changes I was proposing were wanted or considered needed in Massive (even though some were). But I still wanted a version of Massive which did all these things! So I had some choices to make. My own private fork? An explicit, public fork of Massive? A re-write? I thought — hoped! — that much of what I had should be useful to others, too; and at this point, I knew the Massive codebase pretty much inside and out and back to front. Finally, I also knew (or rather guessed, from what I had read elsewhere) that maintaining a fork, with continual decisions about what to do about upstream changes, would be fraught with problems — and was even less likely to succeed than the option I eventually decided upon; the option that involved a whole lot MORE work! I rewrote the core of Massive, while keeping all the new features on top of it that I’d already written. And I did this still with the explicit aim of maintaining very high degree of compatibility with Massive.
So now, may I introduce Mighty?
A new codebase, highly compatible with Massive (because that was the whole point, all along), but now with all the new features mentioned above, and a few more which I’ll list in the next section.
The New Features
Just in case you’re considering using it, here are the most important new features which Mighty now offers, over and above Massive:
- Support for .NET Core 1.0, 1.1, 2.0, 3.0-preview
- Full stored procedure support, including parameter names and directions (where you need it; automatic parameter naming as in Massive still works as before)
- Full transaction support (you can pass 𝙳𝚋𝙲𝚘𝚗𝚗𝚎𝚌𝚝𝚒𝚘𝚗 to all methods, and any commands executed will be automatically joined to any transaction on the connection)
- Cursors (on Oracle and PostgreSQL; cursors are not designed to be passed out to client code on other databases)
- Multiple result sets
- Statically or dynamically typed return values
- Simultaneous access to more than one database provider
- Fully optional table and column name mapping (everything just works, with Massive’s sensible defaults, unless you need to change things)
Summing Up
I have already got a few happy users, the majority of whom, I’m proud to be able to say, found the project completely independently of any personal or business connection to me. But lots more would be very welcome! Perhaps if I’d jumped sooner — when more people were still using Massive — I’d have got more users, faster. Perhaps. Anyway, it took me a while to decide what to do, and I jumped when I jumped.
I think that Mighty is a great tool, because I think Massive was a great tool (and still would be, for many use cases — though Mighty now supports many more — if only it had .NET Core support). I find Mighty highly productive because I found Massive highly productive. I love Mighty’s calling syntax because it’s Massive’s syntax — now with quite a bit added; but nothing taken away. And I love the decisions made about how data access should work in Mighty: they’re Rob Conery’s decisions from Massive, not mine (although of course I’ve extended the same approach, I hope neatly, but certainly without the original fundamental innovations with Rob came up with, in the new features); and they’re very tasteful: they all fit together really well, and they just do what you want, before you even knew you needed it!
So, give it a go!
Do please give me feedback here or in the Mighty Issues page on GitHub. If it works for you — or if it doesn’t — let me know!
Acknowledgements:
Mighty isn’t endorsed by Rob Conery or Frans Bouma. I’m mentioning their names a lot because I’m explaining the origin of this project… and perhaps because I like to think I’ve caught at least a partial glimpse of what made Rob Conery’s original project so good — on so many different levels — after working hard on Massive and then on Mighty, and slowly getting to appreciate how many good design decisions were packed into so little code! Mighty couldn’t conceivably be what it is without Rob Conery’s original genius coding, and I’d also like to acknowledge that nor would it be anywhere close what it actually is without a lot of very useful feedback from Frans Bouma on the Pull Requests I made to Massive, including on the ones which were eventually rejected…!