Mark Seemann skrev forleden et godt indlæg om de udfordringer, man møder, når man skal mappe DTO objekter til domain objekter og videre til viewmodel objekter.

Jeg er en af dem, der har forsøgt sig med at snyde og lade DTO objekter vandre op igennem forretningslaget, og det bliver hurtigt grimt. Bare det at DTO klasserne alle har default constructor gør, at vedligeholdelse af koden bliver en pine. Mark er inde på, at mapning mellem DTO objekter og domain objekter kan være svær, fordi ORM frameworks ikke understøtter mapping, når der ikke findes en default constructor og alle propeties ikke har en setter.

Jeg har haft nogenlunde succes med at bruge AutoMapper til det. Det viser sig, at AutoMapper er i stand til at mappe properties på source objektet til constructor parametre på destination objektet. Man skal bare sørge for, at parametre og properties hedder det samme (de behøver ikke have samme casing), hvilket i mine øjne i øvrigt er god ting at have i sin kodestandard alligevel (konsistent navngivning).

Lad os tage udgangspunkt i følgende klasser Track og DbTrack (tyvstjålet fra Mark’s indlæg):

   1:  public class Track
   2:  {
   3:      private readonly int id;
   4:      private string name;
   5:      private string artist;
   6:   
   7:      public Track(int id, string name, string artist)
   8:      {
   9:          if (name == null)
  10:              throw new ArgumentNullException("name");
  11:          if (artist == null)
  12:              throw new ArgumentNullException("artist");
  13:   
  14:          this.id = id;
  15:          this.name = name;
  16:          this.artist = artist;
  17:      }
  18:   
  19:      public int Id
  20:      {
  21:          get { return this.id; }
  22:      }
  23:   
  24:      public string Name
  25:      {
  26:          get { return this.name; }
  27:          set
  28:          {
  29:              if (value == null)
  30:                  throw new ArgumentNullException("value");
  31:   
  32:              this.name = value;
  33:          }
  34:      }
  35:   
  36:      public string Artist
  37:      {
  38:          get { return this.artist; }
  39:          set
  40:          {
  41:              if (value == null)
  42:                  throw new ArgumentNullException("value");
  43:   
  44:              this.artist = value;
  45:          }
  46:      }
  47:  }
  48:   
  49:  public class DbTrack
  50:  {
  51:      public int Id { get; set; }
  52:      public string Name { get; set; }
  53:      public string Artist { get; set; }
  54:  }

Track har ikke en default constructor og har setter properties for Name og Artist, men ikke for Id. AutoMapper kan – uden nogen egentlig konfiguration – benyttes til at mappe fra DbTrack til Track på følgende måde:

   1:  Mapper.CreateMap<DbTrack, Track>();
   2:   
   3:  var source = new DbTrack()
   4:  {
   5:      Id = 12,
   6:      Name = "Name12",
   7:      Artist = "PSB"
   8:  };
   9:   
  10:  var destination = Mapper.Map<Track>(source);

Kaldet til Mapper.Map i linje 10 vil først kalde constructoren på Track med de rigtige parametre bestemt ud fra parametrenes navne, og da Track klassen har setters på to properties, vil disse properties blive kaldt efterfølgende. Hvis Track klassen kun havde getters på alle properties, ville det naturligvis kun være constructoren, der blev kaldt. Hvis Track klassen har parametre i constructoren, som har navne, der ikke kan matches med properties på DbTrack klassen, vil AutoMapper fejle med en exception.