views:

124

answers:

6

When traversing layers it is very tedious to perform right->left assignments as a way to populate the models. For example:

employeeViewModel.FirstName = employeeModel.FirstName;
employeeViewModel.LastName = employeeModel.LastName;
...

So, we can build a ModelCopier which uses reflection to copy models:

var employeeViewModel = ModelCopier.Copy<EmployeeViewModel>(employeeModel);

This technique greatly simplifies the task. However, there are a few things that are quite disturbing about this:

  • We have effectively lost the ability to track usages of properties on the source and destination objects. For example, finding usages (in Resharper) of the FirstName property does not reveal the ModelCopier cases.
  • If we change the name of a property on the source or destination class, we can unintentionally cause runtime exceptions since we might not realize that we need to update both the source and destination class.

On one end of the spectrum, we can use reflection which is very easy, but at the cost of maintainability. The opposite end of the spectrum is very tedious but very maintainable.

Reflection (Easy and Dangerous) <-----> Direct Assignment (Tedious and Maintainable)

I'm curious if anyone has found a compromise that offers the ease of using reflection for copying with the maintainability of direct assignment.

One solution we have entertained was to build a plugin that will generate extension methods that handle the assignment of properties for each case. In other words, build a tool that will handle the tedious part.

EDIT:

Please understand that this question isn't about which mapping tool to use. I'm trying to understand how we can enjoy the benefits of reflection-based mapping while also enjoying the benefits of maintainability that is provided by direct assignment (or a property map contract).

+9  A: 

Seriously, use AutoMapper. It allows you to setup conversions from one type to another type. Any changes to property names will break the configuration of the automapper, which reflection will not:

Mapper.CreateMap<SiteDto, SiteModel>();
Mapper.CreateMap<SiteModel, SiteDto>();

Then to map to and from, you just do the following:

SiteDto dto = Mapper.Map<SiteModel, SiteDto>(targetModel);
SiteModel model = Mapper.Map<SiteDto, SiteModel>(targetDto);
GenericTypeTea
auto mapper is the way to go for sure. it's so nice!
Patricia
We have already been down the AutoMapper route. The problem we have found with AutoMapper is that in large-scale applications you end up writing lots of property maps. I'm not opposed to having lots of property maps, but if we had something that could assist with the initial creation of the maps would be much more productive.
Page Brooks
These are odd criticisms of Automapper. If you follow a convention based approach to naming it will save you tons of time. If your writing tons of mapping code with automapper then your doing something wrong. Really odd to have "mappable" classes with completely different property names.
jfar
@Page Brooks - I use AutoMapper in a number of 'large-scale' applications and it's one of the most time saving tools we utilise. It only gets a bit tricker when having to define custom property assignments (as per my example), but it's still much nicer to use than having to manually write the assignments.
GenericTypeTea
@jfar - On larger projects property names can be changed in subtle ways by various developers (ex. Phone, PhoneNumber). This can be easily missed during development and cause errors in production. This is what @Page Brooks is referring to.
Thad
@GenericTypeTea, @jfar - I agree that mapping is the way to go. I'm just trying to avoid what I see as a potentially serious pitfall with reflection-based mapping. Yes, the names should be consistent between the source and destination, but in reality they could be easily changed without the developer noticing the damage done until it is too late.
Page Brooks
@Thad, Automapper helps you catch that and will automatically detect non-matching properties. If Page can't ask a developer to use the built in refactoring tools to rename a property then he has much larger problems.
jfar
@jfar - The whole point is to enable the use of refactoring tools and maintainability in general (as noted in my original question). @themarcuz really captures the spirit of problem at hand and seems to have the best solution. If I want to use reflection-based mapping and achieve the protection from unexpected behavior, the answer is simply to make sure we include unit tests that exercises the mapping logic thoroughly. Thank you for your help!
Page Brooks
A: 

One way would be to write a separate utility that uses reflection to get the names of the properties from the model and then writes the source code to expose the properties.

amaca
@amaca - We have considered this approach. But we would like to avoid taking a dependency on a separate tool if possible. I still think this is an option however.
Page Brooks
+3  A: 

Probably the compiler don't do the necessary test all over the code... that's where unit testing comes in hand. If you have defined a test for the conversion between the classes in the different layers (yes, all the possibile conversions you need to perform, otherwise how can you be sure that your reflection approach will work in every situation once in production?), simply run the test will tell the developer who changed the property name, that the solution no longer pass all the tests. You should run every test (unit test, not integration test) every time you want to check-in your source code... and that's shouldn't be a couple of months of coding :))

So I really vote for the use of the reflection approach, spicy with a spoon of unit test approach.

themarcuz
@themarcuz - I agree, a set of unit tests would catch the problem. While discussing the issue, we brought this up and the main detractor was that it depends on the unit test being in place to work. Maybe the best answer is to make sure we have unit tests that explicitly test the mapping operation. It still would be nice if we could catch the issue at compile time.
Page Brooks
@Page Brooks - By definition the compiler run through the code you have written. So the only way to let it does the check you need is to write the code (manually or with a VS plug-in as you stated before). There's no other option: - you want to check the code at compile time -> you write the code (that is what you want to avoid)- you want to avoid to write the code -> you loos compiler checks functionality (but you don't wanna this too)But what you have to achieve is to avoid that some update fires up an error at runtime: improving the testing behaviour of your team is the best way, IMHO
themarcuz
Craig Lairman sugest to check-in every 30 minutes... but let's say an hour... and you have to run all the test before check-in.So there's not so much room for that kind of errors.I know that is not monkey-proof as the compiler is, but it's the best solution
themarcuz
@themarcuz - Well said! Thanks for your feedback!
Page Brooks
In the end, why use reflection if you are going unit test the objects? The same thing could be accomplished by manually mapping the objects. Define a mapping class. Use it to map between type A and Type B. Then you get the protection of the compiler and only have one place to change the code.
Chuck Conway
@Chuck Conway - Haha, that's an excellent point. I think I have come full circle now. It looks like there is no way to avoid the tedium if you really want protection. Thanks for your insight!
Page Brooks
+1  A: 

you can use ValueInjecter for mapping your models:

viewModel.InjectFrom(entity);

http://valueinjecter.codeplex.com/
there is asp.net-mvc sample application available for download (in the samples solution)

Omu
+1  A: 

I'm curious if anyone has found a compromise that offers the ease of using reflection for copying with the maintainability of direct assignment.

It sounds like you are trying to have your cake and eat it.

This is a problem that dynamic languages face everyday. There is no magic pill. Either do the lefthand-righthand assignments and get the protection of the compiler or wrap your critical assignments in unit tests.

My gut tells me this is a design issue, not a code issue.

Chuck Conway
I am most certainly trying to have my cake and eat it! Can you elaborate on why it may be a design issue? Maybe we are doing something that we could avoid altogether? Thanks for your help!
Page Brooks
A: 

your requirements go beyond runtime (implementation) as such since you (and all of us) need dependency graphs, etc. This means dynamic discovery with reflection is out. You seem to need a compile-time solution. Other than @amaca's suggestion of a code-generation plug-in (that you can write easily), what other options could there be?

Ra