Mappen met Dapper

Mapping issuesMappen naar composite objectenMeer resultaatsets

Voor Alle-vakantiehuizen.nl doe ik momenteel een project om data-access richting 2021 te brengen, en vooral de code wat te versimpelen. De keuze is daarbij gevallen op het gebruik van Dapper. Dapper is de implementatie van een micro Object Relation Mapper. Dapper is open-source en als NuGet package gemakkelijk aan je applicatie toe te voegen. Het mooie aan Dapper is dat het razendsnel is en gemakkelijk te gebruiken in legacy code. In dit artikel wat praktische tips rond mapping als je Dapper gaat gebruiken in een lecacy omgeving.

Mapping issues

Dapper mapt bij default de kolomnaam uit de SQL-query naar een property met dezelfde naam in het te mappen object. Zo heb je bijna geen mapping code nodig. Helaas is dat in een bestaande omgeving vaak niet zo, bijvoorbeeld als er gebruik wordt gemaakt van storedprocedures, of als de de naamgeving in de database in een andere taal is dan je code. Gelukkig zijn daar een tweetal oplossingen voor.

Oplossing 1 : Gebruik een alias in je SQL

Als je de SQL query in je code hebt is dit de gemakkelijkste oplossing. In de de twee voorbeelden gebruik ik de class Persoon. Deze class heeft twee properties namelijk Voornaam en Achternaam. De database heeft een table Persons met de kolommen Firstname en Lastname.

// model voor Persoon
public class Persoon {
  public string Voornaam {get; set;}
  public string Achternaam {get; set;}
}
....
string sqlQuery = "SELECT firstname as voornaam , 
                   lastname as achternaam";

using (var conn = My.ConnectionFactory())
 {
    var personen = conn.Query<Persoon>(sqlQuery).ToList();
}
Oplossing 2 : Gebruik mapping code

De tweede oplossing maakt gebruik van Dapper.FluentMap. Maak als eerste een mapping class die erft van EntityMap zoals deze en registreer deze class bij dapper zoals in het volgende voorbeeld. Dapper gebruikt nu de gespecificeerde mapping. N.b. dit is ook te gebruiken voor mapping naar enumerated types.

In de praktijk kan het voorkomen dat je meer dan 1 mapping voor dezelfde kolom zou willen hebben. Helaas kan Fluentmap dat niet afhandelen. Er zijn meerdere scenarios waarbij dit voorkomt. Denk bijvoorbeeld aan een stored procedure die zowel prive als zakelijke contactgegevens van een persoon teruggeeft. In de code zou je dan waarschijnlijk dezelfde ContactGegevens klasse willen gebruiken. Een ander scenario is dat naamgeving van kolommen in stored procedures niet consistent is.

In beide scenarios ligt de gemakkelijkste oplossing in het wijzigen van de stored-procedures.

internal class PersoonMap : EntityMap<Persoon>
 {
    internal AuthenticationMap()
 {
	Map(p => p.Voornaam).ToColumn("firstname");
        Map(p => p.Achternaam).ToColumn("lastname");
    }
}
FluentMapper.Initialize(config => { 
    config.AddMap(new PersoonMap());
});

Mappen naar composite objecten

Het komt regelmatig voor dat de row die je terugkrijgt van je SQL-query gemapt moet worden naar een composite object. Dapper noemt dit multimapping. Stel dat de Persoon class in het vorige voorbeeld een property WoonAdres heeft.

public class Persoon {
  public string Voornaam {get; set;}
  public string Achternaam {get; set;}
  public Adres WoonAdres {get; set; 
}
public class Adres {
  public string Straat {get; set;}
  public string Huisnummer {get; set;}
  public string Postcode {get; set;} 
  public string Plaats {get; set;} 
}

Dapper wil dan weten hoe we de gegevens moeten splitten. Zodra Dapper in onderstaande voorbeeld ‘straat’ tegenkomt gaat Dapper ervan uit dat alle volgende kolommen gemapt moeten worden naar het property WoonAdres. Er kunnen meerdere ‘splits’ gedaan worden. Mappen naar een object dat bestaat uit meerdere objecten is dus geen probleem. Je moet wel letten op de volgorde in de sql-query

string sqlQuery = "SELECT firstname as voornaam , 
                   lastname as achternaam, 
                   street as straat,
                   number as huisnummer,
                   zipcode as postcode,
                   city as plaats
 FROM Persons";

info = conn.Query<Persoon, Adres, Persoon>(sqlQuery,
	map: (p, a) => { p.WoonAdres = a; return a; },
	splitOn: "straat").ToList();

Meer resultaatsets

Soms komt het voor dat een SQL query of stored procedure meerde resultaatsets teruggeeft, die je in een reader leest middels Reader.NextResult(). Dapper heeft hiervoor de QueryMultiple procedure.

Stel dat een persoon meerdere adressen kan hebben, dan zou je een query kunnen maken die je eerst de persoons informatie geeft en vervolgens een een lijst met alle adressen. Dapper kan dit bijvoorbeeld als volgt afhandelen:

var results =conn.QueryMultiple(sqlQuery, parameters);
   var persoon = results.Read<Persoon>().First();
   persoon.Adressen = results.Read<Adres>().ToList() 				

Multimapping en QueryMultiple zijn overigens prima te combineren.

In mijn ogen is Dapper een prettig tool die data-access een stuk vriendelijker maakt, maar waarbij je zelf toch full in control bent.

Happy coding

Geef een reactie