Frage

Ich habe die Notwendigkeit für ein Objekt zu Objekt-Mapper in meiner Anwendung. Ich habe aus einem paar versucht, habe aber nicht in der Lage, etwas zu finden, die meine Bedürfnisse passen, so dass ich meine eigen ich schreibe. Zur Zeit habe ich eine Schnittstelle wie folgt:

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

ich implementieren dann eine AccountMapper, dass ein Kunde mit einem Kontokarten wie:

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

Das funktioniert gut, so weit, aber ich mehrere Quelleneinheiten, dass auf derselben Zieleinheit habe. Zum Beispiel habe ich eine Zahlung und eine Rechnung, dass sowohl Karte BillHistory. Für die oben, dies zu unterstützen, muss ich zwei separate Mapper (dh. BillHistoryPaymentMapper und BillHistoryInvoiceMapper) machen, was in Ordnung ist. Ich würde jedoch gerne in der Lage sein, es wie unten etwas anders zu implementieren. Einziges Problem ist, ich weiß nicht, ob es möglich ist und wenn ja, ich weiß nicht, die korrekte Syntax.

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

public class BillHistoryMapper : IMapper<Account> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

Während die erste Implementierung funktioniert gut, würde die zweite etwas eleganter sein. Ist dies möglich, und wenn ja, was wäre die richtige 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 die Schnittstelle eine gemeinsame Logik über alle der Mapper zu implementieren. Also meine Mapper Unterschrift ist eigentlich:

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

Dabei gilt 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));
    }
}
War es hilfreich?

Lösung

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

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

Ich würde ernst, einen Blick auf AutoMapper stattdessen Ihre eigenen zu schreiben. Es gibt viele Nuancen in Mapping, dass es bereits gelöst hat, nicht zu erwähnen, es durch viele Performance-Tests ist, Bug-Fixes, etc.

Andere Tipps

Im Hinblick auf Ihre abstrakte Klasse betrachtet es loszuwerden und es mit einer Erweiterung Methode zu ersetzen. Dies ermöglicht es Ihnen, die MapAll Funktion unabhängig davon zu verwenden, ob Sie die Schnittstelle implementieren oder irgendeine Art von Vererbungskette verwendet werden.

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 zu erben von einer Basisklasse Sie nun die 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) {}
}

Sehen Sie sich auch Ihre IMapper allgemeine Parameter zu ändern umgekehrt zu sein (ich nahm sich 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 abbildet auf 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 halten wollten und haben Ihren Code Agnostiker Ihre Mapping-Strategie, dann ist es sehr einfach, die oben Schnittstelle zu verwenden, um einen Wrapper um AutoMapper zu injizieren. Es bedeutet auch, können Sie weiterhin die MapAll Erweiterungsmethode oben erläutert verwendet werden.

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

Schlusswort

Beachten Sie auch, Sie werden nicht immer feststellen, dass Ihre Mapping-Strategie auf der ganzen Linie arbeiten, so versuchen Sie nicht, Ihre Domain zu kämpfen und es zu zwingen, Ihre Mapping-Strategie zu passen. Ein besonderes Beispiel ist, dass Sie vielleicht aus zwei Eingabeelementen in eine zur Karte haben. Sie können offenbar damit diese Ihre Strategie passen, aber man kann feststellen, es chaotisch wird. In diesem speziellen Beispiel halte es für eine Zusammenführung.

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, though.

Wenn es eine begrenzte Anzahl von Typen, die Sie von abbilden wollen, dann würde ich die erste Methode der Deklaration der Ein- und Ausgangstypen in der Schnittstellendefinition verwenden. Dann kann ein Mapper implementieren Schnittstellen für jeden Eingabetyp unterstützt, so dass Ihr BillHistoryMapper als deklariert werden würde:

public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
   ...
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top