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());
但是,正如人们所猜测的那样,这创建了新实例,但没有复制值
补充说明
请注意我想要达到的目标是
a) 每个 属性 值都根据命名约定被复制到目的地
b) 每个目的地 属性(字符串除外)都是新实例
每个都是使用 IoC 创建的,并且不是源 属性
的相同实例
- c) b 点包括嵌套属性、集合(集合中的每个元素都需要使用 ioc 和 sub 属性 values 复制过来的值创建)
为您的 Person
class 创建到自身的映射将确保实例在映射时被深度复制:
cfg.CreateMap<Person, Person>();
如果你的任何子属性也是需要深拷贝的引用类型,你可以按照相同的策略为它们定义自映射。
我目前正在处理一个需要 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));
}
}
并使用
将其添加到 MappingRegisteryvar cfg = new MapperConfigurationExpression();
cfg.Mappers.Insert(0,new NewInstanceMapper());
但是,正如人们所猜测的那样,这创建了新实例,但没有复制值
补充说明 请注意我想要达到的目标是
a) 每个 属性 值都根据命名约定被复制到目的地
b) 每个目的地 属性(字符串除外)都是新实例 每个都是使用 IoC 创建的,并且不是源 属性
的相同实例
- c) b 点包括嵌套属性、集合(集合中的每个元素都需要使用 ioc 和 sub 属性 values 复制过来的值创建)
为您的 Person
class 创建到自身的映射将确保实例在映射时被深度复制:
cfg.CreateMap<Person, Person>();
如果你的任何子属性也是需要深拷贝的引用类型,你可以按照相同的策略为它们定义自映射。