MappingGenerator in the premium version provides a dedicated UI panel for controlling different aspects of code generation. For non-premium users, this panel is available in preview mode
which means you can check how different options affect the generated code but you can’t use the output.
This option allows controlling how mapping code generation should handle collections of simple types (primitives, enums, Guid
or string
). When this option is selected, the source collection is cloned by adding ToList()
or ToArray()
method invocation. When the option is unchecked then source collection is directly assigned to the target.
MappingGenerator always respects access modifiers while searching for mapping sources and targets. When a type member is not accessible in a given context then it’s excluded from the mapping. However, C#
allows for setting fields and properties of readonly members using a special initialization block syntax.
For complex types:
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: MainAddress is a property with private setter
MainAddress =
{
City = entity.MainAddress.City,
ZipCode = entity.MainAddress.ZipCode,
Street = entity.MainAddress.Street,
FlatNo = entity.MainAddress.FlatNo,
BuildingNo = entity.MainAddress.Building_No
}
};
}
For collections (when defines Add(IEnumerable<T> items)
method):
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Addresses is a collection property with private setter. This collection type expose Add(IEnumerable<T> items) method
Addresses = {
entity.Addresses.ConvertAll(entityAddress => new AddressDTO
{
City = entityAddress.City,
ZipCode = entityAddress.ZipCode,
Street = entityAddress.Street,
FlatNo = entityAddress.FlatNo,
BuildingNo = entityAddress.Building_No
})
}
};
}
}
This option works only inside the initialization block.
There’s no way to determine if the readonly member’s value was already assigned to an existing object instance. If the reference points to null
value then initialization block execution results in NullReferenceException
in the runtime.
This option expresses the preference for constructor invocation syntax. Depends on the selection, when there’s a need to call the existing constructor to satisfy type compatibility between source and target, the named arguments are used or not.
Option checked:
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Constructor called with named arguments
Name = new FullNameDTO(firstName: entity.Name.FirstName, lastName: entity.Name.LastName)
};
}
Option unchecked:
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Constructor called without specifying parameters' names
Name = new FullNameDTO(entity.Name.FirstName, entity.Name.LastName)
};
}
This option allows to change the constructor invocation’s syntax to the new()
notation introduced in C# 9:
public static UserDTO Map(UserEntity entity)
{
return new()
{
Id = entity.Id,
Version = entity.Version,
Name = new(entity.Name.FirstName, entity.Name.LastName),
Age = entity.Age,
Account = new()
{
BankName = entity.Account.BankName,
Number = entity.Account.Number
}
};
}
This option switches constructor invocation’s formatting to multi-line mode, where every argument is put in a separated line.
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
Name = new FullNameDTO
(
firstName: entity.Name.FirstName,
lastName: entity.Name.LastName
)
};
}
This option controls the order of mapping expression based on the targets. There are available the following sorting criteria:
By default, MappingGenerator ignores sources when it is not able to locate the corresponding mapping target (the target is not accessible or doesn’t exist). This option allows for overriding this behavior and we have the following strategies for handling unmatched sources:
//TODO: ??? = {SourceName}
comment???
target resulting in the non-compilable output code.By default, MappingGenerator ignores targets when it is not able to locate the corresponding mapping source (the source is not accessible or doesn’t exist). This option allows for overriding this behavior and we have the following strategies for handling unmatched targets:
default
keyword is used to initialize the target’s members//TODO: {TargetName} = ???
comment???
value resulting in the non-compilable output code.This option defines how mapping from nullable value source to non-nullable target should look like. We have the following options for this setting:
target.Property = source.Property ?? throw new ArgumentNullException(nameof(source), "The value of 'source.Property' should not be null")
target.Property = source.Property.GetValueOrDefault()
target.Property = source.Property.Value
This option affects the source-to-target matching algorithm by extending source searching on nested properties when there is no direct match. It allows achieving mappings that project complex objects to a flat structure.
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Properties from the 'Name' sub-object matched directly to the main object properties
FirstName = entity.Name.FirstName,
LastName = entity.Name.LastName,
// INFO: Properties from the 'MainAddress' sub-object matched directly to the main object properties
City = entity.MainAddress.City,
ZipCode = entity.MainAddress.ZipCode,
Street = entity.MainAddress.Street,
BuildingNo = entity.MainAddress.Building_No,
FlatNo = entity.MainAddress.FlatNo
};
}
This option extends source-to-target matching algorithm with another fallback that tries to match both sides by treating them as prefix/suffix of each other. It allows to achieve more complete mapping with one of the sides contains extra prefix/suffix and matching is only partial:
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
// INFO: 'Id' matched with 'UserId'
Id = entity.UserId,
Version = entity.Version,
// INFO: 'FullName' matched with 'Name'
FullName = entity.Name,
Age = entity.Age
};
}
These settings determine how the sub-mappings of a complex object should be handled. There are the following options to chose from:
Option | Explanation |
---|---|
Inline | All sub-mappings are generated inline as a part of one expression. Code duplication possible. |
Always extract methods | Mapping of every complex sub-property is extracted to separated methods. |
Avoid duplication and extract methods | Mappings of complex objects are extracted to separated methods only when occurring more than once. |
This option specifies the location of the generated sub-mapping methods. They can be emitted as local functions
inside the currently implemented method or as containing type members
.
Local function:
public static UserDTO Map(UserEntity entity)
{
// INFO: Mapping for Address object extracted to local function
AddressDTO MapFromAddressEntityToAddressDTO(AddressEntity source)
{
return new AddressDTO
{
City = source.City,
ZipCode = source.ZipCode,
Street = source.Street,
FlatNo = source.FlatNo,
BuildingNo = source.BuildingNo
};
}
return new UserDTO
{
Id = entity.Id,
FirstName = entity.FirstName,
LastName = entity.LastName,
Address = MapFromAddressEntityToAddressDTO(entity.Address),
};
}
Containing type members:
public class TestMapper
{
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
FirstName = entity.FirstName,
LastName = entity.LastName,
Address = MapFromAddressEntityToAddressDTO(entity.Address),
};
}
// INFO: Mapping for Address object extracted to member function
private static AddressDTO MapFromAddressEntityToAddressDTO(AddressEntity source)
{
return new AddressDTO
{
City = source.City,
ZipCode = source.ZipCode,
Street = source.Street,
FlatNo = source.FlatNo,
BuildingNo = source.BuildingNo
};
}
}
This option defines the naming of the extracted method sub-mapping methods:
Strategy | Example for mapping AddressEntity to AddressDTO |
---|---|
Map{SourceTypeName}To{TargetTypeName} | MapAddressEntityToAddressDTO |
Map{CommonTypeNamePart} | MapAddress |
Map | Map |
This setting allows for controlling how to perform the conversions between two different enum types. We can choose one of the following options:
Cast - The source is explicitly cast to the target enum type.
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Enum value casted to the target enum type
Authentication = (AuthenticationKindAPI)entity.Authentication
};
}
Map based on the option name - The switch expression is generated that maps source enum options to the matched targe options. Options from a source are matched to target based on the option names. Names are compared in a case insensitive manner ignoring underscore characters. Unmatched options are controller by Unmapped targets
settings. For Visual Studio 2017, instead of switch expression, the switch statement is generated and extracted to a separated function.
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Enum values mapped with switch expression by matching enum option names
Authentication = entity.Authentication switch
{
AuthenticationKind.Password => AuthenticationKindAPI.Password,
AuthenticationKind.Active_Directory => AuthenticationKindAPI.ActiveDirectory,
AuthenticationKind.Social_Media => AuthenticationKindAPI.SocialMedia,
_ => throw new InvalidEnumArgumentException(nameof(entity.Authentication), (int)entity.Authentication, typeof(AuthenticationKind))
}
};
}
Map based on the option value - The switch expression is generated that maps source enum options to the matched targe options. Options from a source are matched to target based on the option values. Unmatched options are controller by Unmapped targets
settings. For Visual Studio 2017, instead of switch expression, the switch statement is generated and extracted to a separated function.
public enum AuthenticationKind
{
Password,
Windows,
SSO
}
public enum AuthenticationKindAPI
{
Password,
ActiveDirectory,
SocialMedia
}
public static UserDTO Map(UserEntity entity)
{
return new UserDTO
{
Id = entity.Id,
Version = entity.Version,
// INFO: Enum values mapped with switch expression by matching enum option values
Authentication = entity.Authentication switch
{
AuthenticationKind.Password => AuthenticationKindAPI.Password,
AuthenticationKind.Windows => AuthenticationKindAPI.ActiveDirectory,
AuthenticationKind.SSO => AuthenticationKindAPI.SocialMedia,
_ => throw new InvalidEnumArgumentException(nameof(entity.Authentication), (int)entity.Authentication, typeof(AuthenticationKind))
}
};
}
MappingGenerator can re-use existing methods for conversions between different types. A method is classified as a conversion method when accepts a single parameter and returns a non-void type. The following options allow for enabling different sources of the existing conversion methods:
Option | Explanation |
---|---|
Containing type methods | Mapping generator scans currently implemented method’s containing type for conversion methods |
Containing type dependencies | Mapping generator scans currently implemented method’s containing type’s properties and fields for conversion methods |
Source instance methods | Conversion methods are searched among source type’s instance methods |
Source static methods | Conversion methods are searched among source type’s static methods |
Target static methods | Conversion methods are searched among target’s type static methods |
Extension methods | Conversion methods are searched among extension methods available in the current scope |