2010-05-13 7 views
11

J'ai besoin d'un objet pour mapper l'objet dans mon application. J'en ai essayé quelques-uns, mais je n'ai pas réussi à trouver quelque chose qui corresponde à mes besoins, alors j'écris le mien. Actuellement, j'ai une interface comme ci-dessous:C# objet basé sur les génériques à l'objet mapper question

public interface IMapper<T, R> { 
    T Map(R obj); 
} 

je mets en œuvre alors une AccountMapper qui associe un client à un compte que:

public class AccountMapper : IMapper<Account, Customer> { 
    Account Map(Customer obj) { 
     // mapping code 
    } 
} 

Cela fonctionne bien jusqu'à présent, mais j'ai plusieurs entités source qui cartographient à la même entité de destination. Par exemple, j'ai un paiement et une facture qui correspondent à BillHistory. Pour ce qui précède, je dois créer deux mappeurs distincts (BillHistoryPaymentMapper et BillHistoryInvoiceMapper), ce qui est très bien. Cependant, j'aimerais pouvoir l'implanter légèrement différemment comme ci-dessous. Le seul problème est que je ne sais pas si c'est possible et si c'est le cas, je ne connais pas la syntaxe correcte. Alors que la première mise en œuvre fonctionne bien, la seconde serait légèrement plus élégante. Est-ce possible et si oui, à quoi ressemblerait la syntaxe correcte?

modifier -------

Je déteste quand les gens font cela, mais bien sûr j'oublié de mentionner un petit détail. Nous avons une classe abstraite entre le mappeur et l'interface pour implémenter une logique commune à tous les mappeurs. Donc, ma signature mappeur est en fait:

public class BillHistoryMapper : Mapper<BillHistory, Invoice> { 
} 

où Mapper contient:

public abstract class Mapper<T, R> : IMapper<T, R> { 
    public IList<T> Map(IList<R> objList) { 
     return objList.ToList<R>().ConvertAll<T>(new Converter<T, R>(Map)); 
    } 
} 

Répondre

3

Vous devez utiliser votre première interface et implémenter l'interface plusieurs fois sur votre objet:

public class BillHistoryMapper : IMapper<Account, Invoice>, 
           IMapper<Account, Payment> {  
    ... 
} 

Je serais sérieux envisager de jeter un oeil à AutoMapper au lieu d'écrire le vôtre. Il y a beaucoup de nuances dans la cartographie qu'il a déjà résolues, sans parler de nombreux tests de performance, corrections de bugs, etc.

+0

voir ci-dessus, le code ne s'affiche pas correctement dans le commentaire – Brian

+1

Vous codez implémenter votre méthode Map en tant que méthode d'extension sur l'interface IMapper. De cette façon, vous n'avez pas besoin d'utiliser une classe de base abstraite. –

+0

J'ai marqué ceci comme la réponse b/c il a répondu à ma question originale. J'ai également pu accomplir ceci sans un cours abstrait en utilisant la suggestion d'Eric. Merci!!! – Brian

0

Votre deuxième exemple travaillera avec seulement quelques changements:

// you have to include the R type in the declaration of the Mapper interface 
public interface IMapper<T, R> { 
    T Map<R>(R obj); 
} 

// You have to specify both IMapper implementations in the declaration 
public class BillHistoryMapper : IMapper<Account, Invoice>, IMapper<Account, Payment> { 
    public BillHistory Map<Invoice>(Invoice obj) { 
     // mapping code 
    } 
    public BillHistory Map<Payment>(Payment obj) { 
     // mapping code 
    } 
} 

Je ne sais pas si cela En fait, vous gagnez quelque chose sur le modèle existant.

0

S'il y a un nombre limité de types que vous voulez mapper, alors j'utiliserais la première méthode de déclaration des types d'entrée et de sortie dans la définition de l'interface. Ensuite, un mappeur peut implémenter des interfaces pour chaque type d'entrée, il prend en charge, de sorte que votre BillHistoryMapper serait déclarée comme:

public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment> 
{ 
    ... 
} 
1

En ce qui concerne votre classe abstraite, pensez à vous en débarrasser et à la remplacer par une méthode d'extension. Cela vous permettra d'utiliser la fonction MapAll, que vous implémentiez l'interface ou que vous utilisiez une sorte de chaîne d'héritage.

public static class MapperExtensions 
{ 
    public static IEnumerable<TOutput> MapAll<TInput, TOutput> 
     (this IMapper<TInput, TOutput> mapper, IEnumerable<TInput> input) 
    { 
     return input.Select(x => mapper.Map(x)); 
    } 
} 

Cela maintenant plus facile lorsque vous essayez de résoudre votre problème ci-dessus parce que vous n'avez plus d'hériter d'une classe de base, vous pouvez maintenant implémenter l'interface de cartographie pour les types que vous souhaitez faire.

public class BillHistoryMapper : 
    IMapper<Invoice, BillHistory>, IMapper<Payment, BillHistory> 
{ 
    public BillHistory Map<Invoice>(Invoice obj) {} 
    public BillHistory Map<Payment>(Payment obj) {} 
} 

Voir également changer vos IMapper paramètres génériques pour être l'inverse (je pris la liberté dans les exemples précédents):

public interface IMapper<in TInput, out TOutput> 
{ 
    TOutput Map(TInput input); 
} 

La raison est que les cartes directement System.Converter<T> délégué et vous pouvez faire quelque chose comme:

IMapper<ObjectA, ObjectB> myAToBMapper = new MyAToBMapper(); 

ObjectA[] aArray = { new ObjectA(), new ObjectA() }; 
ObjectB[] bArray = Array.ConvertAll<ObjectA, ObjectB>(aArray, myAToBMapper.Map); 

List<ObjectA> aList = new List<ObjectA> { new ObjectA(), new ObjectA() }; 
List<ObjectB> bList = aList.ConvertAll<ObjectB>(myAToBMapper.Map); 

// Or 

var aToBConverter = new Converter<ObjectA, ObjectB>(myAToBMapper.Map); 
bArray = Array.ConvertAll(aArray, aToBConverter); 
bList = aList.ConvertAll(aToBConverter); 

AutoMapper a également été suggéré ce qui rendra votre vie plus facile. Cependant, si vous souhaitez conserver votre abstraction de cartographie et que votre code est indépendant de votre stratégie de cartographie, il est très facile d'utiliser l'interface ci-dessus pour injecter un wrapper autour d'AutoMapper. Cela signifie également que vous pouvez continuer à utiliser la méthode d'extension MapAll expliquée ci-dessus.

public class AutoMapperWrapper<in TInput, out TOutput> : IMapper<TInput, TOutput> 
{ 
    public TOutput Map(TInput input) 
    { 
     return Mapper.Map<TOutput>(input); 
    } 
} 

Le dernier mot

Gardez à l'esprit que vous allez pas toujours trouver que votre stratégie de cartographie fonctionnera dans tous les domaines afin de ne pas essayer de combattre votre domaine et le forcer à s'adapter à votre stratégie de cartographie. Un exemple particulier est que vous pourriez avoir à mapper de deux éléments d'entrée en un seul. Vous pouvez évidemment faire en sorte que cela corresponde à votre stratégie, mais vous pouvez trouver que cela devient salissant. Dans cet exemple particulier, considérez cela comme une fusion.