自动映射器将 IList<> 映射到 Iesi.Collections.Generic.ISet<>

标签 automapper

我在标题中提到的映射中遇到了一些问题。以下是详细信息:

class MyDomain
{
   public Iesi.Collections.Generic.ISet<SomeType> MySomeTypes{ get; set; }
   ....
}


class MyDTO
{
  public IList<SomeTypeDTO> MySomeTypes{ get; set; }
  ...
}

映射:
Mapper.CreateMap<MyDomain, MyDTO>().ForMember(dto=>dto.MySomeTypes, opt.ResolveUsing<DomaintoDTOMySomeTypesResolver>());

Mapper.CreateMap<MyDTO, MyDomain>().ForMember(domain=>domain.MySomeTypes, opt.ResolveUsing<DTOtoDomainMySomeTypesResolver>());

解析器:
class DomaintoDTOMySomeTypesResolver: ValueResolver<MyDomain, IList<SomeTypeDTO>> 
{
  protected override IList<SomeTypeDTO> ResolveCore(MyDomain source)
  {
      IList<SomeTypeDTO> abc = new List<DemandClassConfigurationDTO>();
      //Do custom mapping
      return abc;
  }
}

class DTOtoDomainMySomeTypesResolver: ValueResolver<MyDTO, Iesi.Collections.Generic.ISet<SomeType>> 
{
   protected override Iesi.Collections.Generic.ISet<SomeType> ResolveCore(SystemParameterDTO source)
   {
     Iesi.Collections.Generic.ISet<SomeType> abc = new HashedSet<SomeType>();
     //Do custom mapping
     return abc;
   }
}

从域映射到 DTO 工作正常,正如预期的那样,我得到了一个带有 IList 的“SomeTypeDTO”对象的 MyDTO 对象。
但是,将 DTO 映射到域会引发以下错误:
 Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
  ----> AutoMapper.AutoMapperMappingException : Trying to map  Iesi.Collections.Generic.HashedSet`1[SomeType, MyAssembly...] to Iesi.Collections.Generic.ISet`1[SomeType, MyAssembly...]

 Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
  ----> System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.List`1[SomeType]' to type 'Iesi.Collections.Generic.ISet`1[SomeType]

我可能做错了什么,错误消息意味着什么?几乎似乎自动映射器在映射 ISet(及其具体实现 HashedSet)时遇到了一些问题。我的理解是,在上述场景中,自动映射器应该只使用“DTOtoDomainMySomeTypesResolver”返回的 ISet 引用。我也不明白为什么我会收到“从 List 转换为 ISet 错误”。

最佳答案

这是因为 AutoMapper 目前不支持 ISet<>集合属性。它在 ISet<> 的目标属性时起作用已经实例化(不为空),因为 ISet<>实际上继承自 ICollection<> ,因此 Automapper 可以理解这一点,并将正确地进行集合映射。

当目标属性为 null 并且是接口(interface)类型时,它不起作用。你得到这个错误,因为 automapper 实际上发现它可以从 ICollection<> 分配。所以它使用通用 List<> 实例化属性,这是 automapper 必须创建新集合属性时的默认集合,但是当它尝试实际分配它时,它将失败,因为显然 List<>不能转换为 ISet<>
对此有三种解决方案:

  • 创建功能请求以支持 ISet<>收藏并希望他们会添加它
  • 确保该属性不为空。例如。在构造函数中将其实例化为空 HashSet<> .这可能会给 ORM 层带来一些麻烦,但可行
  • 我采用的最佳解决方案是创建自定义值解析器,如果它为空,您已经拥有并自己实例化该属性。您需要执行 IValueResolver , 因为提供的基数 ValueResolver不会让您实例化该属性。这是我使用的代码片段:
  • public class EntityCollectionMerge : IValueResolver where TDest : IEntityWithId where TSource : IDtoWithId { public ResolutionResult Resolve(ResolutionResult source) { //if source collection is not enumerable return var sourceCollection = source.Value as IEnumerable; if (sourceCollection == null) return source.New(null, typeof(IEnumerable)); //if the destination collection is ISet if (typeof(ISet).IsAssignableFrom(source.Context.DestinationType)) { //get the destination ISet var destSet = source.Context.PropertyMap.GetDestinationValue(source.Context.DestinationValue) as ISet; //if destination set is null, instantiate it if (destSet == null) { destSet = new HashSet(); source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, destSet); } Merge(sourceCollection, destSet); return source.New(destSet); } if (typeof(ICollection).IsAssignableFrom(source.Context.DestinationType)) { //get the destination collection var destCollection = source.Context.PropertyMap.GetDestinationValue(source.Context.DestinationValue) as ICollection; //if destination collection is null, instantiate it if (destCollection == null) { destCollection = new List(); source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, destCollection); } Merge(sourceCollection, destCollection); return source.New(destCollection); } throw new ArgumentException("Only ISet and ICollection are supported at the moment."); } public static void Merge(IEnumerable source, ICollection destination) { if (source == null) return; var destinationIds = destination.Select(x => x.Id).ToHashSet(); var sourceDtos = source.ToDictionary(x => x.Id); //add new or update foreach (var sourceDto in sourceDtos) { //if the source doesnt exist in destionation add it if (sourceDto.Key (sourceDto.Value)); continue; } //update exisiting one Mapper.Map(sourceDto.Value, destination.First(x => x.Id == sourceDto.Key)); } //delete entity in destination which were removed from source dto foreach (var entityToDelete in destination.Where(entity => !sourceDtos.ContainsKey(entity.Id)).ToList()) { destination.Remove(entityToDelete); } } }
    然后在你的映射上使用 opt => opt.ResolveUsing(new EntitCollectionMerge<Entity,Dto>()).FromMember(x => x.ISetMember)或者,如果您有很多这样的集合,您可以通过 typeMaps 将它们自动添加到所有集合中。

    关于自动映射器将 IList<> 映射到 Iesi.Collections.Generic.ISet<>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8341297/

    相关文章:

    c# - Automapper:从自动映射对象解析源属性名称

    automapper 类和嵌套类映射到一个类

    c# - .Net Core 单元测试错误 - 源 IQueryable 未实现 IAsyncEnumerable<...>

    c# - 使用自动映射器映射两个数组返回空数组

    c# - Automapper - 将实体映射到枚举

    .net - 发射映射器与 valueinjecter 或 automapper 性能

    asp.net-mvc - 我应该如何为 Unity 和 AutoMapper 实现 MVC Bootstrapper?

    c# - AutoMapper 可以为接口(interface)创建映射,然后使用派生类型进行映射吗?

    c# - 在 Automapper 中映射集合

    c# - 如何从映射中找到自动映射器配置文件?