Automapper, отображающий IList <> в Iesi.Collections.Generic.ISet <> - PullRequest
2 голосов
/ 01 декабря 2011

У меня есть некоторые проблемы в отображении, упомянутом в заголовке. Вот подробности:

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 работает нормально, и, как и ожидалось, я получаю объект MyDTO с IList объектов SomeTypeDTO. Однако сопоставление 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). Насколько я понимаю, в вышеописанном сценарии autopper должен просто использовать ссылку ISet, возвращаемую "DTOtoDomainMySomeTypesResolver". Я также не понимаю, почему я получаю сообщение «Ошибка приведения из списка в ISet».

1 Ответ

1 голос
/ 13 марта 2015

Это потому, что AutoMapper в настоящее время не поддерживает ISet<> свойства коллекции.Он работает, когда свойство назначения ISet<> уже создано (не равно нулю), потому что ISet<> на самом деле наследует от ICollection<>, поэтому Automapper может понять это и правильно выполнит сопоставление коллекции.

Это не работает, когда свойство destination имеет значение null и имеет тип интерфейса.Вы получаете эту ошибку, потому что automapper фактически обнаружил, что его можно назначить из ICollection<>, поэтому он создает экземпляр свойства, используя generic List<>, который является коллекцией по умолчанию, когда automapper должен создать новое свойство коллекции, но затем, когда он пытается фактически назначить его, он потерпит неудачу, потому что, очевидно, List<> не может быть приведен к ISet<>

. Есть три решения для этого:

  1. Создайте запрос функции для поддержки ISet<> коллекций инадеюсь, они добавят его
  2. Убедитесь, что свойство не равно нулю.Например.создать экземпляр в конструкторе, чтобы очистить HashSet<>.Это может вызвать некоторые проблемы для слоев ORM, но выполнимо
  3. Лучшее решение, которое я выбрал, - это создание пользовательского преобразователя значений, который у вас уже есть, и создание экземпляра свойства самостоятельно, если оно равно нулю.Вам необходимо реализовать 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...