2010-05-13 10 views
11

Ich habe ein Objekt Objekt Mapper in meiner Anwendung Objekt. Ich habe ein paar ausprobiert, aber ich konnte nichts finden, was meinen Bedürfnissen entspricht, also schreibe ich mein eigenes. Zur Zeit habe ich eine Schnittstelle wie unten:C# Generika-basierte Objekt zu Objekt-Mapper-Frage

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

ich dann ein AccountMapper implementieren, die einen Kunden auf ein Konto abbildet als:

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

Das funktioniert so weit in Ordnung, aber ich mehrere Quelleneinheiten, die Karte zu derselben Zieleinheit. Zum Beispiel habe ich eine Zahlung und eine Rechnung, die beide BillHistory zuordnen. Um dies zu unterstützen, muss ich zwei separate Mapper erstellen (dh BillHistoryPaymentMapper und BillHistoryInvoiceMapper), was in Ordnung ist. Wie auch immer, ich würde es gerne etwas anders umsetzen, wie unten beschrieben. Das einzige Problem ist, dass ich nicht weiß, ob es möglich ist und wenn ja, ich kenne die richtige Syntax nicht.

Während die erste Implementierung gut funktioniert, wäre die zweite etwas eleganter. Ist das möglich und wenn ja, wie würde die korrekte Syntax aussehen?

bearbeiten -------

Ich hasse es, wenn Menschen dies tun, aber natürlich habe ich vergessen, ein kleines Detail zu erwähnen. Wir haben eine abstrakte Klasse zwischen dem Mapper und der Schnittstelle, um eine gemeinsame Logik für alle Mapper zu implementieren. Also meine Mapper Unterschrift ist eigentlich:

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

wo Mapper enthält:

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

Antwort

3

Sie müssen Ihre erste Schnittstelle verwenden werden und implementieren die Schnittstelle mehrfach auf Ihrem Objekt:

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

Ich würde ernsthaft in Erwägung ziehen, einen Blick auf AutoMapper anstatt Ihre eigenen zu schreiben. Es gibt viele Nuancen im Mapping, die es bereits gelöst hat, ganz zu schweigen von zahlreichen Performancetests, Fehlerbehebungen usw.

+0

siehe oben, der Code wurde im Kommentar nicht korrekt angezeigt – Brian

+1

Sie implementieren Ihre Map-Methode als eine Erweiterungsmethode auf der IMapper-Schnittstelle. Auf diese Weise müssen Sie keine abstrakte Basisklasse verwenden. –

+0

Ich habe dies als Antwort b/c markiert es meine ursprüngliche Frage beantwortet. Ich konnte dies auch ohne eine abstrakte Klasse mit Erics Vorschlag erreichen. Vielen Dank!!! – Brian

0

Ihr zweites Beispiel wird mit nur wenigen Änderungen arbeiten:

// 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 
    } 
} 

Ich bin mir nicht sicher, ob dies Tatsächlich gewinnt man etwas über das bestehende Muster.

0

Wenn es eine begrenzte Anzahl von Typen gibt, denen Sie zuordnen möchten, würde ich die erste Methode verwenden, um die Eingabe- und Ausgabetypen in der Schnittstellendefinition zu deklarieren. Dann kann ein Mapper implementieren Schnittstellen für jeden Eingabetyp unterstützt, so dass Ihre BillHistoryMapper erklärt werden würde, wie:

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

Im Hinblick auf Ihre abstrakte Klasse betrachten Sie es loszuwerden und zu ersetzen eine Erweiterungsmethode. Auf diese Weise können Sie die MapAll-Funktion verwenden, unabhängig davon, ob Sie die Schnittstelle implementieren oder eine Art von Vererbungskette verwenden.

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

Dies wird nun es einfacher machen, wenn sie versuchen, Ihr Problem oben zu lösen, weil Sie nicht mehr von einer Basisklasse erben, müssen Sie nun das Mapping-Schnittstelle für die Typen implementieren können Sie zuordnen möchten.

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

Auch Ihre IMapper generischen Parameter betrachten Wechsel rund um die andere Art und Weise zu sein (habe ich die Freiheit in den vorherigen Beispielen):

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

Der Grund dafür ist, dass es direkt Karten zu den System.Converter<T> delegieren und Sie können wie etwas tun:

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 wurde auch vorgeschlagen, die Ihnen das Leben leichter machen. Wenn Sie jedoch Ihre Mapping-Abstraktion beibehalten und Ihren Code für Ihre Mapping-Strategie agnostisch halten möchten, ist es sehr einfach, die obige Schnittstelle zu verwenden, um einen Wrapper um AutoMapper zu injizieren. Es bedeutet auch, dass Sie weiterhin die oben erläuterte Erweiterungsmethode MapAll verwenden können.

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

Schlusswort

im Auge behalten, auch Sie sind nicht immer gehen zu finden, dass Ihre Mapping-Strategie auf der ganzen Linie arbeiten, so versuchen Sie nicht, Ihre Domain zu kämpfen und ihn mit Gewalt zu Ihren passen Mapping-Strategie. Ein spezielles Beispiel besteht darin, dass Sie möglicherweise zwei Eingabeelemente in einem zusammenfassen müssen. Sie können dies natürlich zu Ihrer Strategie machen, aber Sie werden feststellen, dass es unordentlich wird. In diesem speziellen Beispiel betrachten Sie es als eine Zusammenführung.