我正在研究一个严重依赖Automapper的项目,例如,大多数时候,我们会将完整的数据集映射到一组视图模型中
IEnumerable<ObjectA> ListOfObjectA = MockupOfObjectA;
IEnumerable<ViewModelA> = Mapper.Map<IEnumerable<ObjectA>>(ListOfOjectA)
在映射设置中,由于使用了IMemberValueResolver,我们正在使用“自定义”解析器。 Resolve和ResolveStatic方法中的参数和可访问数据只是被映射的当前实体。在这种情况下,是否可以在解析器内部访问完整的源(ListOfOjectA)?
到目前为止,我将ListOfOjectA添加到MappingOperationsOptions.Items中并从context.Items中使用它们,但这是一种不易使用且无法很好扩展的变通方法。
我希望我的问题比较清楚。
答案 0 :(得分:1)
值得指出的是,您并不是真正将self
映射到ObjectA
。宁可将ViewModelA
,ObjectA
改为List<ObjectA>
,因为如果没有ViewModelA
,您似乎无法定义ViewModelA
。
要模拟,假设List<ObjectA>
具有ObjectA
属性及其包含的Index
数量。
Pages
对于public class ObjectA
{
public int Index { get; set; }
public int Pages { get; set; }
public string MyProperty { get; set; }
}
,我们要根据先前ViewModelA
的属性来解析StartPage
。
ObjectA
我们可以使用扩展方法来清理您当前的方法。
public class ViewModelA
{
public int StartPage { get; set; }
public string MyProperty { get; set; }
}
使用这些方法,我们不再需要直接处理上下文的public static class AutoMapperExt
{
public static TDestination MapWithSource<TSource, TDestination>(this IMapper mapper, TSource source)
=> mapper.Map<TSource, TDestination>(source, opts => opts.Items[typeof(TSource).ToString()] = source);
public static TSource GetSource<TSource>(this ResolutionContext context)
=> (TSource)context.Items[typeof(TSource).ToString()];
}
集合。
Items
如果要在不同的类上重用class Program
{
static void Main(string[] args)
{
var config =
new MapperConfiguration(cfg =>
cfg.CreateMap<ObjectA, ViewModelA>()
.ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver, int>(src => src.Index))
);
var mapper = config.CreateMapper();
var source = new List<ObjectA>
{
new ObjectA { Index = 0, Pages = 3, MyProperty = "Foo" },
new ObjectA { Index = 1, Pages = 2, MyProperty = "Bar" },
new ObjectA { Index = 2, Pages = 1, MyProperty = "Foz" },
};
var result = mapper.MapWithSource<List<ObjectA>, List<ViewModelA>>(source);
result.ForEach(o => Console.WriteLine(o.StartPage)); // prints 1,4,6
Console.ReadKey();
}
}
public class CustomResolver : IMemberValueResolver<object, object, int, int>
{
public int Resolve(object source, object destination, int sourceMember, int destMember, ResolutionContext context)
{
var index = sourceMember;
var list = context.GetSource<List<ObjectA>>();
var pages = 1;
for (int i = 0; i < index; i++)
{
pages += list[i].Pages;
}
return pages;
}
}
,则可以将其操作的属性抽象到接口中。
CustomResolver
通过这种方式,解析程序不再绑定到具体的实现。现在,我们甚至可以将接口用作类型参数。
public interface IHavePages
{
int Index { get; }
int Pages { get; }
}
public class ObjectA : IHavePages
{
public int Index { get; set; }
public int Pages { get; set; }
public string MyProperty { get; set; }
}
我们需要做的就是在映射之前转换public class CustomResolver : IMemberValueResolver<IHavePages, object, int, int>
{
public int Resolve(IHavePages source, object destination, int sourceMember, int destMember, ResolutionContext context)
{
var hasPages = source;
var index = sourceMember;
var list = context.GetSource<List<IHavePages>>();
var pages = 1;
for (int i = 0; i < index; i++)
{
pages += list[i].Pages;
}
return pages;
}
}
。
List<ObjectA>
一旦您对接口进行编码,var listOfObjectA = new List<ObjectA>
{
new ObjectA { Index = 0, Pages = 3, MyProperty = "Foo" },
new ObjectA { Index = 1, Pages = 2, MyProperty = "Bar" },
new ObjectA { Index = 2, Pages = 1, MyProperty = "Foz" },
};
var source = listOfObjectA.OfType<IHavePages>().ToList();
var result = mapper.MapWithSource<List<IHavePages>, List<ViewModelA>>(source);
// AutoMapper still maps properties that aren't part of the interface
result.ForEach(o => Console.WriteLine($"{o.StartPage} - {o.MyProperty}"));
中的sourceMember
就变得多余了。现在,我们可以通过传递的CustomResolver
来获取它。由于我们是从source
而不是IValueResolver
派生而来的,因此允许一个最终的重构。
IMemberValueResolver
更新签名。
public class CustomResolver : IValueResolver<IHavePages, object, int>
{
public int Resolve(IHavePages source, object destination, int destMember, ResolutionContext context)
{
var list = context.GetSource<List<IHavePages>>();
var pages = 1;
for (int i = 0; i < source.Index; i++)
{
pages += list[i].Pages;
}
return pages;
}
}
您可以完全依靠它,但是可以通过引入抽象来提高代码重用性。
答案 1 :(得分:0)
您可以通过以下方式映射dto集合或其他类中的项目集合。
public Order Convert(OrderDto orderDto)
{
var order = new Order { OrderLines = new OrderLines() };
order.OrderLines = Mapper.Map<List<OrderLine>>(orderDto.Positions);
return order;
}
您的个人档案类构造函数可以用某种方式编写。这仅仅是一个例子。您不需要在解析器中接受列表,您可以为一个对象进行处理,然后从外部映射到列表。
public Profile()
{
CreateMap<PositionDto, OrderLine>()
.ForMember(dest => dest, opt => opt.ResolveUsing<OrderResolver>());
}
}
}
答案 2 :(得分:0)
如果您不想使用ResolutionContext
,则可以通过一个既包含当前源项目又包含完整源列表的中间对象来建立映射。
使用轻量值类型,例如。 Tuple
或ValueTuple。
下面的映射使用ValueTuple
(但也可以使用Tuple
表示)。
注意,这种映射的意图和前提条件非常明确。它表示需要2个输入/源元素:ObjectA
和IEnumerable<ObjectA>
(通过ValueTuple
传递)。
Mapper.Initialize(cfg =>
cfg.CreateMap<(ObjectA, IEnumerable<ObjectA>), ViewModelA>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom<CustomResolver>()
));
在映射时,您将源列表投影到相应的ValueTuple
之一中。
首选仅使用1个当前ValueTuple
保持流的流状态。
var viewModels =
Mapper.Map<IEnumerable<ViewModelA>>(
ListOfObjectA.Select(o => (o, ListOfObjectA))
);
自定义IValueResolver
通过ValueTuple
源参数接收当前输入项和完整列表。
public class CustomResolver :
IValueResolver<
(ObjectA Item, IEnumerable<ObjectA> List),
ViewModelA,
String
>
{
public string Resolve(
(ObjectA Item, IEnumerable<ObjectA> List) source,
ViewModelA destination,
string destMember,
ResolutionContext context
)
{
/* Retrieve something via the list. */
var suffix = source.List.Count().ToString();
return $"{source.Item.Name} {suffix}";
}
}
完整示例。
IEnumerable<ObjectA> ListOfObjectA = new List<ObjectA> {
new ObjectA { Name = "One" },
new ObjectA { Name = "Two" },
new ObjectA { Name = "Three" }
};
Mapper.Initialize(cfg =>
cfg.CreateMap<(ObjectA, IEnumerable<ObjectA>), ViewModelA>()
.ForMember(
dest => dest.Name,
opt => opt.MapFrom<CustomResolver>()
));
var viewModels =
Mapper.Map<IEnumerable<ViewModelA>>(
ListOfObjectA.Select(o => (o, ListOfObjectA))
);