Automapper - 确保子属性与源实例不同,并且是使用 IOC 创建的

Automapper - Ensure Sub Properties are different Instances from Source and are created using IOC

我目前正在处理一个需要 Automapper 使用 IOC 创建新实例的场景。此外,仅当需求增加时,我才需要为 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。 Source 和 Destination 中的 MyProfile 实例看起来是一样的。

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

此外,我想使用 IoC 创建每个用户定义的子属性。为此,我将 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 时为用户定义的子属性创建新实例(即创建新实例并复制值,除非类型是原始类型)?

更新001

按照 Lucian 的建议,我尝试如下创建自定义映射器并将其添加到映射注册表。

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());

但是,正如人们所猜测的那样,这创建了新实例,但没有复制值

补充说明 请注意我想要达到的目标是

为您的 Person class 创建到自身的映射将确保实例在映射时被深度复制:

cfg.CreateMap<Person, Person>();

如果你的任何子属性也是需要深拷贝的引用类型,你可以按照相同的策略为它们定义自映射。