65.9K
CodeProject 正在变化。 阅读更多。
Home

5 个 AutoMapper 的技巧和窍门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (23投票s)

2014 年 9 月 3 日

CPOL

4分钟阅读

viewsIcon

163900

5 个有用的技巧, 帮助您充分利用 AutoMapper

AutoMapper 是一个旨在帮助您编写更少重复代码映射代码的生产力工具。AutoMapper 使用约定和配置将对象映射到对象。AutoMapper 足够灵活,可以被覆盖,以便它甚至可以与最旧的遗留系统一起工作。这篇文章演示了我发现的 5 个最有用的、鲜为人知的特性。

提示: 我编写了单元测试来演示每个基本概念。如果您想了解有关单元测试的更多信息,请查看我的帖子C# 使用 NUnit 和 Moq 编写单元测试

演示项目代码

这是我将在整个教程中使用的代码的基本结构;

public class Doctor
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
 
public class HealthcareProfessional
{
    public string FullName { get; set; }
}
 
public class Person
{
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
 
public class KitchenCutlery
{
    public int Knifes { get; set; }
    public int Forks { get; set; }
}
 
public class Kitchen
{
    public int KnifesAndForks { get; set; }
}
 
public class MyContext : DbContext
{
    public DbSet<Doctor> Doctors { get; set; }
}
 
public class DbInitializer : DropCreateDatabaseAlways<MyContext>
{
    protected override void Seed(MyContext context)
    {
        context.Doctors.Add(new Doctor
        {
            FirstName = "Jon",
            LastName = "Preece",
            Title = "Mr"
        });
    }
}

我将在每个示例中参考此代码。

AutoMapper 投影

毫无疑问,AutoMapper 最好也是可能使用最少的特性之一是投影。AutoMapper 与对象关系映射器 (ORM)(例如 Entity Framework)一起使用时,可以在数据库级别将源对象转换为目标类型。这可能会导致更有效的数据库查询。

毫无疑问,AutoMapper 最好也是可能使用最少的特性之一是投影。AutoMapper 与对象关系映射器 (ORM)(例如 Entity Framework)一起使用时,可以在数据库级别将源对象转换为目标类型。这可能会导致更有效的数据库查询。

AutoMapper 提供了 `Project` 扩展方法,该方法扩展了 `IQueryable` 接口以执行此任务。这意味着在映射发生之前不必完全检索源对象。

以以下单元测试为例;

[Test]
public void Doctor_ProjectToPerson_PersonFirstNameIsNotNull()
{
    //Arrange
    Mapper.CreateMap<doctor, person="">()
            .ForMember(dest => dest.LastName, opt => opt.Ignore());
 
    //Act
    Person result;
    using (MyContext context = new MyContext())
    {
        context.Database.Log += s => Debug.WriteLine(s);
        result = context.Doctors.Project().To<person>().FirstOrDefault();
    }
 
    //Assert
    Assert.IsNotNull(result.FirstName);
}

针对数据库创建并执行的查询如下;

SELECT TOP (1) 
    [c].[Id] AS [Id], 
    [c].[FirstName] AS [FirstName]
    FROM [dbo].[Doctors] AS [c]

请注意,`LastName` 没有从数据库返回?这是一个非常简单的示例,但是当处理更复杂的对象时,潜在的性能提升是显而易见的。

配置验证

AutoMapper 最有用、最节省时间的特性是配置验证。基本上,在设置映射后,您可以 `callMapper.AssertConfigurationIsValid()` 来确保您定义的映射是有意义的。这省去了运行项目、导航到相应的页面、单击按钮 A/B/C 等等来测试您的映射代码是否实际有效的麻烦。

以以下单元测试为例;

[Test]
public void Doctor_MapsToHealthcareProfessional_ConfigurationIsValid()
{
    //Arrange
    Mapper.CreateMap<doctor, healthcareprofessional="">();
 
    //Act
 
    //Assert
    Mapper.AssertConfigurationIsValid();
}

AutoMapper 抛出以下异常;

引用

AutoMapper.AutoMapperConfigurationException : 发现未映射的成员。查看以下类型和成员。添加自定义映射表达式、忽略、添加自定义解析器或修改源/目标类型 =================================================================== Doctor -> HealthcareProfessional (目标成员列表) MakingLifeEasier.Doctor -> MakingLifeEasier.HealthcareProfessional(目标成员列表) ------------------------------------------------------------------- FullName

AutoMapper 无法推断 `Doctor` 和 `HealthcareProfessional` 之间的映射,因为它们的结构差异很大。需要使用自定义转换器或 `ForMember` 来指示关系;

[Test]
public void Doctor_MapsToHealthcareProfessional_ConfigurationIsValid()
{
    //Arrange
    Mapper.CreateMap<doctor, healthcareprofessional="">()
          .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => string.Join(" ", src.Title, src.FirstName, src.LastName)));
 
    //Act
 
    //Assert
    Mapper.AssertConfigurationIsValid();
}

现在测试通过,因为每个公共属性现在都有有效的映射。

自定义转换

有时,当源对象和目标对象差异太大而无法使用约定进行映射,并且太大而无法为每个成员编写优雅的内联映射代码 (`ForMember`) 时,自己进行映射可能是有意义的。AutoMapper 通过提供 `ITypeConverter` 接口使这变得容易。

以下是将 `Doctor` 映射到 `HealthcareProfessional` 的实现;

public class HealthcareProfessionalTypeConverter : ITypeConverter<doctor, healthcareprofessional="">
{
    public HealthcareProfessional Convert(ResolutionContext context)
    {
        if (context == null || context.IsSourceValueNull)
            return null;
 
        Doctor source = (Doctor)context.SourceValue;
 
        return new HealthcareProfessional
        {
            FullName = string.Join(" ", new[] { source.Title, source.FirstName, source.LastName })
        };
    }
}

您可以使用 `ConvertUsing` 方法指示 AutoMapper 使用您的转换器,如下所示;

[Test]
public void Legacy_SourceMappedToDestination_DestinationNotNull()
{
    //Arrange
    Mapper.CreateMap<doctor, healthcareprofessional="">()
            .ConvertUsing<healthcareprofessionaltypeconverter>();
 
    Doctor source = new Doctor
    {
        Title = "Mr",
        FirstName = "Jon",
        LastName = "Preece",
    };
 
    Mapper.AssertConfigurationIsValid();
 
    //Act
    HealthcareProfessional result = Mapper.Map<healthcareprofessional>(source);
 
    //Assert
    Assert.IsNotNull(result);
}

AutoMapper 只会将源对象 (Doctor) 交给您,您将返回一个填充了属性的目标对象 (HealthcareProfessional) 的新实例。我喜欢这种方法,因为它意味着我可以将所有我的“猴子”映射代码放在一个地方。

值解析器

值解析器允许正确映射值类型。源对象 `KitchenCutlery` 包含厨房中刀叉数量的精确细分,而目标对象 `Kitchen` 只关心两者的总和。AutoMapper 将无法在此为我们创建基于约定的映射,因此我们使用值(类型)解析器;

public class KitchenResolver : ValueResolver<kitchencutlery, int="">
{
    protected override int ResolveCore(KitchenCutlery source)
    {
        return source.Knifes + source.Forks;
    }
}

值解析器类似于类型转换器,负责映射并返回结果,但请注意,它特定于单个属性,而不是整个对象。

以下代码片段显示了如何使用值解析器;

[Test]
public void Kitchen_KnifesKitchen_ConfigurationIsValid()
{
    //Arrange
 
    Mapper.CreateMap<kitchencutlery, kitchen="">()
            .ForMember(dest => dest.KnifesAndForks, opt => opt.ResolveUsing<kitchenresolver>());
 
    //Act
 
    //Assert
    Mapper.AssertConfigurationIsValid();
}

空值替换

考虑默认值。如果您想在源值为 `null` 时为目标对象赋予默认值,可以使用 AutoMapper 的 `NullSubstitute` 功能。

`NullSubstitute` 方法的示例用法,分别应用于每个属性;

[Test]
public void Doctor_TitleIsNull_DefaultTitleIsUsed()
{
    //Arrange
    Doctor source = new Doctor
    {
        FirstName = "Jon",
        LastName = "Preece"
    };
 
    Mapper.CreateMap<doctor, person="">()
            .ForMember(dest => dest.Title, opt => opt.NullSubstitute("Dr"));
 
    //Act
    Person result = Mapper.Map<person>(source);
 
    //Assert
    Assert.AreSame(result.Title, "Dr");
}

摘要

AutoMapper 是一个旨在帮助您编写更少重复代码映射代码的生产力工具。您不必重写现有代码或以特定样式编写代码即可使用 AutoMapper,因为 AutoMapper 足够灵活,可以配置为与最旧的遗留代码一起工作。大多数开发人员并没有充分利用 AutoMapper,很少偏离 `Mapper.Map` 。有很多有用的技巧,包括:投影、配置验证、自定义转换、值解析器和空值替换,如果使用正确,可以帮助简化复杂的逻辑。

© . All rights reserved.