When using ASP.NET MVC and EF together it makes sense to have a data layer and model layer for good separation of concerns, however this can lead to lots of object to object mapping, this is a technique to keep this task nice and dry, specifically on saving model data to the db.
What I want from my mapper is:
I have created an application with the basic pattern described here.
This contains the static maps collection and the functions to add a map and execute the mapping. You can put this functionality in any old class you deem appropriate and in scope for your own purposes - in real life I have a Antix.Data.DataFactory which is a container for this and other such functionality.
A static dictionary containing all the maps available
private static Dictionary<Tuple<Type, Type>, object> _maps = new Dictionary<Tuple<Type, Type>, object>();
A quick note on the Tuple, this is a new addition to .NET 4.0, it makes a great key for your dictionaries where you need a compound value.
Couldn't be simpler, create a Tuple as a key using the from and to types and add the map to the collection
public static void AddMap<TFrom, TTo>(Action<TFrom, TTo> map)
where TFrom : class
where TTo : class {
_maps.Add(Tuple.Create(typeof(TFrom), typeof(TTo)), map);
}This is the function that finds the right map delegate and performs the mapping, if no appropriate mapping is found then a nice exception is thrown.
public static void Map<TFrom, TTo>(TFrom from, TTo to) {
var key = Tuple.Create(typeof(TFrom), typeof(TTo));
var map = (Action<TFrom, TTo>)_maps[key];
if (map == null)
throw new Exception(
string.Format("No map defined for {0} => {1}",
typeof(TFrom).Name, typeof(TTo).Name));
map(from, to);
}One of the advantages of this technique is that you can nest mappings according to your inheritance/interfaces implementation in your object model. Here I have a Person data entity and two person models which serve different purposes in an application, PersonInfoModel and PersonDetailModel. PersonDetailModel is a derived class of the PersonInfoModel and the mapping for PersonDetailModel will call the mapping for PersonInfoModel, see below.
Mapper.AddMap<PersonInfoModel, Person>(
(model, data) => {
data.Name = string.Format("{0} {1}", model.FirstName, model.LastName);
});
Mapper.AddMap<PersonDetailModel, Person>(
(model, data) => {
Mapper.Map((PersonInfoModel)model, data);
data.JobTitle = model.JobTitle;
});In MVC I would be calling the mapper in a Post where, I have the model as an argument in the Action and I've checked that the model state is valid.
Here I've written a test to check the mapping works as expected.
[TestMethod]
public void TestMap() {
var personModel = new PersonDetailModel {
FirstName = "Garry",
LastName = "Snail",
JobTitle = "Health and Safety Officer"
};
var person = new Person();
// do the map
Mapper.Map(personModel, person);
Assert.AreEqual(
string.Format("{0} {1}",
personModel.FirstName, personModel.LastName),
person.Name);
Assert.AreEqual(personModel.JobTitle, person.JobTitle);
}Calling map passing the PersonModel will map all the data onto the person object, the model is passed into a nested map by typing the model to its base class (PersonInfoModel).
This could also be used to map a data entity to a model, and at first glance it would makes sense to do this for the same reasons described above. However, EF does not accept method delegates in its queries, so you can't do it. You could load the entire data model from the database and then call the mapper out-side of the query, but watch out, that maybe inefficient - loading unnecessary data.
ObjectObjectMapping.zip VS 2010 C# 4.0
Hey, that's some pretty concise code.
BTW, do you have any opinions on AutoMapper?
http://automapper.codeplex.com/@Charles Strahan I think its great, but for me I like to have full control over the mappings. Quite often my view models do not directly map to my domain models and AutoMapper config becomes more of a task
| < | May 2012 | |||||
|---|---|---|---|---|---|---|
| S | M | T | W | T | F | S |
| 29 | 30 | 1 | 2 | 3 | 4 | 5 |
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | 31 | 1 | 2 |
Add-ins AJAX ASP.NET MVC Browsers C# Caching CodeDom Compression CORS CSS CV Data Database DependencyResolver Development Dynamic Entity Framework Error Handling Extend File Upload Forms GDI+ HTML HTML Editor HTTP Interfaces JavaScript JQuery MCE MetadataProvider MSBuild Numbers Objects Patterns Progressive Enhancement Projects Publish Regex Resources Security SEO SMTP Source Control Strings Sub-Collections TDD Templates Tools Twitter User Interface WCF Web Development WHS WMC XLinq XML
15/05/2012
WindowsAzure
Announcing the MEET Windows Azure Event! Streamed online June 7th. Register at http://t.co/bObzTAuL #MEETAzure #WindowsAzure
10/05/2012
kevinwhinnery
Comparing Titanium and PhoneGap - A common question I get asked at developer events and conferences is how... http://t.co/Zq2eND6B
09/05/2012
brianleroux
PhoneGap goals and philosophy: http://t.co/wkq8wI2T
just now
cloudtrends
The Bing Search API on Windows Azure Marketplace is Here! http://t.co/4sxmX0rL #WindowsAzure
just now
mobiledevs
The Bing Search API on Windows Azure Marketplace is Here! http://t.co/WpcOWrz7 #WindowsAzure