Live Demos

Saturday, November 28, 2009

LINQ to SQL with AutoMapper

So remember my first post about LINQ to SQL? Well, I think I found a HUGE improvement over the mapping files thanks to Jimmy Bogard's AutoMapper. Take a look:

So I created a really simple domain model that looks like this:

After I created my domain model, I created a implementation project with a simple repository and a DBML file, which looks like this:




The repository code looks like this:

    public class PhotoRepository : IDisposable 
{
private readonly PhotosDataContext _dc;
public PhotoRepository()
{
Registry.
AutoMapperRegistry.Configure();
_dc =
new PhotosDataContext();
}

public List<Photo> GetAll()
{
return Mapper.Map<PhotoDataModel[], Photo[]>(_dc.PhotoDataModels.ToArray()).ToList();
}

public void Save(Photo photo)
{
Registry.
AutoMapperRegistry.Configure();
using (var dc = new PhotosDataContext())
{
dc.PhotoDataModels.InsertOnSubmit(
Mapper.Map<Photo, PhotoDataModel>(photo));
dc.SubmitChanges();
}
}

public void Dispose()
{
if (_dc != null)
_dc.Dispose();
}
}

So in the implementation project, I added my AutoMapper configuration. I setup two profiles one for the LINQtoSQL and then one to reverse it. Here's that code:

    public class AutoMapperRegistry
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<
LinqToSqlProfile>();
x.AddProfile<
LinqToSqlReverseProfile>();
});
}
}
    public class LinqToSqlProfile : Profile
{
protected override string ProfileName
{
get
{
return "LinqToSqlProfile";
}
}

protected override void Configure()
{
Mapper.CreateMap<PhotoDataModel, Photo>()
.ForMember(dest => dest.Photographer, opt => opt.MapFrom(src=>src.PhotographerDataModel));

Mapper.CreateMap<PhotographerDataModel, Photographer>();
}
}
    public class LinqToSqlReverseProfile:Profile
{
protected override string ProfileName
{
get
{
return "LinqToSqlReverseProfile";
}
}

protected override void Configure()
{
Mapper.CreateMap<Photo, PhotoDataModel>()
.ForMember(dest => dest.PhotographerDataModel, opt => opt.MapFrom(src => src.Photographer));

Mapper.CreateMap<Photographer, PhotographerDataModel>();
}
}

You'll notice in my repository I call the configure method twice once in the constructor and again on the save. I didn't have to do it in the save, but the way I did the console app, it was required. I could've called it before calling the save, but I wanted it all in one place to make it a little easier to read...I think it is anyhow.

So in order to flatten out my domain model like Headspring seems to practice and I try to mimic everything they do because lets face it, they're the best software company in the world. At least that's my opinion. So here is my view model:

    public class PhotoViewModel
{
public string Name { get; set; }
public string FileSize { get; set; }
public DateTime DateTaken { get; set; }
public string Description { get; set; }
public string PhotographerName { get; set; }
}

Here is my AutoMapper configuration for the UI project:

    public class ViewProfile:Profile
{
protected override string ProfileName
{
get{ return "ViewProfile"; }
}

protected override void Configure()
{
Mapper.CreateMap<Core.Photo, PhotoViewModel>()
.ForMember(dest => dest.PhotographerName, opt => opt.MapFrom(src => src.Photographer.Name));
}
}

Here's the initialization:

    public class AutoMapperRegistry
{
public static void Configure()
{
Mapper.Initialize(x => x.AddProfile<ViewProfile>());
}
}

Finally, here is my UI code that displays a list of the photographers and then saves a new one.

        static void Main(string[] args)
{
var pr = new PhotoRepository();
var photos = pr.GetAll();

AutoMapperRegistry.Configure();
foreach (var p in photos)
{
var photo = Mapper.Map<Photo, PhotoViewModel>(p);
Console.WriteLine("Photographer: " + photo.PhotographerName);
}
Console.Read();

var p1 = new Photo();
p1.Id =
Guid.NewGuid();
p1.Name =
"Test Photo";
p1.Photographer.Id =
Guid.NewGuid();
p1.Photographer.Name =
"John Smith";
p1.DateTaken =
DateTime.Now;
pr.Save(p1);
}


Download Source Here

Hopefully Jimmy reads this post and lets me know if there is anything messed up or anything that I'm not doing correctly with my mappings.