2010-08-19 17 views
16

Sto provando Entity Framework Code prima CTP4. Supponiamo di avere:Il programma di automapper può mappare una chiave esterna a un oggetto utilizzando un repository?

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 mappatura al modello Modifica non è un problema. Sul mio schermo seleziono madre attraverso un certo controllo (DropDownList, autocompletamento, ecc) e l'Es della madre viene scritto sul retro:

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

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

Come devo risolvere l'ultima mappatura? Non voglio mettere Mother_Id nell'oggetto Child. Per ora uso questa soluzione, ma spero che possa essere risolta con 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 questo funziona, ma ora devo farlo per ogni chiave esterna (BTW: contesto sarebbe iniettato soluzione finale):

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

Quello che mi piacerebbe molto sarebbe:

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

     Mapper.CreateMap<ChildEdit, Child>(); 

Ciò è possibile con Automapper?

risposta

17

In primo luogo, darò per scontato che avete un'interfaccia repository come IRepository<T>

Successivamente creare la seguente classe:

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

In pratica questa classe verrà utilizzata per eseguire tutte le conversioni tra un'entità int e un'entità di dominio. Utilizza l'"Id" dell'entità per caricarlo dal repository. L'IRepository sarà iniettato nel convertitore usando un contenitore IoC, ma più e più tardi.

Diamo configurare la mappatura automapper utilizzando:

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

Suggerisco di creare questa mappatura "generico", invece in modo che se si hanno altri riferimenti a "Madre" su altre classi sono mappati automaticamente senza ulteriore -sforzo.

Per quanto riguarda il Dependency Injection per l'IRepository, se si sta utilizzando il Castello di Windsor, la configurazione automapper dovrebbe anche avere:

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

Ho usato questo metodo e funziona abbastanza bene.

+0

Funziona come un incantesimo. Grazie. –

+0

Puoi mostrare come usare la tua soluzione quando voglio mappare l'id all'entità? Voglio dire come mappare quando ho implementato tutto? – user2412672

6

Ecco come ho fatto: (usando ValueInjecter)
Ho fatto i requisiti un po 'più grande solo per mostrare come funziona


[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

Grazie. Volevo evitare di dover creare un repository nella logica di mappatura perché è costoso, ma dalla tua soluzione ho capito che non è vero. Destra?. Proverò a ottenere qualcosa di simile in Automapper e pubblicare la mia soluzione. –

+0

devi ottenere i dati da qualche parte :), di solito faccio IoC.Resolve (tipo), e che non sarebbe costoso perché il contenitore IoC darà un'istanza già creata – Omu

+0

In qui l'ho fatto uber generico perché volevo mostra come gestisci più proprietà di diversi tipi con una sola iniezione, ma ovviamente è possibile farlo senza il MakeGenericType, method.invoke e tutto questo ma molto probabilmente dovresti creare più iniezioni, puoi guardare il " iniziare "del ValueInjecter – Omu

2

E 'possibile definire la chiave esterna in EF questo modo pure:

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

In questo caso, non è necessario fare una query in più per trovare la madre.Basta assegnare il MotherId di ViewModel al MotherId del modello.

Problemi correlati