Automapper - 获取解析器中的所有条目
Automapper - Get all entries in resolver
我正在做一个严重依赖 Automapper 的项目,大多数时候我们将完整的数据集映射到一组视图模型中,例如
IEnumerable<ObjectA> ListOfObjectA = MockupOfObjectA;
IEnumerable<ViewModelA> = Mapper.Map<IEnumerable<ObjectA>>(ListOfOjectA)
由于 IMemberValueResolver,我们在映射设置中使用自定义解析器。 Resolve 和 ResolveStatic 方法中的参数和可访问数据只是被映射的当前实体。在这种情况下,是否可以在解析器内部访问完整的源代码 (ListOfOjectA)?
到目前为止,我正在将 ListOfOjectA 添加到 MappingOperationsOptions.Items 中并从 context.Items 开始使用它们,但这是一种不容易使用且无法很好扩展的解决方法。
希望我的问题说得比较清楚。
您可以通过以下方式映射来自 dto 集合或其他 class 的项目集合。
public Order Convert(OrderDto orderDto)
{
var order = new Order { OrderLines = new OrderLines() };
order.OrderLines = Mapper.Map<List<OrderLine>>(orderDto.Positions);
return order;
}
而你的配置文件class构造函数可以这样写。这只是一个例子。您不需要在解析器中接受列表,您可以为一个对象执行此操作并从外部映射到列表。
public Profile()
{
CreateMap<PositionDto, OrderLine>()
.ForMember(dest => dest, opt => opt.ResolveUsing<OrderResolver>());
}
}
}
值得指出的是,您并不是真正将 ObjectA
映射到 ViewModelA
。而是 (ObjectA
, List<ObjectA>
) 到 ViewModelA
,因为如果没有 List<ObjectA>
.
,您似乎无法定义 ViewModelA
为了模拟,假设 ObjectA
有一个 Index
属性 以及它包含的 Pages
的数量。
public class ObjectA
{
public int Index { get; set; }
public int Pages { get; set; }
public string MyProperty { get; set; }
}
而对于 ViewModelA
,我们要根据之前 ObjectA
的属性解析 StartPage
。
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}"));
一旦你对接口进行编码,CustomResolver
中的 sourceMember
就变得多余了。我们现在可以通过传过来的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;
}
}
正在更新签名。
cfg.CreateMap<ObjectA, ViewModelA>()
.ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver>())
你能走多远完全取决于你,但你可以通过引入抽象来提高代码重用。
如果您不想使用 ResolutionContext
,您可以通过包含当前源项目和完整源列表的中间对象设置映射。
使用轻量级值类型,例如。一个 Tuple
或 ValueTuple.
下面的映射使用 ValueTuple
(但也可以使用 Tuple
表示)。
请注意,此映射的意图和先决条件非常明确;它表示需要 2 个 input/source 元素: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))
);
我正在做一个严重依赖 Automapper 的项目,大多数时候我们将完整的数据集映射到一组视图模型中,例如
IEnumerable<ObjectA> ListOfObjectA = MockupOfObjectA;
IEnumerable<ViewModelA> = Mapper.Map<IEnumerable<ObjectA>>(ListOfOjectA)
由于 IMemberValueResolver,我们在映射设置中使用自定义解析器。 Resolve 和 ResolveStatic 方法中的参数和可访问数据只是被映射的当前实体。在这种情况下,是否可以在解析器内部访问完整的源代码 (ListOfOjectA)?
到目前为止,我正在将 ListOfOjectA 添加到 MappingOperationsOptions.Items 中并从 context.Items 开始使用它们,但这是一种不容易使用且无法很好扩展的解决方法。
希望我的问题说得比较清楚。
您可以通过以下方式映射来自 dto 集合或其他 class 的项目集合。
public Order Convert(OrderDto orderDto)
{
var order = new Order { OrderLines = new OrderLines() };
order.OrderLines = Mapper.Map<List<OrderLine>>(orderDto.Positions);
return order;
}
而你的配置文件class构造函数可以这样写。这只是一个例子。您不需要在解析器中接受列表,您可以为一个对象执行此操作并从外部映射到列表。
public Profile()
{
CreateMap<PositionDto, OrderLine>()
.ForMember(dest => dest, opt => opt.ResolveUsing<OrderResolver>());
}
}
}
值得指出的是,您并不是真正将 ObjectA
映射到 ViewModelA
。而是 (ObjectA
, List<ObjectA>
) 到 ViewModelA
,因为如果没有 List<ObjectA>
.
ViewModelA
为了模拟,假设 ObjectA
有一个 Index
属性 以及它包含的 Pages
的数量。
public class ObjectA
{
public int Index { get; set; }
public int Pages { get; set; }
public string MyProperty { get; set; }
}
而对于 ViewModelA
,我们要根据之前 ObjectA
的属性解析 StartPage
。
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}"));
一旦你对接口进行编码,CustomResolver
中的 sourceMember
就变得多余了。我们现在可以通过传过来的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;
}
}
正在更新签名。
cfg.CreateMap<ObjectA, ViewModelA>()
.ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver>())
你能走多远完全取决于你,但你可以通过引入抽象来提高代码重用。
如果您不想使用 ResolutionContext
,您可以通过包含当前源项目和完整源列表的中间对象设置映射。
使用轻量级值类型,例如。一个 Tuple
或 ValueTuple.
下面的映射使用 ValueTuple
(但也可以使用 Tuple
表示)。
请注意,此映射的意图和先决条件非常明确;它表示需要 2 个 input/source 元素: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))
);