As you might know I’m running a .NET exception service called OneTrueError. When I moved from a NoSQL db to SQL Azure I had to be able to work with the database in some way. I’m not using OR/Ms any more. They might significantly reduce the bootstrapping, but in the long run they always tend to make you struggle as the application grow. To me, ORMs is a bit like ASP.NET WebForms, but for data. i.e. it tries to make something what it isn’t. I therefore wanted something that did not take away the control from me nor hide the underlying layer. I still want to work with my domain entities though.
After evaluating different mappers I decided to write my own. It’s unobtrusive as it’s built as extensions to ADO.NET, instead of hiding ADO.NET. You can easily alternate between ADO.NET and my mapper. A single technology doesn’t work for all cases. You also have full control over the table/class mappings. The mappings are not hidden and you can easily customize them, for instance if the column and property types do not match. There is no support for LINQ statements or any other fancy way of creating queries. Those you will have to write using SQL.
Do remember that it’s a mapper and not an ORM. It doesn’t try to solve all problems in the data layer. It works excellent for many cases, but not all. Use plain ADO.NET when justified. i.e. don’t be afraid to mix technologies in your data layer.
How does it work then? First of all you should read my ADO.NET article since this layer is an extension to that.
Select statements
Let’s see how we can fetch data with Griffin.Framework.
In the ADO.NET article you had code like this (using only ADO.NET):
With Griffin.Framework you reduce the code to this:
See? You still have to write the queries by yourself. For simple queries (only using “AND” and equal sign) we can reduce it even further.
The anonymous object will automatically be converted into the exact same WHERE clause.
First
First will thrown an exception if an entity is not found. In applications where IDs always are provided through code this is the number one way to fetch items, since you probably have an error somewhere if the entity is not found in that case.
Here is a small example:
If the user is not found you’ll get an exception which includes the SQL query and all arguments:
Failed to find entity of type ‘Griffin.Data.IntegrationTests.Sqlite.Entites.User’.
Command: SELECT * FROM Users WHERE Id = @Id
Parameters: Id=7f6d4ef8e7044dbc884f961f3d57cac2
FirstOrDefault
Works just as First, but doesn’t thrown an exception if the entity is not found.
ToList
ToList will populate a list in memory and return it to you.
ToEnumerable
Sometimes you would want to work with larger data amounts, so building a list in memory would be very inefficent. ToEnumerable() uses lazy loading and will not map rows unless they are requested. Hence doing something like:
..would skip through the first 1000 rows without mapping them and then just map the next 10 rows.
Create/Update/Delete
With plain ADO.NET you have to write all CRUD statements by yourself, they would look something like:
With Griffin.Framework they would instead look like this:
That lowers the time compared to plain ADO.NET. And again, sometimes you need to do custom queries. As my library is just an extension to ADO.NET, you can mix them depending on the use case.
Mappings
To make this work we have to have mappings somewhere. For this mapping layer they are mandatory and are represented by classes. By default the mapping classes are automatically picked up by the library using reflection. You can however customize how the mappings are loaded by specifying a factory using EntityMappingProvider.Provider = new YourCustomProvider().
If your table looks exactly like your class you can just create an empty class:
The constructor specifies which table to use.
Column name / property name mismatch
However, sometimes the column names do not match the property names. In this case we’ll have to configure the mappings a bit more.
Column type / property type mismatch
If the database do not support the same types as .NET we’ll have to convert the value. Those conversions need to be configured using adapters.
You can also create two way conversions:
Value types / Child aggregates
In some cases it doesn’t make sense to create tables for child aggregates as they are never going to be accessed directly. Instead you just want to store them as part of the root aggregate (as a column value). With the mappers you can do that easily by using the adapters.
Let’s say that you have the following classes:
Instead of creating a table for all addresses, simply add a new text column called “Addresses” and do the following (using JSON.NET):
The great thing with that is that you never have to track if any of the child aggregates have been added/changed/removed.
Field vs Property
Private setters or getters are no problem, but sometimes that isn’t enough. You might want to use a field instead, typically if you expose IEnumerable but use a List internally in your class.
We do support that out of the box. Just make sure that the field is named as the property, but with underscore and camel hump style.
Transactions
For transactions you can of course use IDbTransaction implementations like SqlTransaction etc. But as the transaction is typically handled by a layer on top of the data layer that leaks data layer specific implementation details to the above layer. We’ve instead added a new interface called IUnitOfWork and an UnitOfWorkFactory class.
That means that your business layer would have code like:
To make that work you have to configure the UnitOfWorkFactory class by doing something like:
The unit of work also have extension method to perform db operations in the transaction:
To create a plain IDbCommand you can do like this:
Asynchronous
The mapper fully supports asynchronous operations. The entire API is available using TPL.
or using commands:
Exceptions
I’ve put a lot of effort in the exception messages to aid you when something fails. You will for instance always get information about which entity we couldn’t find:
Failed to find entity of type ‘Griffin.Data.IntegrationTests.Sqlite.Entites.User’.
Command: SELECT * FROM Users WHERE Id = @Id
Parameters: Id=7f6d4ef8e7044dbc884f961f3d57cac2
Or if a mapping is incorrect:
Griffin.Data.IntegrationTests.Sqlite.Entites.User: Failed to cast ‘Id’ from ‘System.Int32′.
A quick examination of the mapping for that entity would reveal that ‘Guid’ was expected.
In my opinion exception messages is the best way of making it easy to solve errors. That in combination with the complexity is the number one reason to why most OR/Ms are so hard to work with.
Summary
This mapper is the first part of Griffin.Framework. I’m in progress of merging my most popular frameworks into one library. It makes it easier to support and build more advanced features that require multiple libraries. The next part being merged is Griffin.Networking (a more stable and performant rewrite).
I’ll add complete examples to github when I have a chance.
The library is available in nuget: install-package griffin.framework