1. Introduction
Here at Miracle Devs, we believe software should adapt to different scenarios, different clients, and, in line with the above, different budgets. If you host distributed applications using an IIS server, a SQL Server Farm, a load balancer to handle traffic with all the bells and whistles, you're surely creating an amazing infrastructure for your application, but also one that has steep costs of deployment and a high barrier of entry for new clients. For smaller businesses, licencing costs can be prohibitive, preventing them from integrating solutions that can help them drive revenue and improve processes, while costing dev teams new job opportunities. We believe that .NET Core is an amazing opportunity for .NET technology to fill all those gaps where high licencing costs are not an option, where legacy infrastructure or outdated software require deployment of C# code on alternative OS and platforms.
When we first started considering to integrate .NET Core into our dev stack, allowing us to capitalize on the world of cross-platform, multiple OSes possibilities described above, the first caveat was the need to maintain our workflows and processes. At least keep the ones that have been proven successful time and time again in real scenarios. One of these, if not the single most important for us, is the ability to approach app design from a Database First standpoint, only working on the rest of the tiers afterwards. Having this in mind, we set out to analyze our options in the new(ish) world of NET Core.
Entity Framework
Our obvious first choice was the tried and true (viejo y querido!) EF. We've been using Entity Framework for at least 7 years now: we love EF capabilities, but also realize its downsides. While EF does provide a fine structure for an app, complex data queries need always be kept in the database server and never in our domain logic, or pay the performance cost that entails. Our first issue with the NET Core flavor of Entity Framework (EF Core) was they did not support the DbFirst approach we were used to (at the time of this writing) or at least didn't do it the way we were used to. On top of that, our preliminary tests suggested problems with mapping stored procedures and views, which was an absolute deal breaker for us. While we could navigate around other limitations, not having the option to make our changes in the database first, updating our models from there, and having issues mapping procedures eventually drove us away from EF. A comprehensive list of missing features (at the time of this writing) can be found here.
Dapper
Our second option was to study Dapper, the fresh and fast "new" kid on the block. Dapper is tweaked for speed over all other aspects, and we can certainly relate to that approach. They also have quite a pragmatic view on the old discussion about DB vs Domain, and where the logic should be placed. We love the well structured and object-oriented code, and having reusable modules is very nice: but we prefer to handle all that on the database by design, using procedures and views for complex queries, so the full extent of that effort is pretty much lost on us. By design, we prefer large volumes of data to be handled in the database, were they are meant to be handled. That gives us responsiveness and performance that we can never get from a solution that performs this on the application side, no matter how fast, reliable or well executed it is. Dapper allowed mapping practically anything, and gave us the opportunity to work with a real DbFirst approach, but lacking code generation or model updates, which are also important things for us to have in an ORM, we decided to pass on it.
Dapper did leave us with a question in mind. We know that we want (need) blazing fast data access. But we also need fast code development, and ability to generate boilerplate code quickly and efficiently across projects (something we loved about entity framework and custom T4s). What if we made a custom library to try and implement a middle ground between the two?
And that's how we started development on our own ORM, Paradigm ORM, a solution that doesn't compromise on all the things we need. This is not meant to be a one-fits-all kind of solution, and may not be a good fit for many of you. But for us it serves the purpose just right, and we are making it available in the hope someone else find that true as well.
The next point will explore Paradigm ORM objectives and how we intended it: basically an overview of what this tool is, and what it's not.
1.1. Purpose and objectives
As mentioned on the previous points, the main features on Paradigm ORM are:
- DbFirst: The ability to design and work from the database first, later applying (or cascading) those changes into our .NET code base.
- Lightning Fast data access: ORMs in general are known for creating non-optimal queries, mostly due to limitations in the ability to translate queryable interfaces (we are looking at you Linq!). While queryable interface are a great, quick way to prototype without touching the DB, we are after no-compromise speed and performance.
- Avoid unnecessary logic overhead necessary to support entity tracking or weird mappings (also known as: All the other amazing things Entity Framework does that we never use in real-world enterprise scenarios).
- Support (purposefully limited) domain navigation: We don't need no weird navigation scenarios. We are simple people that need simple domain scenarios, like one-to-one, or one-to-many. No need for fancy many-to-many mappings that add overhead and we seldom use (just our experience!).
- Support a way to generate an intermediate model to map from the database to our domain code. Sort of an exporter that we could feed into the point below this one.
- Support custom code generation. True DBFirst derives the domain from the database, so why not have it generated automatically while we are at it? And not only for domain classes or database contexts like Entity used to do, but for repositories, mappers, the entire stack. (To be completely honest, we were already generating all those classes as part of our workflow with T4s, but did you ever modified a T4 in that way? It's really annoying for lack of better words :)).
- An easy way to update the code every time we wanted, to apply changes we made on the database.
These were the main principles, objectives or goals driving us while developing this library. Being a small company we did have to leave some features behind, stuff that we considered unnecessary for an initial implementation. We do realize the value of having them, for us and other, and so we are listing them below as a reminder for future milestones:
- Fancy mapping types and mapping relations: Maybe one day in the far-off future when people and robots live together in harmony but there's a murder and everything seems to point to a robot as the murderer, there will be a well designed enterprise application that needs this. So why not having it?
- Queryable interfaces: True that they do come in handy on some situations, and they speed up development when you don't feel like issuing an update on the db or hand crafting a query.
- Graphical model editors: This one we do miss from the good ol' EF days :)
All in all, Paradigm ORM is the result of a desire to use .NET Core on our development stack, and do it in our terms. It includes most of the features that we used time and time again for every project in the last 7 years, while fixing some of the ones we had to work around. And it does that without compromising any performance, in line with the NET Core spirit of minimal footprint and maximum efficiency on any kind of deployment scenario.
The list above is of course not written in stone, and as we move forward and learn more from using Paradigm, we are sure that some aspects will be reviewed. For now, it solves our problem: we sincerely hope it can solve yours too.