Пропустить нулевые значения с помощью настраиваемого преобразователя

Я хочу использовать automapper для сопоставления между моими контрактами с публичными данными и моделями БД. Я создал класс, который автоматически проходит через все контракты, создает сопоставления. Единственная проблема, с которой я сталкиваюсь, это то, что я хочу только сопоставить значения из контракта с моделью БД, если значение не равно нулю. Я посмотрел на другой вопрос, но не вижу примеров, которые используют пользовательские резольверы.

Вот мой код

var mapToTarget = AutoMapper.Mapper.CreateMap(contract, mappedTo);
foreach (var property in contract.GetProperties().Where(property => property.CustomAttributes.Any(a => a.AttributeType == typeof(MapsToProperty))))
{
  var attribute = property.GetCustomAttributes(typeof(MapsToProperty), true).FirstOrDefault() as MapsToProperty;

  if (attribute == null) continue;

  mapToTarget.ForMember(attribute.MappedToName,
                    opt => 
                        opt.ResolveUsing<ContractToSourceResolver>()
                            .ConstructedBy(() => new ContractToSourceResolver(new MapsToProperty(property.Name, attribute.SourceToContractMethod, attribute.ContractToSourceMethod))));
}


private class ContractToSourceResolver : ValueResolver<IDataContract, object>
{
  private MapsToProperty Property { get; set; }

  public ContractToSourceResolver(MapsToProperty property)
  {
     Property = property;
  }

  protected override object ResolveCore(IDataContract contract)
  {
     object result = null;
     if (Property.ContractToSourceMethod != null)
     {
         var method = contract.GetType()
                    .GetMethod(Property.ContractToSourceMethod, BindingFlags.Public | BindingFlags.Static);
          result = method != null ? method.Invoke(null, new object[] {contract}) : null;
      }
      else
      {
         var property =
                    contract.GetType().GetProperties().FirstOrDefault(p => p.Name == Property.MappedToName);
         if (property != null)
         {
             result = property.GetValue(contract);
         }
      }

      return result;
   }
}

И вот как я хочу его использовать

AutoMapper.Mapper.Map(dataContract, dbModel)

В настоящее время это отлично работает, но если в dataContract есть значение NULL, оно заменит существующее значение в dbModel, это не то, что я хочу. Как заставить AutoMapper игнорировать значения нулевого источника?

ИЗМЕНИТЬ

Как указано в одном из ответов, это

Mapper.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));

Это было бы идеально, за исключением того, что .ForAllMembers недоступен из

Mapper.CreateMap(SourceType, DestinationType)

Ответ 1

Если вы хотите, чтобы все свойства источника с нулевыми значениями игнорировались, вы можете использовать:

Mapper.CreateMap<SourceType, DestinationType>()
  .ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));

В противном случае вы можете сделать что-то подобное для каждого члена.

Прочитайте этот.

Ответ 2

Для более новых версий, использующих API-интерфейс экземпляра, используйте это вместо:

var mappingConfig = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(
        (source, destination, sourceMember, destMember) => (sourceMember != null)));
});

Примечание.. Эта функция работает с 5.0.2, взламывая более поздние версии на момент написания этой статьи. Ожидание следующей версии 5.2.x рекомендуется при обновлении с версии 5.0.2.

Ответ 3

Решение здесь работает для моего проекта, который использует AutoMapper 6.0.2. В предыдущих проектах с использованием AutoMapper 4 я использовал IsSourceValueNull для достижения аналогичного поведения. У меня есть типы с нулевым значением, сопоставленные с типами, не подлежащими обнулению, в моем проекте, это решение может обрабатывать этот сценарий.

Я сделал небольшое изменение первоначального решения. Вместо того, чтобы проверять тип отображаемого свойства, я устанавливаю фильтр в ForAllPropertyMaps для проверки типа исходного объекта, так что пользовательский преобразователь применяется только к картам из этого исходного объекта. Но фильтр может быть установлен на все, что необходимо.

var config = new MapperConfiguration(cfg =>
{
    cfg.ForAllPropertyMaps(
        pm => pm.TypeMap.SourceType == typeof(<class of source object>),
        (pm, c) => c.ResolveUsing<object, object, object, object>(new IgnoreNullResolver(), pm.SourceMember.Name));
});

class IgnoreNullResolver : IMemberValueResolver<object, object, object, object>
{
    public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
    {
        return sourceMember ?? destinationMember;
    }
}

Ответ 4

У меня такая же точная проблема с отображением условных выражений на не-общие типы. Вот как я это решил.

Подключить:

foreach (PropertyInfo p in type.GetProperties().Where(x => x.GetCustomAttributes<SkipMapIfNullAttribute>().Any()))
    map.ForMember(p.Name, x => x.ResolveUsing(typeof(SkipMapIfNullResolver)).FromMember(p.Name));

Требуется второй .FromMember, поэтому значение для элемента передается в значение resolver, а не полная модель.

Резольвер выглядит следующим образом:

public class SkipMapIfNullResolver : IValueResolver
{
    public ResolutionResult Resolve(ResolutionResult source)
    {
        if (source.Value == null)
            source.ShouldIgnore = true;

        return source;
    }
}