views:

670

answers:

4

UPDATE: this stuff has evolved into a nice project, see it at http://valueinjecter.codeplex.com


check this out, I just wrote a simple automapper, it takes the value from the property with the same name and type of one object and puts it into another, and you can add exceptions (ifs, switch) for each type you may need

so tell me what do you think about it ?

I did it so I could do something like this:

Product –> ProductDTO

ProductDTO –> Product

that's how it begun:

I use the "object" type in my Inputs/Dto/ViewModels for DropDowns because I send to the html a IEnumerable<SelectListItem> and I receive a string array of selected keys back

 public void Map(object a, object b)
    {
        var pp = a.GetType().GetProperties();
        foreach (var pa in pp)
        {
            var value = pa.GetValue(a, null);

            // property with the same name in b
            var pb = b.GetType().GetProperty(pa.Name);
            if (pb == null)
            {
                //no such property in b
                continue;
            }

            if (pa.PropertyType == pb.PropertyType)
            {
                pb.SetValue(b, value, null);
            }

        }
    }

UPDATE: the real usage:
the Build methods (Input = Dto):

        public static TI BuildInput<TI, T>(this T entity) where TI: class, new()
        {
            var input = new TI();
            input = Map(entity, input) as TI;
            return input;
        }

        public static T BuildEntity<T, TI, TR>(this TI input)
            where T : class, new()
            where TR : IBaseAdvanceService<T>
        {               
            var id = (long)input.GetType().GetProperty("Id").GetValue(input, null);
            var entity = LocatorConfigurator.Resolve<TR>().Get(id) ?? new T();
            entity = Map(input, entity) as T;
            return entity;
        }

        public static TI RebuildInput<T, TI, TR>(this TI input)
            where T: class, new()
            where TR : IBaseAdvanceService<T>
            where TI : class, new()
        {

                return input.BuildEntity<T, TI, TR>().BuildInput<TI, T>();
            }

in the controller:

    public ActionResult Create()
    { 
        return View(new Organisation().BuildInput<OrganisationInput, Organisation>());
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(OrganisationInput o)
    {
        if (!ModelState.IsValid)
        {
            return View(o.RebuildInput<Organisation,OrganisationInput, IOrganisationService>());                
        }
        organisationService.SaveOrUpdate(o.BuildEntity<Organisation, OrganisationInput, IOrganisationService>());
        return RedirectToAction("Index");
    }

The real Map method

public static object Map(object a, object b)
        {
            var lookups = GetLookups();

            var propertyInfos = a.GetType().GetProperties();
            foreach (var pa in propertyInfos)
            {
                var value = pa.GetValue(a, null);

                // property with the same name in b
                var pb = b.GetType().GetProperty(pa.Name);
                if (pb == null)
                {
                    continue;
                }

                if (pa.PropertyType == pb.PropertyType)
                {
                    pb.SetValue(b, value, null);
                }
                else if (lookups.Contains(pa.Name) && pa.PropertyType == typeof(LookupItem))
                {
                    pb.SetValue(b, (pa.GetValue(a, null) as LookupItem).GetSelectList(pa.Name), null);
                }
                else if (lookups.Contains(pa.Name) && pa.PropertyType == typeof(object))
                {
                    pb.SetValue(b, pa.GetValue(a, null).ReadSelectItemValue(), null);
                }
                else if (pa.PropertyType == typeof(long) && pb.PropertyType == typeof(Organisation))
                {
                    pb.SetValue(b, pa.GetValue<long>(a).ReadOrganisationId(), null);
                }
                else if (pa.PropertyType == typeof(Organisation) && pb.PropertyType == typeof(long))
                {
                    pb.SetValue(b, pa.GetValue<Organisation>(a).Id, null);
                }
            }

            return b;
        }
+6  A: 

Just use AutoMapper. This is fine, but it'll grow into a mini project.

Just some things AM (the real one) does is:

  • reporting if you have properties that can't be mapped to
  • flattening objects
  • providing hooks for you to customise some aspects, not in a big switch statement
  • using Expression.Compile for perf reasons rather than reflection directly

But it's certainly an interesting space to mess about in, and the idea of auto mapping is certainly useful.

A bit like DI in 15 or 33 lines vs NInject or its friends - cool, but why?.

I take it you've read the article and comments regarding 2 way mapping on Jimmy's blog ?

Ruben Bartelink
I intend to use it for editing entities (to map from my models into my viewmodels and viceversa), i wanted to use automapper for that, but Jimmy told me not to :)
Omu
@Omu, fair enough. The point for me though is that your superficially simple 'solution' is hiding an iceberg of special cases, cool and all as it is, and in completing it, you'll end up with a non-trivial amount of work. cottsak's point asking what are you doing this mapping for is worth considering. But you didnt give us much to go on regarding what your *real* scenario is. For all we know you might the ideal .NET RIA Services user :P Did Jimmy say not to add 2-way mapping to AM or that he wasnt going to because its not a use case he sees making sense? Forking could be better than reinventing
Ruben Bartelink
I have updated the question text, you can see now how I use it
Omu
Thanks for the link, it (lack of 2-way mapping) makes sense now.
JTew
+3  A: 

Just a thought:

One might wonder what the point of an abstraction is if the abstraction is so easily mapped to that being abstracted.

cottsak
+3  A: 

you can add exceptions (ifs, switch) for each type you may need

You're not going to do this. Seriously. Case statements acting on object types are bad OOP style. I mean, really bad. Like driving with a drink of Vodka inside your stomach. It may work for some time, but eventually you get into trouble.

OK Jimmy told you not to use AutoMapper... but I bet he meant something else. Now, have you invented something different - something that make Jimmy happy? ;-) No, you just made your own half-rolled AutoMapper. And Jimmy told you not to use it! ;-)

So here's my suggestion: ignore what Jimmy says, just think yourself.. and use AutoMapper ;-)

queen3
well yes, I could, but with this stuff I can do my own conventions very easily; I use it at the moment in 2 methods of a "Builder" class: BuildEntity<T,TI> BuildInput<TI, T>, so i'm mapping automatically all my entities to all my Inputs (ViewModels), without the need to specify a mapping (CreateMap) for each pair of types
Omu
You can have your BuildEntity<> methods to invoke AutoMapper CreateMap/Map internally. Moreover, you can have it to check if CreateMap is already registered, so you have possibility to override default mapping - instead of hard-coding into switches which is the BAD BAD style. Still I don't see a reason to re-invent AutoMapper.
queen3
I have updated the question text, you can see now how I use it
Omu
+4  A: 

One thing you might want to add is to cache the reflection bits. If you map an object twice, you probably don't want to look up all the reflection stuff again. Also, things like GetValue and SetValue are quite slow, I switched to late-bound delegates + Reflection.Emit to speed things up.

Jimmy Bogard
:) well, I don't map a specific type to another one, it's more like per property mapping for all Types
Omu
I'm going to investigate how to speed the things up though, thanx for suggesting Reflection.Emit and late-bound delegates, not sure about caching
Omu
Caching definitely helped AutoMapper out - the GetFields and GetProperties calls can be a bottleneck - run this in something like dotTrace and you can see where the time is spent. This type of coding is pretty cool, have fun with it!
Jimmy Bogard
thnx, good idea with caching the GetProperties, I can store the Properties for each type in cache so I won't need to get them each time
Omu
@Jimmy Bogard it is fun :), check it out http://valueinjecter.codeplex.com/documentation
Omu