Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can automapper map a foreign key to an object using a repository?

I'm trying out Entity Framework Code first CTP4. Suppose I have:

public class  Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Parent Mother { get; set; }
}

public class TestContext : DbContext
{
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }
}

public class ChildEdit
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int MotherId { get; set; }
}

Mapper.CreateMap<Child, ChildEdit>();

Mapping to the Edit model is not a problem. On my screen I select the mother through some control (dropdownlist, autocompleter, etc) and the Id of the mother gets posted in back:

[HttpPost]
public ActionResult Edit(ChildEdit posted)
{
    var repo = new TestContext();

    var mapped = Mapper.Map<ChildEdit, Child>(posted);  // <------- ???????
}

How should I solve the last mapping? I don't want to put Mother_Id in the Child object. For now I use this solution, but I hope it can be solved in Automapper.

        Mapper.CreateMap<ChildEdit, Child>()
            .ForMember(i => i.Mother, opt => opt.Ignore());

        var mapped = Mapper.Map<ChildEdit, Child>(posted);
        mapped.Mother = repo.Parents.Find(posted.MotherId);

EDIT This works, but now I have to do that for each foreign key (BTW: context would be injected in final solution):

        Mapper.CreateMap<ChildEdit, Child>();
            .ForMember(i => i.Mother,
                       opt => opt.MapFrom(o => 
                              new TestContext().Parents.Find(o.MotherId)
                                         )
                      );

What I'd really like would be:

        Mapper.CreateMap<int, Parent>()
            .ForMember(i => i, 
                       opt => opt.MapFrom(o => new TestContext().Parents.Find(o))
                      );

        Mapper.CreateMap<ChildEdit, Child>();

Is that possible with Automapper?

like image 957
John Landheer Avatar asked Aug 19 '10 18:08

John Landheer


People also ask

Can AutoMapper map collections?

Polymorphic element types in collectionsAutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.

Does AutoMapper map private properties?

By default, AutoMapper only recognizes public members. It can map to private setters, but will skip internal/private methods and properties if the entire property is private/internal.

Where is AutoMapper used?

AutoMapper is used whenever there are many data properties for objects, and we need to map them between the object of source class to the object of destination class, Along with the knowledge of data structure and algorithms, a developer is required to have excellent development skills as well.


1 Answers

First, I'll assume that you have a repository interface like IRepository<T>

Afterwards create the following class:

public class EntityConverter<T> : ITypeConverter<int, T>
{
    private readonly IRepository<T> _repository;
    public EntityConverter(IRepository<T> repository)
    {
        _repository = repository;
    }
    public T Convert(ResolutionContext context)
    {
        return _repository.Find(System.Convert.ToInt32(context.SourceValue));       
    }
}

Basically this class will be used to do all the conversion between an int and a domain entity. It uses the "Id" of the entity to load it from the Repository. The IRepository will be injected into the converter using an IoC container, but more and that later.

Let's configure the AutoMapper mapping using:

Mapper.CreateMap<int, Mother>().ConvertUsing<EntityConverter<Mother>>();

I suggest creating this "generic" mapping instead so that if you have other references to "Mother" on other classes they're mapped automatically without extra-effort.

Regarding the Dependency Injection for the IRepository, if you're using Castle Windsor, the AutoMapper configuration should also have:

IWindsorContainer container = CreateContainer();
Mapper.Initialize(map => map.ConstructServicesUsing(container.Resolve));

I've used this approach and it works quite well.

like image 82
psousa Avatar answered Sep 20 '22 05:09

psousa