PocoEmit.Mapper refactors the object mapping pipeline through a Projection mechanism, unifying prefixes, suffixes, and alias replacement into composable rules. This approach solves the rigidity of traditional mapper configuration and the difficulty of matching inconsistent naming conventions. Keywords: PocoEmit.Mapper, Projection, object mapping.
Technical Specification Snapshot
| Parameter | Description |
|---|---|
| Core project | PocoEmit.Mapper |
| Related library | Hand.Projections |
| Primary language | C# / .NET |
| Core interface | `IProjection |
| ` | |
| Mapping modes | Filter / Through / Cross |
| Typical protocol/paradigm | Convention-based object property mapping, SQL-style projection view |
| Repository | GitHub / Gitee |
| Star count | Not provided in the original article |
| Core dependencies | PocoEmit.Mapper, Hand.Projections |
| Installation example | dotnet add package PocoEmit.Mapper --version 0.8.8.1-alpha |
The projection mechanism upgrades object mapping from hardcoded matching to rule-driven matching.
Traditional object mapping tools usually depend on explicit configuration or fixed naming conventions. Once the source object and target object differ by prefixes, suffixes, or spelling variants, the amount of configuration grows rapidly. The key to the PocoEmit.Mapper refactor is the introduction of the Projection abstraction, which isolates name transformation into reusable rules.
Here, “projection” can be understood like SELECT ... AS ... in SQL, or as a new representation of an object after a naming transformation. The mapping engine no longer looks only at original member names. Instead, it uses both the original names and the projected results to improve match rates.
The IProjection interface is minimal, but expressive enough.
public interface IProjection
<T>
{
// Attempts to convert the source value into another value of the same type
bool TryConvert(T source, out T result);
}
This interface constrains exactly one thing: given an input, can it produce a projected result that satisfies a rule? Its value comes from being small enough to compose, chain, and reuse with very little effort.
Three projection modes cover the mainstream naming transformation scenarios in object mapping.
Filter keeps only members whose projection succeeds. Through keeps both successfully projected results and directly matchable members. Cross keeps both original members and projected members at the same time. For a mapper, Cross is the most valuable because it maximizes the candidate match set.
Prefix projection can expand Id into UserId.
public record User(int Id, string UserName);
public record UserDTO(int UserId, string UserName);
var sourceMembers = new Dictionary<string, Func<User, object>>()
{
[nameof(User.Id)] = obj => obj.Id,
[nameof(User.UserName)] = obj => obj.UserName
};
// Create a prefix projection that adds the User prefix to member names
var projection = Projection.Prefix("User");
This code constructs a typical scenario: the source object has Id, while the target object has UserId. The projection layer fills the semantic gap through a naming expansion rule.
Filter behaves like selecting only projected alias columns.
IDictionary<string, Func<User, object>> result = projection.Filter(sourceMembers);
Assert.Single(result);
Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));
It is equivalent to outputting only the successfully projected part, similar to SQL: SELECT Id AS UserId FROM User.
Through fits scenarios where the original name works, but aliases should also be filled in.
IDictionary<string, Func<User, object>> result = projection.Through(sourceMembers);
Assert.Equal(sourceMembers.Count, result.Count);
Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));
Assert.True(result.ContainsKey(nameof(UserDTO.UserName)));
It is roughly equivalent to SELECT Id AS UserId, UserName FROM User, which fits cases where some fields require transformation and others can remain unchanged.
Cross is the key strategy adopted in the PocoEmit.Mapper refactor.
IDictionary<string, Func<User, object>> result = projection.Cross(sourceMembers);
Assert.Equal(3, result.Count);
Assert.True(result.ContainsKey(nameof(User.Id)));
Assert.True(result.ContainsKey(nameof(UserDTO.UserId)));
Assert.True(result.ContainsKey(nameof(UserDTO.UserName)));
Cross preserves both the original fields and the projected fields. It is equivalent to SELECT Id, UserName, Id AS UserId FROM User. The core benefit is that the mapper does not need to predict which rule will hit. It only needs to match by name within a larger candidate set.
Projection composition gives mapping rules horizontal scalability.
The “horizontal scalability” emphasized in the original article is essentially the ability to try multiple projections in parallel. For example, both UserId and UId may exist. In that case, a single rule is not enough, and multiple prefix-removal strategies must be tried in sequence.
// Chain two prefix-removal projections and return the first successful result
var user = Projection.RemovePrefix("User");
var u = Projection.RemovePrefix("U");
var projection = Projection.FirstReturn(user, u);
This composition upgrades the rule system from a single transformer into a strategy chain. It is especially useful in legacy systems where DTO naming is inconsistent or multiple API versions coexist.
AddPrefix actually applies a prefix-removal projection on the source side.
In the mapping from AutoUserDTO to User, the source properties are UserId and UserName, while the target properties are Id and UserName. The most direct approach is not to modify the target, but to first apply RemovePrefix("User") to the source members.
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<AutoUserDTO, User>()
.Source
.AddPrefix("User"); // Internally converted to RemovePrefix("User")
var source = new AutoUserDTO { UserId = "222", UserName = "Jxj2" };
var converter = mapper.GetConverter<AutoUserDTO, User>();
var result = converter.Convert(source);
The essence of this configuration is to project UserId into Id, so the names align directly. The result is less code and more stable mapping semantics.
The AddPrefix replacement overload works well for migrations across naming systems.
When the source side uses the User prefix and the target side uses the U prefix, you can complete the mapping through prefix replacement instead of simple removal. This is ideal during refactoring periods when old DTOs and new DTOs coexist.
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<AutoUserDTO, UserCustomDTO>()
.Source
.AddPrefix("User", "U"); // Convert UserX into UX
This configuration projects UserId into UId and UserName into UName, enabling cross-system matching between different naming conventions.
AddSuffix handles singular/plural and trailing-suffix naming differences.
Suffix replacement is another high-frequency problem, such as City and Cities. Compared with hardcoding property mappings, suffix rules are easier to reuse and align better with domain naming conventions.
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<Customer, CustomerDTO>()
.Source
.AddSuffix("y", "ies"); // Project City into Cities
var source = new Customer("Jxj", "北京");
var converter = mapper.GetConverter<Customer, CustomerDTO>();
var result = converter.Convert(source);
This rule also stacks with the default prefix recognition capability, so it can match Name to CustomerName and City to CustomerCities.
AddProjection provides the truly open extension point for custom rules.
If the naming difference is neither a prefix nor a suffix—for example, a spelling variant such as Nume -> Name—you need to attach a custom projection directly. The value of AddProjection is that it does not restrict the type of rule.
IMapper mapper = Mapper.Create();
mapper.ConfigureMap<ProductJson, Product>()
.Source
.AddProjection(Projection.Replace("Nume", "Name")); // Custom string replacement rule
var source = new ProductJson { CityNume = "北京", ProductNumeCount = 999 };
var converter = mapper.GetConverter<ProductJson, Product>();
var result = converter.Convert(source);
This code shows that Projection is not merely an internal implementation detail of the mapper. It is a rule system that can also be exposed to users.
The repository images show platform branding rather than technical architecture.
This image is a Cnblogs brand mark. It does not convey project structure, workflow, or code semantics, so no technical visual interpretation is needed.

AI Visual Insight: This animated image shows a sharing interaction prompt on the blog page. It reflects the distribution entry point of the publishing platform rather than the architecture, interfaces, or execution flow of PocoEmit.Mapper or the projection mechanism itself.
This refactor essentially upgrades the mapper from a utility into a rule engine.
With Projection, PocoEmit.Mapper abstracts field naming transformations into an independent capability, then combines Filter, Through, Cross, and parallel composition into a complete matching strategy. This both improves mapping success rates and significantly increases rule reusability.
From an engineering perspective, this design is better than stuffing every naming edge case into the mapper core. The Projection library can be reused independently and could later extend to scenarios such as Source Generators, forming a more unified naming transformation infrastructure.
Related resources
- Projection source code: https://github.com/donetsoftwork/HandCore.net/tree/master/Hand.Projections
- PocoEmit.Mapper source code: https://github.com/donetsoftwork/MyEmit/tree/main/PocoEmit.Mapper
- Installation command:
dotnet add package PocoEmit.Mapper --version 0.8.8.1-alpha
FAQ
1. What is the biggest difference between PocoEmit.Mapper Projection and an AutoMapper Profile?
Projection focuses more on abstracting and composing naming rules than on writing one-off configuration. It lets you turn prefix, suffix, and replacement logic into general-purpose rules that can be reused across multiple mapping relationships.
2. Why is Cross projection better suited for the mapper’s internal implementation?
Because Cross preserves both original fields and projected fields, it creates a larger candidate set. That lets it cover both original-name matching and alias matching, reducing mapping failures caused by naming differences.
3. When should you use AddProjection instead of AddPrefix or AddSuffix?
When the field difference does not belong to a standard prefix or suffix scenario—for example, spelling errors, historical naming baggage, or domain abbreviation conversion—you should prefer AddProjection to attach a custom rule.
AI Readability Summary
This article systematically breaks down the Projection refactor in PocoEmit.Mapper. It explains the IProjection interface, the three projection modes—Filter, Through, and Cross—and how they apply to prefix, suffix, and replacement rules. The goal is to help developers build extensible, composable object mapping with less configuration overhead.