我需要一个对象来在我的应用程序中对象映射器。我试过了一些,但是找不到符合我需要的东西,所以我自己写的。目前我有一个如下界面:
public interface IMapper<T, R> {
T Map(R obj);
}
然后我实现了一个AccountMapper,它将Customer映射到帐户:
public class AccountMapper : IMapper<Account, Customer> {
Account Map(Customer obj) {
// mapping code
}
}
到目前为止这工作正常,但我有几个源实体映射到同一目标实体。例如,我有一个付款和一个发票都映射到BillHistory。为了支持这一点,我需要制作两个独立的映射器(即BillHistoryPaymentMapper和BillHistoryInvoiceMapper),这很好。但是,我希望能够像下面那样略微不同地实现它。唯一的问题是我不知道是否可能,如果是,我不知道正确的语法。
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
}
}
虽然第一个实现工作正常,但第二个实现更优雅。这是可能的,如果是这样,正确的语法是什么样的?
编辑-------
我讨厌人们这样做,但当然我忘了提一个小细节。我们在mapper和接口之间有一个抽象类,用于在所有映射器中实现一些通用逻辑。所以我的映射器签名实际上是:
public class BillHistoryMapper : Mapper<BillHistory, Invoice> {
}
Mapper包含:
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));
}
}
答案 0 :(得分:4)
您必须使用第一个界面并在对象上多次实现界面:
public class BillHistoryMapper : IMapper<Account, Invoice>,
IMapper<Account, Payment> {
...
}
我会认真考虑看一下AutoMapper而不是写自己的。它已经解决了映射中的许多细微差别,更不用说它已经通过大量的性能测试,错误修复等。
答案 1 :(得分:1)
关于你的抽象类,考虑去除它并用扩展方法替换它。这将允许您使用MapAll
函数,无论您是实现接口还是使用某种继承链。
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));
}
}
现在,在尝试解决上述问题时,这将变得更加容易,因为您不再需要从基类继承,现在可以为要映射的类型实现映射接口。
public class BillHistoryMapper :
IMapper<Invoice, BillHistory>, IMapper<Payment, BillHistory>
{
public BillHistory Map<Invoice>(Invoice obj) {}
public BillHistory Map<Payment>(Payment obj) {}
}
另外,请考虑将您的IMapper
通用参数更改为另一种方式(我在前面的示例中取得了自由):
public interface IMapper<in TInput, out TOutput>
{
TOutput Map(TInput input);
}
原因是它直接映射到System.Converter<T>
委托,您可以执行以下操作:
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也被建议使您的生活更轻松。但是,如果您希望保留映射抽象并使您的代码与映射策略无关,那么使用上述接口为AutoMapper注入包装器非常容易。这也意味着您可以继续使用上面解释的MapAll
扩展方法。
public class AutoMapperWrapper<in TInput, out TOutput> : IMapper<TInput, TOutput>
{
public TOutput Map(TInput input)
{
return Mapper.Map<TOutput>(input);
}
}
最后一句话
另请注意,您并不总是会发现您的映射策略将全面运行,因此请勿尝试对抗您的域并强制它适合您的映射策略。一个特定示例是您可能必须将两个输入项映射为一个。你可以明显地使这适合你的策略,但你可能会发现它变得混乱。在这个特定的例子中,将其视为合并。
答案 2 :(得分:0)
您的第二个示例仅适用于一些更改:
// 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
}
}
我不确定这是否真的能让你获得现有模式的任何东西。
答案 3 :(得分:0)
如果要映射的类型数量有限,那么我将使用第一种在接口定义中声明输入和输出类型的方法。然后mapper可以为它支持的每种输入类型实现接口,因此BillHistoryMapper
将被声明为:
public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
...
}