Automapper - Убедитесь, что Sub Properties отличаются от Source и создаются с помощью IOC - PullRequest
0 голосов
/ 25 марта 2020

В настоящее время я работаю над сценарием, в котором мне понадобится Automapper, чтобы использовать IO C для создания новых экземпляров. Кроме того, мне нужно было бы зарегистрировать сопоставление для Automapper только при повышении требования (поэтому я не использую файлы профилей). Чтобы добиться того же, я сделал следующее.

var cfg = new MapperConfigurationExpression();
cfg.ConstructServicesUsing(type =>
            {
                return CreateInstanceUsingIOC(type);
            });

cfg.CreateMap<Model1, Model2>()
    .ConstructUsingServiceLocator();
var config = new MapperConfiguration(cfg);
var mapper = config.CreateMapper();

var model1UsingIoC = CreateModel1UsingIoC();
model1UsingIoC.MyProfile = new Person();
model1UsingIoC.MyProfile.FirstName = "New First Name";
model1UsingIoC.MyProfile.LastName = "New Last Name";
model1UsingIoC.CommonProperty = "This wont be copied";
var model2b = mapper.Map<Model2>(model1UsingIoC);

Это работает как нужно, однако проблема заключается во внутреннем свойстве Model1.MyProfile. Экземпляр MyProfile в Source и Destination выглядит одинаково.

ReferenceEquals(model2b.MyProfile,model1UsingIoC.MyProfile) // True

Кроме того, я хотел бы создать каждое из пользовательских вложенных свойств, используя Io C. Для этой цели я добавил измененный оператор ConstructServicesUsing как

 cfg.ConstructServicesUsing(type =>
            {
                var instance = CreateInstance(type);
                return ReassignProperties(instance);
            });

Где ReassignProperties определяется как

public static object ReassignProperties(object obj)
{
    if (obj == null) return null;
    Type objType = obj.GetType();

    PropertyInfo[] properties = obj.GetType().GetProperties();
    foreach (PropertyInfo property in properties)
    {
        // Check if its a collection
        if (property.IsEnumerable())
        {
            if (property.GetValue(obj, null) is IEnumerable elems)
            {
                foreach (var item in elems)
                {
                    ReassignProperties(item);
                }
            }
        }
        else
        {
            // If it is User Defined Type, Recursively loop again
            if (property.PropertyType.Assembly == objType.Assembly)
            {
                var newValue = CreateInstance(property.PropertyType);
                property.SetValue(obj, newValue);
                var value = property.GetValue(obj);
                ReassignProperties(value);
            }
            else
            {
                var propValue = property.GetValue(obj, null);
                Console.WriteLine($"{property.Name} = {propValue}");

            }

        }
    }
    return obj;
}

Но, как можно предположить, это не помогло ни во время отображения, один и тот же экземпляр копируется для подпредприятий. Может ли кто-нибудь подсказать мне, как обеспечить создание нового экземпляра для пользовательских под-свойств при использовании Automapper (ie, новый экземпляр создается и значения копируются, если тип не является примитивным типом)?

Обновление 001

В соответствии с предложением Lucian, я попытался создать собственный маппер следующим образом и добавить его в Mapping Registery.

public class NewInstanceMapper : IObjectMapper
{
    public bool IsMatch(TypePair context) => true;


    public static object CreateInstance(Type type)
    {
        try
        {
            var methodInfo = typeof(IoC).GetMethod(nameof(IoC.Get), BindingFlags.Public | BindingFlags.Static);
            var genericMethodInfo = methodInfo.MakeGenericMethod(type);
            return genericMethodInfo.Invoke(null, new object[] { null });
        }
        catch (Exception)
        {
            return Activator.CreateInstance(type);
        }
    }

    public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
    {
        return Expression.Call(this.GetType(), nameof(CreateInstance), null, Expression.Constant(destExpression.Type));
    }
}

И добавил его в MappingRegistery, используя

var cfg = new MapperConfigurationExpression();
cfg.Mappers.Insert(0,new NewInstanceMapper());

Но, как можно догадаться, это создало новый экземпляр, но не скопировало значение

Дополнительные примечания Обратите внимание, что я хотел бы достичь, будет

  • a) Каждое значение свойства копируется в место назначения в соответствии с соглашениями об именах

  • b) Каждое свойство назначения (исключение для строки) является новым каждый экземпляр, созданный с использованием Io C, и не совпадающий с экземплярами свойства источника

  • c) Точка b содержит вложенные свойства, коллекции (каждый элемент нуждается в коллекции создается с использованием io c и скопированных значений значений вспомогательных свойств)

1 Ответ

0 голосов
/ 31 марта 2020

Создание карты для вашего класса Person для самого себя обеспечит глубокое копирование экземпляров при отображении:

cfg.CreateMap<Person, Person>();

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

...