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

时间:2011-12-01 12:20:22

标签: 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]

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

1 个答案:

答案 0 :(得分:1)

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

当destination属性为null并且是接口类型时,它不起作用。你得到这个错误,因为automapper实际上发现它可以从ICollection<>分配,所以它使用泛型List<>实例化该属性,当automapper必须创建新的集合属性时,这是默认集合,但是当它尝试时要实际分配它,它会失败,因为显然List<>无法转换为ISet<>

这有三个解决方案:

  1. 创建功能请求以支持ISet<>个集合,并希望他们将其添加
  2. 确保该属性不为null。例如。在构造函数中实例化它以清空HashSet<>。这可能会给ORM层带来一些麻烦,但是可行
  3. 我使用的最佳解决方案是创建自定义值解析器,如果它为null,则自己实例化该属性。您需要实现IValueResolver,因为提供的基础ValueResolver不允许您实例化该属性。以下是我使用的代码段:
  4. 
    
    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自动将它们添加到所有这些集合中。