Exploring the .NET open source hybrid ORM library RepoDB
It's nice to explore alternatives, especially in open source software. Just because there's a way, or an "official" way doesn't mean it's the best way.
Today I'm looking at RepoDb. It says it's "a hybrid ORM library for .NET. It is your best alternative ORM to both Dapper and Entity Framework." Cool, let's take a look.
Michael Pendon, the author puts his micro-ORM in the same category as Dapper and EF. He says "RepoDb is a new hybrid micro-ORM for .NET designed to cater the missing pieces of both micro-ORMs and macro-ORMs (aka full-ORMs). Both are fast, efficient and easy-to-use. They are also addressing different use-cases."
Dapper is a great and venerable library that is great if you love SQL. Repo is a hybrid ORM and offers more than one way to query, and support a bunch of popular databases:
Here's some example code:
/* Dapper */
using (var connection = new SqlConnection(ConnectionString))
{
var customers = connection.Query<Customer>("SELECT Id, Name, DateOfBirth, CreatedDateUtc FROM [dbo].[Customer];");
}
/* RepoDb - Raw */
using (var connection = new SqlConnection(ConnectionString))
{
var customers = connection.ExecuteQuery<Customer>("SELECT Id, Name, DateOfBirth, CreatedDateUtc FROM [dbo].[Customer];");
}
/* RepoDb - Fluent */
using (var connection = new SqlConnection(ConnectionString))
{
var customers = connection.QueryAll<Customer>();
}
I like RepoDB's strongly typed Fluent insertion syntax:
/* RepoDb - Fluent */
using (var connection = new SqlConnection(connectionString))
{
var id = connection.Insert<Customer, int>(new Customer
{
Name = "John Doe",
DateOfBirth = DateTime.Parse("1970/01/01"),
CreatedDateUtc = DateTime.UtcNow
});
}
Speaking of inserts, it's BulkInsert (my least favorite thing to do) is super clean:
using (var connection = new SqlConnection(ConnectionString))
{
var customers = GenerateCustomers(1000);
var insertedRows = connection.BulkInsert(customers);
}
The most interesting part of RepoDB is that it formally acknowledges 2nd layer caches and has a whole section on caching in the excellent RepoDB official documentation. I have a whole LazyCache subsystem behind my podcast site that is super fast but added some complexity to the code with more Func<T> that I would have preferred.
This is super clean, just passing in an ICache when you start the connection and then mention the key when querying.
var cache = CacheFactory.GetMemoryCache();
using (var connection = new SqlConnection(connectionString).EnsureOpen())
{
var products = connection.QueryAll<Product>(cacheKey: "products", cache: cache);
}
using (var repository = new DbRepository<Product, SqlConnection>(connectionString))
{
var products = repository.QueryAll(cacheKey: "products");
}
It also shows how to do generated cache keys...also clean:
// An example of the second cache key convention:
var cache = CacheFactory.GetMemoryCache();
using (var connection = new SqlConnection(connectionString).EnsureOpen())
{
var productId = 5;
Query<Product>(product => product.Id == productId,
cacheKey: $"product-id-{productId}",
cache: cache);
}
And of course, if you like to drop into SQL directly for whatever reason, you can .ExecuteQuery() and call sprocs or use inline SQL as you like. So far I'm enjoying RepoDB very much. It's thoughtfully designed and well documented and fast. Give it a try and see if you like it to?
Why don't you head over to https://github.com/mikependon/RepoDb now and GIVE THEM A STAR. Encourage open source. Try it on your own project and go tweet the author and share your thoughts!
Sponsor: Have you tried developing in Rider yet? This fast and feature-rich cross-platform IDE improves your code for .NET, ASP.NET, .NET Core, Xamarin, and Unity applications on Windows, Mac, and Linux.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
We are using Linq2db for a while now (https://github.com/linq2db/linq2db), and very pleased with it. I'm not sure if it gets enough attention, so I've dropped this line in case readers here are not aware of it.
I used this one for many years for the same reasons - not write SQL manually
Dapper.SimpleCRUD
More Docs
Looks like RepoDB with cache support is an interesting thing
https://github.com/Arnab-Developer/repodb-demo
Dislike "failing the first unit test" of fluent interfaces (no contract between method called and code calling that method).
Fluent fails to have a contract between the user of the fluent interface and the fluent interface.
Consider this mess from the Azure C# samples on creating a virtual machine on how much extra work a fluent interface puts on the client developer. 19 extra calls to do what should be one or two calls.
Fluent:
- Easier on the API developer, easier to write simple methods, easier to document, easier to unit test, and easier to return a generic uninformative error when there is a problem
- MUCH HARDER on the developer using the API
- What needs to be called, what order to call them, how to test, how to debug,
From Azure samples: https://github.com/Azure-Samples/compute-dotnet-create-virtual-machines-from-generalized-image-or-specialized-vhd/blob/master/Program.cs
Utilities.Log("Creating a Linux VM");
var linuxVM = azure.VirtualMachines.Define(linuxVmName1)
.WithRegion(Region.USWest)
.WithNewResourceGroup(rgName)
.WithNewPrimaryNetwork("10.0.0.0/28")
.WithPrimaryPrivateIPAddressDynamic()
.WithNewPrimaryPublicIPAddress(publicIpDnsLabel) .WithPopularLinuxImage(KnownLinuxVirtualMachineImage.UbuntuServer16_04_Lts)
.WithRootUsername(UserName)
.WithRootPassword(Password)
.WithUnmanagedDisks()
.WithSize(VirtualMachineSizeTypes.StandardD3V2)
.DefineNewExtension("CustomScriptForLinux")
.WithPublisher("Microsoft.OSTCExtensions")
.WithType("CustomScriptForLinux")
.WithVersion("1.4")
.WithMinorVersionAutoUpgrade()
.WithPublicSetting("fileUris", ApacheInstallScriptUris)
.WithPublicSetting("commandToExecute", ApacheInstallCommand)
.Attach()
.Create()
This fluent API lets the MS dev documentation be generated from the method headers with one line of text for the method explanation and one line of text for the parameter and no mention of the exceptions for each method.
First unit test of programming: Let the compiler detect problems when passing arguments to a function (check types, check number of arguments, maybe even semantic checking - cannot pass null literals)
The above fluent API example is more difficult to use than writing the API as a set of well defined contracts.
Azure SDK needs a re-think and not be dumbed down
Comments are closed.
I prefer to use Dapper + Entity2SQL