在开发中,为了减少系统负载,一般不会吧整个表的字段(即Model对象所有属性)作为传输的数据,而是单独根据具体业务需求,写一些新的类(一般以DTO结尾命名)只包含整个表字段的某些字段,用来进行数据的传输,这些类的对象称为DTO对象(数据传输对象),DTO对象与数据库实体对象Mdoel之间的赋值过程可以通过AutoMapper来实现,从而减少代码量。AutoMapper是.NET的一个对象映射工具,用于数据对象传输DTO,实现DTO对象和实体对象之间的赋值转换。引入AutoMapper.dll.
1.定义映射规则:
AutoMapper是基于约定的,在映射之前需要进行映射规则的配置:
public class Source
{
public int SomeValue { get; set; }
public string AnotherValue { get; set; }
}
<pre name="code" class="csharp"> public class SourceDTO
{
public int SomeValue { get; set; }
}
映射规则:
Mapper.CreateMap<Source, SourceDTO>();
简易映射测试:
将Source类的对象映射到SourceDTO类的对象上
Source src = new Source() { SomeValue = 1, AnotherValue = "2" };
SourceDTO dest = Mapper.Map<SourceDTO>(src);
Console.WriteLine(dest.SomeValue);//1
2.通过AutoMapper.Profile中的CreateMap来配置映射规则:
Profile提供了一个命名的映射类,所有继承自Profile类的子类都是一个映射集合。为每个类定义的Profile类都要继承自Profile.在其Profile类中重写Porfile类的Configure方法,并在该方法中定义该类与DTO类之间转换的映射规则:如SourceProfile.cs:
protected override void Configure()
{
//1.0 添加第一个映射规则,Source和Destination的映射
CreateMap<Source, SourceDTO>();
//2.0 添加第二个映射规则
//同名的会自动映射
//这里添加Source类的AnotherValue属性和Source2DTO的AnotherValue2属性进行映射
CreateMap<Source, Source2DTO>().ForMember(d => d.AnotherValue2,
opt =>
{
opt.MapFrom(s => s.AnotherValue);
});
}
然后在global.asax的Application_Start方法中初始化Mapper规则:
Mapper.Initialize(a => a.AddProfile<SourceProfile>());
3. 定义Configuration.cs类:
由于在一个项目中可能会有很多DTO类,对应的Profile类也很多,为了方便管理这些Profile类,可以定义一个Configuration.cs初始化所有的Profile,在Global.asax的Application_Start()中调用一次就可以完成所有实体与DTO之间的映射规则的配置:
public class Configuration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<Profiles.SourceProfile>();
cfg.AddProfile<Profiles.OrderProfile>();
cfg.AddProfile<Profiles.CalendarEventProfile>();
});
}
}
在global.asax的Application_Start()中调用:
AutoMapper.Configuration.Configure();
在项目目录结构中Profile目录结构可以这样:
4.扁平化映射(Flattening):
默认情况下,我们的Source类和SourceDTO类是根据属性名称进行匹配映射的。除此之外,默认的映射规则还有下面两种情况,我们称之为扁平化映射,即当Source类中不包含SourceDTO类中的属性的时候,AutoMapper会将SourceDTO类中的属性进行分割,或匹配Source中“Get”开头的方法,例如:
public class Order
{
public Customer Customer { get; set; }
public decimal GetTotal()
{
return 100M;
}
}
public class OrderDto
{
public string CustomerName { get; set; }
public string Total { get; set; }
}
public class Customer
{
public string Name { get; set; }
}
我们在进行映射的时候,不需要进行特殊的配置,既可以完成从Order到OrderDto的映射:
public class OrderProfile : Profile
{
protected override void Configure()
{
CreateMap<Entity.Order, Dto.OrderDto>();
}
}
测试:
Entity.Customer customer = new Entity.Customer() { Name = "Tom" };
Entity.Order order = new Entity.Order() { Customer = customer };
Dto.OrderDto orderDto = Mapper.Map<Dto.OrderDto>(order);
Console.WriteLine(orderDto.Total);//100
5.手动指定映射字段:
在实际的业务环境中,我们的Source类和Destination类的字段不可能一对一的匹配,这个时候我们就需要来指定他们的实际映射关系,例如:
public class CalendarEvent
{
public DateTime Date { get; set; }
public string Title { get; set; }
}
public class CalendarEventDTO
{
public DateTime EventDate { get; set; }
public int EventHour { get; set; }
public int EventMinute { get; set; }
public string DisplayTitle { get; set; }
}
在这两个类中,CalendarEvent的Date将被拆分为CalendarEventForm的日期、时、分三个字段,Title也将对应DisplayTitle字段,那么相应的Profile定义如下:
public class CalendarEventProfile : Profile
{
protected override void Configure()
{
CreateMap<Entity.CalendarEvent, Entity.CalendarEventDTO>()
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.Date.Date))
.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.Date.Hour))
.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.Date.Minute))
.ForMember(dest => dest.DisplayTitle, opt => opt.MapFrom(src => src.Title));
}
}
测试:
Entity.CalendarEvent calendarEvent = new Entity.CalendarEvent()
{
Date = DateTime.Now,
Title = "Demo Event"
};
Entity.CalendarEventForm calendarEventDTO = Mapper.Map<Entity.CalendarEventDTO>(calendarEvent);
Console.WriteLine(calendarEventDTO.EventDate)//2016/07/08
6.验证配置项(Configuration Validation):
AutoMapper提供了一种验证机制,用来判断DTO类中的所有属性是否都被映射,如果存在未被映射的属性,则抛出异常。
<pre name="code" class="csharp">public class Source
{
public int SomeValue { get; set; }
public string AnotherValue { get; set; }
}
public class SourceDTO
{
public int SomeValuefff { get; set; }
}
测试:
Mapper.CreateMap<Entity.Source, Entity.Destination>();
Mapper.AssertConfigurationIsValid();
此时抛出异常AutoMapperConfigurationException,这是因为SourceDTO中的SomeValuefff在Source类中没有对应的字段造成的,解决方法:
指定映射字段:
Mapper.CreateMap<Entity.Source, Entity.SourceDTO>()
.ForMember(dest => dest.SomeValuefff, opt =>
{
opt.MapFrom(src => src.SomeValue);
});
或使用Ignore方法:
Mapper.CreateMap<Entity.Source, Entity.SourceDTO>()
.ForMember(dest => dest.SomeValuefff, opt =>
{
opt.Ignore();
});
或使用自定义解析器,见下讲:
7.自定义解析器:(Custom value resolvers)
AutoMapper允许我们自定义解析器来完成Source到SourceDTO的值的转换:
public class Source
{
public int Value1 { get; set; }
public int Value2 { get; set; }
}
public class SourceDTO
{
public int Total { get; set; }
}
SourceDTO中的Total属性在Source中不存在,如果现在创建映射规则,在映射的时候必然会抛出异常。这个时候我们就需要使用自定义解析器来完成映射。自定义解析器需要实现 IValueResolver 接口:
public class CustomResolver : ValueResolver<Source, int>
{
protected override int ResolveCore(Source source)
{
return source.Value1 + source.Value2;
}
}
然后在映射规则中使用这个解析器:
public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, SourceDTO>()
.ForMember(dest => dest.Total, opt =>
{
opt.ResolveUsing<CustomResolver>();
});
}
}
测试:
Source src = new Source()
{
Value1 = 1,
Value2 = 2
};
SourceDTO dest = Mapper.Map<SourceDTO>(src);
Console.WriteLine(dest.Total);//3
在使用自定义Resolver中,我们还可以指定Resolver的构造函数,例如:
CreateMap<Source, SourceDTO>()
.ForMember(dest => dest.Total, opt =>
{
opt.ResolveUsing<CustomResolver>()
<span style="color:#FF0000;">.ConstructedBy(() => new CustomResolver());</span>
});
8.自定义类型转换器:(Custom type converters)
AutoMapper通过ConvertUsing来使用自定义类型转换器。ConvertUsing有三种用法:
void ConvertUsing(Func<TSource, TDestination> mappingFunction);
void ConvertUsing(ITypeConverter<TSource, TDestination> converter);
void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;
举例:
public class Source
{
public string Value1 { get; set; }
}
public class SourceDTO
{
public int Value1 { get; set; }
}
使用如下配置:
public class SourceProfile : Profile
{
protected override void Configure()
{
//string->int
CreateMap<string, int>()
.ConvertUsing(Convert.ToInt32);
//Source->SourceDTO
CreateMap<Source, SourceDTO>();
}
}
在上面的配置中,我们首先创建了从string到int的类型转换,这里使用了系统自带的Convert.ToInt32转换方法。
除了这种方法之外,我们还可以自定义类型转换器:
public class CustomConverter : ITypeConverter<Source, SourceDTO>
{
public SourceDTO Convert(ResolutionContext context)
{
Source src = context.SourceValue as Source;
SourceDTO dest = new SourceDTO();
dest.Value1 = System.Convert.ToInt32(src.Value1);
return dest;
}
}
通过这个转换器,我们可以绕过string到int的转换,直接将Source类的对象转换为Destination类的对象。
对应的配置如下:
public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, SourceDTO>()
.ConvertUsing<CustomConverter>();
}
}
或者,我们也可以使用下面的配置:
public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CustomConverter converter = new CustomConverter();
CreateMap<Source, SourceDTO>()
.ConvertUsing(converter);
}
}
9.空值替换:(Null substitution)
空值替换允许我们将Source对象中的空值在转换为SourceDTO的值的时候,使用指定的值来替换空值。
public class Source
{
public string Value { get; set; }
}
public class SourceDTO
{
public string Value { get; set; }
}
public class SourceProfile : Profile
{
protected override void Configure()
{
//Source->Destination
CreateMap<Source, SourceDTO>()
.ForMember(dest => dest.Value, opt =>
{
opt.NullSubstitute("原始值为NULL");
});
}
}
Source src = new Source();
Destination dest = Mapper.Map<Destination>(src);
Console.WriteLine(dest.Value);//原始值为Null
10.条件映射:(Conditional mapping)
条件映射只当Source类中的属性值满足一定条件的时候才进行映射。例如:
public class Foo
{
public int baz;
}
public class FooDTO
{
public uint baz;
Mapper.CreateMap<Foo, FooDTO>()
.ForMember(dest => dest.baz, opt =>
{
opt.Condition(src => (src.baz >= 0));
});