2010-08-19 16 views
16

Estoy probando Entity Framework Code first CTP4. Supongamos que tengo:¿Puede el Automapper asignar una clave externa a un objeto utilizando un repositorio?

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>(); 

La asignación al modelo de edición no es un problema. En mi pantalla selecciono la madre a través de algún tipo de control (lista desplegable, Autocompleter, etc) y el ID de la madre se ha escrito en la espalda:

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

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

¿Cómo debería resolver el último mapeo? No quiero poner Mother_Id en el objeto Child. Por ahora uso esta solución, pero espero que se pueda resolver en 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); 

EDITAR Esto funciona, pero ahora tengo que ver que para cada clave externa (Por cierto: contexto sería inyectado en la solución final):

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

Lo que me gusta mucho sería:

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

     Mapper.CreateMap<ChildEdit, Child>(); 

¿Eso es posible con Automapper?

Respuesta

17

En primer lugar, voy a suponer que tiene una interfaz de depósito como IRepository<T>

A continuación, cree la siguiente clase:

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));  
    } 
} 

Básicamente esta clase se usará para hacer toda la conversión entre una entidad int y una entidad de dominio. Utiliza el "Id" de la entidad para cargarlo desde el Repositorio. El IRepository se inyectará en el convertidor usando un contenedor IoC, pero más y más tarde.

Vamos a configurar el mapeo AutoMapper usando:

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

Sugiero crear este mapeo "genérico" en lugar de modo que si usted tiene otras referencias a la "madre" de otras clases que estamos asignada automáticamente sin adicional -esfuerzo.

En cuanto a la inyección de dependencia para el IRepository, si está usando el castillo de Windsor, la configuración AutoMapper también debe tener:

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

he utilizado este enfoque y funciona bastante bien.

+0

Funciona como un amuleto. Gracias. –

+0

¿Podría mostrarme cómo usar su solución cuando quiero asignar el ID a la entidad? Me refiero a cómo mapear cuando implementé todo? – user2412672

6

Así es como lo hice: (usando ValueInjecter)
Hice los requisitos de un poco más grande sólo para mostrar cómo funciona


[TestFixture] 
public class JohnLandheer 
{ 
    [Test] 
    public void Test() 
    { 
     var child = new Child 
     { 
      Id = 1, 
      Name = "John", 
      Mother = new Parent { Id = 3 }, 
      Father = new Parent { Id = 9 }, 
      Brother = new Child { Id = 5 }, 
      Sister = new Child { Id = 7 } 
     }; 
     var childEdit = new ChildEdit(); 

     childEdit.InjectFrom(child) 
       .InjectFrom<EntityToInt>(child); 

     Assert.AreEqual(1, childEdit.Id); 
     Assert.AreEqual("John", childEdit.Name); 
     Assert.AreEqual(3, childEdit.MotherId); 
     Assert.AreEqual(9, childEdit.FatherId); 
     Assert.AreEqual(5, childEdit.BrotherId); 
     Assert.AreEqual(7, childEdit.SisterId); 
     Assert.AreEqual(0, childEdit.Sister2Id); 

     var c = new Child(); 

     c.InjectFrom(childEdit) 
      .InjectFrom<IntToEntity>(childEdit); 

     Assert.AreEqual(1, c.Id); 
     Assert.AreEqual("John", c.Name); 
     Assert.AreEqual(3, c.Mother.Id); 
     Assert.AreEqual(9, c.Father.Id); 
     Assert.AreEqual(5, c.Brother.Id); 
     Assert.AreEqual(7, c.Sister.Id); 
     Assert.AreEqual(null, c.Sister2); 
    } 

    public class Entity 
    { 
     public int Id { get; set; } 
    } 

    public class Parent : Entity 
    { 
     public string Name { get; set; } 
    } 

    public class Child : Entity 
    { 
     public string Name { get; set; } 
     public Parent Mother { get; set; } 
     public Parent Father { get; set; } 
     public Child Brother { get; set; } 
     public Child Sister { get; set; } 
     public Child Sister2 { get; set; } 
    } 

    public class ChildEdit 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
     public int MotherId { get; set; } 
     public int FatherId { get; set; } 
     public int BrotherId { get; set; } 
     public int SisterId { get; set; } 
     public int Sister2Id { get; set; } 
    } 

    public class EntityToInt : LoopValueInjection 
    { 
     protected override bool TypesMatch(Type sourceType, Type targetType) 
     { 
      return sourceType.IsSubclassOf(typeof(Entity)) && targetType == typeof(int); 
     } 

     protected override string TargetPropName(string sourcePropName) 
     { 
      return sourcePropName + "Id"; 
     } 

     protected override bool AllowSetValue(object value) 
     { 
      return value != null; 
     } 

     protected override object SetValue(object sourcePropertyValue) 
     { 
      return (sourcePropertyValue as Entity).Id; 
     } 
    } 

    public class IntToEntity : LoopValueInjection 
    { 
     protected override bool TypesMatch(Type sourceType, Type targetType) 
     { 
      return sourceType == typeof(int) && targetType.IsSubclassOf(typeof(Entity)); 
     } 

     protected override string TargetPropName(string sourcePropName) 
     { 
      return sourcePropName.RemoveSuffix("Id"); 
     } 

     protected override bool AllowSetValue(object value) 
     { 
      return (int)value > 0; 
     } 

     protected override object SetValue(object sourcePropertyValue) 
     { 
      // you could as well do repoType = IoC.Resolve(typeof(IRepo<>).MakeGenericType(TargetPropType)) 
      var repoType = typeof (Repo<>).MakeGenericType(TargetPropType); 
      var repo = Activator.CreateInstance(repoType); 
      return repoType.GetMethod("Get").Invoke(repo, new[] {sourcePropertyValue}); 
     } 
    } 

    class Repo<T> : IRepo<T> where T : Entity, new() 
    { 
     public T Get(int id) 
     { 
      return new T{Id = id}; 
     } 
    } 

    private interface IRepo<T> 
    { 
     T Get(int id); 
    } 
} 
+0

Gracias. Quería evitar tener que crear un repositorio en la lógica de mapeo porque es caro, pero de su solución entiendo que no es cierto. ¿Derecha?. Trataré de obtener algo similar en Automapper y publicar mi solución. –

+0

tienes que obtener los datos de alguna parte :), normalmente hago IoC.Resolve (tipo), y eso no sería caro porque el contenedor IoC dará una instancia ya creada – Omu

+0

Aquí lo hice súper genérico porque quería muestra cómo manejas múltiples propiedades de diferentes tipos con una sola inyección, pero por supuesto es posible hacerlo sin MakeGenericType, method.invoke y todo eso, pero lo más probable es que tengas que crear inyecciones múltiples, puedes mirar el " Getting started "del ValueInjecter – Omu

2

Es posible definir la clave externa de EF de esta manera también:

[ForeignKey("MotherId")] 
public virtual Parent Mother { get; set; } 
public int MotherId { get; set; } 

En este caso, no es necesario hacer una consulta adicional para encontrar la madre.Simplemente asigne MotherId de ViewModel al MotherId del modelo.

Cuestiones relacionadas