[C ×] AutoMapper user manual

catalog

This paper is based on AutoMapper 9.0.0

AutoMapper is an object object mapper that maps one object to another.

Official website address: http://automapper.org/

Official documents: https://docs.automapper.org/en/latest/

1 introduction example

public class Foo
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class FooDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public void Map()
{
    var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());

    var mapper = config.CreateMapper();

    Foo foo = new Foo { ID = 1, Name = "Tom" };

    FooDto dto = mapper.Map<FooDto>(foo);
}

2 Registration

Before using the Map method, first tell AutoMapper what classes to Map to.

var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());

Each AppDomain can only be configured once. This means that the best place to place configuration code is in the application startup, for example ASP.NET Application's Global.asax Documents.

From 9.0 Mapper.Initialize Method is not available.

2.1 Profile

Profile is another way to map organizations. Create a new class, inherit the profile, and configure the mapping in the constructor.

public class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeDto>();
    }
}

var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<EmployeeProfile>();
});

The configuration within the Profile is only applicable to the mapping within the Profile. The configuration applied to the root configuration applies to all mappings created.

AutoMapper can also scan the specified assembly for classes inherited from Profile and add them to the configuration.

var config = new MapperConfiguration(cfg =>
{
    // Scan current assembly
    cfg.AddMaps(System.AppDomain.CurrentDomain.GetAssemblies());
    
    // You can also pass the assembly name (dll name)
    cfg.AddMaps("LibCoreTest");
});

3 configuration

3.1 naming convention

By default, AutoMapper maps based on the same field name and is case insensitive.

But sometimes, we need to deal with special situations.

  • SourceMemberNamingConvention indicates source type naming rules
  • DestinationMemberNamingConvention indicates the naming rule of the target type

Lowerscorenaming Convention and Pascal case naming convention are two naming rules provided by AutoMapper. The former is named lowercase and contains underscores, the latter is Pascal naming rules (the initial of each word is uppercase).

My understanding is that if the source type and the target type adopt snake naming and hump naming respectively, then you need to specify naming rules so that they can map correctly.

public class Foo
{
    public int Id { get; set; }

    public string MyName { get; set; }
}

public class FooDto
{
    public int ID { get; set; }

    public string My_Name { get; set; }
}

public void Map()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Foo, FooDto>();

        cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
        cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
    });

    var mapper = config.CreateMapper();

    Foo foo = new Foo { Id = 2, MyName = "Tom" };

    FooDto dto = mapper.Map<FooDto>(foo);
}

3.2 configuration visibility

By default, AutoMapper maps only public members, but it can map to private properties.

var config = new MapperConfiguration(cfg =>
{
    cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.SetMethod.IsPrivate;
    cfg.CreateMap<Source, Destination>();
});

It should be noted that private set must be added to the attribute here, and it is not allowed to omit set.

3.3 global attribute / field filtering

By default, AutoMapper attempts to map each public property / field. The following configuration ignores field mapping.

var config = new MapperConfiguration(cfg =>
{
	cfg.ShouldMapField = fi => false;
});

3.4 identifying prefixes and suffixes

var config = new MapperConfiguration(cfg =>
{
    cfg.RecognizePrefixes("My");
    cfg.RecognizePostfixes("My");
}

3.5 replacement characters

var config = new MapperConfiguration(cfg =>
{
    cfg.ReplaceMemberName("Ä", "A");
});

We can hardly use this function.

4 call constructor

In some classes, the set method of a property is private.

public class Commodity
{
    public string Name { get; set; }

    public int Price { get; set; }
}

public class CommodityDto
{
    public string Name { get; }

    public int Price { get; }

    public CommodityDto(string name, int price)
    {
        Name = name;
        Price = price * 2;
    }
}

AutoMapper will automatically find the corresponding constructor call. If you make some changes to the parameters in the constructor, the changes will be reflected in the mapping results. For example, after mapping, Price multiplies 2.

Disable constructor mapping:

var config = new MapperConfiguration(cfg => cfg.DisableConstructorMapping());

If constructor mapping is disabled, the target class must have a parameterless constructor.

5 array and list mapping

The mapping between arrays and lists is relatively simple. Only the element types need to be configured. The simple types are defined as follows:

public class Source
{
    public int Value { get; set; }
}

public class Destination
{
    public int Value { get; set; }
}

Mapping:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Source, Destination>();
});
IMapper mapper = config.CreateMapper();

var sources = new[]
{
    new Source { Value = 5 },
    new Source { Value = 6 },
    new Source { Value = 7 }
};

IEnumerable<Destination> ienumerableDest = mapper.Map<Source[], IEnumerable<Destination>>(sources);
ICollection<Destination> icollectionDest = mapper.Map<Source[], ICollection<Destination>>(sources);
IList<Destination> ilistDest = mapper.Map<Source[], IList<Destination>>(sources);
List<Destination> listDest = mapper.Map<Source[], List<Destination>>(sources);
Destination[] arrayDest = mapper.Map<Source[], Destination[]>(sources);

Specifically, the supported source collection types include:

  • IEnumerable
  • IEnumerable
  • ICollection
  • ICollection
  • IList
  • IList
  • List
  • Arrays

When you map to an existing collection, the target collection is cleared first. If this is not what you want, please check AutoMapper.Collection .

5.1 handling empty sets

When mapping Collection properties, if the source value is null, AutoMapper maps the target field to an empty Collection instead of null. This is consistent with the behavior of Entity Framework and Framework Design Guidelines, which believe that C reference, array, List, Collection, Dictionary and IEnumerables should never be null.

5.2 polymorphism in sets

This official document is not well understood. Let me give you another example. The entity classes are as follows:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class Employee2 : Employee
{
    public string DeptName { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class EmployeeDto2 : EmployeeDto
{
    public string DeptName { get; set; }
}

The array mapping code is as follows:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>().Include<Employee2, EmployeeDto2>();
    cfg.CreateMap<Employee2, EmployeeDto2>();
});
IMapper mapper = config.CreateMapper();

var employees = new[]
{
    new Employee { ID = 1, Name = "Tom" },
    new Employee2 { ID = 2, Name = "Jerry", DeptName = "R & D" }
};

var dto = mapper.Map<Employee[], EmployeeDto[]>(employees);

You can see that after mapping, the types of two elements in dto are employedto and employedto2, that is, the parent class is mapped to the parent class and the child class is mapped to the child class.

If the Include method is removed, the type of both elements in the dto after mapping is employedto.

6 method to attribute mapping

AutoMapper can not only map attribute to attribute, but also map method to attribute without any configuration. The method name can be the same as the attribute name, or it can be prefixed with Get.

For example Employee.GetFullName() method, which can be mapped to EmployeeDto.FullName Property.

public class Employee
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string GetFullName()
    {
        return $"{FirstName} {LastName}";
    }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName { get; set; }
}

7 custom mapping

When the source type is inconsistent with the target type name, or some conversion is needed for the source data, you can use the custom mapping.

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DateTime JoinTime { get; set; }
}

public class EmployeeDto
{
    public int EmployeeID { get; set; }

    public string EmployeeName { get; set; }

    public int JoinYear { get; set; }
}

For example, ID and EmployeeID attribute names are different. JoinTime and JoinYear not only have different attribute names, but also have different attribute types.

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>()
        .ForMember("EmployeeID", opt => opt.MapFrom(src => src.ID))
        .ForMember(dest => dest.EmployeeName, opt => opt.MapFrom(src => src.Name))
        .ForMember(dest => dest.JoinYear, opt => opt.MapFrom(src => src.JoinTime.Year));
});

8 flattening mapping

One of the common uses of object object mapping is to flatten complex object models into simpler models.

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

If the attribute of the target type does not correspond to the attribute and method of the source type, AutoMapper will split the target member name into a single word according to the hump method, and then match it. For example, in the example above, EmployeeDto.DepartmentID That's it Employee.Department.ID .

8.1 IncludeMembers

If the attribute naming does not conform to the above rules, it is as follows:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

If the property name in the Department class is directly the same as that in the employedto class, you can use the IncludeMembers method to specify it.

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>().IncludeMembers(e => e.Department);
    cfg.CreateMap<Department, EmployeeDto>();
});

9 nested mapping

Sometimes, we may not need to flatten. See the following example:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string Heads { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DepartmentDto Department { get; set; }
}

public class DepartmentDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

We want to map the Employee to the employedto and the Department to the DepartmentDto.

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>();
    cfg.CreateMap<Department, DepartmentDto>();
});

Tags: C# Attribute

Posted on Fri, 12 Jun 2020 00:51:55 -0400 by johnsworld