Beware - Mapping Classes With ReadOnly Properties Using Dapper With VB.NET, C# & F#
[C#, Dapper, VB.NET, F#]
One of the most monotonous tasks in building applications is mapping data between your domain types and storage.
There are, of course, a number of solutions to this problem - you could always use a full ORM (object-relational mapper) like Entity Framework Core.
Of, especially if you have legacy code, you can use an excellent library called Dapper.
Dapper
is implemented as several extension methods to objects extending the DbConnection object. This means it will work for any complete ADO.NET providers for SQL Server, PostgreSQL, MySQL, SQLite, etc.
Let us assume we had this very simple object with a single property:
public class Result
{
public DateTime Value { get; set; }
}
To use this, we start by installing the Dapper
package into our program.
dotnet add package Dapper
Then, we add the data access libraries for SQL Server, Microsoft.Data.SqlClient, which we will use in this example
dotnet add package Microsoft.Data.SqlClient
We can then write the code to map the current date on the database server onto this Result
object.
using Dapper;
using Microsoft.Data.SqlClient;
var cn = new SqlConnection("Data Source=localhost;uid=sa;pwd=YourStrongPassword123;TrustServerCertificate=True");
var result = cn.QuerySingle<Result>("SELECT GETDATE() as Value");
Console.WriteLine(result.Value);
You should see the current date printed on the console.
31/01/2025 23:11:23
I am a huge proponent of immutable types, as it prevents inadvertent changing of state.
You can modify the Result class as appropriate.
public class Result
{
public DateTime Value { get; set; }
}
This will still work, as Dapper
will, behind the scenes, do some reflection to set the value appropriately.
Given that Dapper
is a .NET library, it would work the same with other languages, so let us create a VB.NET project.
dotnet new console -o DapperTestVB -lang VB
We then add the Dapper
and Microsoft.Data.SqlClient
, as explained above.
Finally, we create our types.
The first Result
type is as follows:
Public Class Result
public Property Value as DateTime
End Class
Our VB.NET program will look like this:
Imports System
Imports Dapper
Imports Microsoft.Data.SqlClient
Module Program
Sub Main(args As String())
dim cn = new SqlConnection("Data Source=localhost;uid=sa;pwd=YourStrongPassword123;TrustServerCertificate=True")
dim result = cn.QuerySingle (Of Result)("SELECT GETDATE() as Value")
Console.WriteLine(result.Value)
End Sub
End Module
You might be wondering why there is a Module
, a Main
class, etc. This is because VB.NET does not support top-level statements, and so we have to write a full traditional program.
Nevertheless, this code will still run.
31/01/2025 23:29:13
In VB.NET, we indicate a read-only property with the ReadOnly keyword.
We can update our class as follows:
Public Class Result
public ReadOnly Property Value as DateTime
End Class
If we run this program, we get the following result:
01/01/0001 00:00:00
What has happened here?
Dapper
has been unable to map the property and has thus returned the default empty DateTime
- year, month, date, hour, minute, and second are all 0.
This means that the behavior is different with VB.NET - it is unable to bind to read-only properties.
You might be wondering if F# has this problem.
In F#, we can define our type thus:
type Result = { Value: System.DateTime }
By default, types in F# are immutable, and you have to go out of your way to change this.
Our program would look like this:
open Microsoft.Data.SqlClient
open Dapper
let connectionString =
"Data Source=localhost;uid=sa;pwd=YourStrongPassword123;TrustServerCertificate=True"
let cn = new SqlConnection(connectionString)
let result = cn.QuerySingle<Result>("SELECT GETDATE() as Value")
printfn $"%A{result.Value}"
This, unsurprisingly, prints the correct result:
31/01/2025 23:39:13
TLDR
Dapper
cannot bind to read-only properties when implemented in a VB.NET program.
The code is in my GitHub.
Happy hacking!