Terms of use?
By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all the following statements:
- You condemn Russia and its military aggression against Ukraine
- You recognize that Russia is an occupant that unlawfully invaded a sovereign state
- You support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas
- You reject false narratives perpetuated by Russian state propaganda
To learn more about the war and how you can help, click here. Glory to Ukraine!
AlephMapper is a Source Generator that automatically creates projectable and/or updatable companion methods from your mapping logic. It is built to keep your mappings DRY: you author a single method, and AlephMapper reuses that logic everywhereβruntime, expression trees for EF Core projections, and optional in-place updates.
- Single-source mappings - One mapping method feeds runtime, expression-tree, and update variants
- Expression tree generation - Automatically converts method bodies to
Expression<Func<...>> - Null-conditional operator support - Configurable handling of
?.operators - Updatable methods - Generate in-place update overloads that mutate existing instances
dotnet add package AlephMapperEnsure the containing class is
static partial.
public static partial class PersonMapper
{
[Expressive]
public static PersonDto MapToPerson(Employee employee) => new PersonDto
{
Id = employee.EmployeeId,
FullName = GetFullName(employee),
Email = employee.ContactInfo.Email,
Age = CalculateAge(employee.BirthDate),
Department = employee.Department?.Name ?? "Unknown"
};
public static string GetFullName(Employee employee) =>
$"{employee.FirstName} {employee.LastName}";
public static int CalculateAge(DateTime birthDate) =>
DateTime.Now.Year - birthDate.Year;
}partial class PersonMapper
{
public static Expression<Func<Employee, PersonDto>> MapToPersonExpression() => employee => new PersonDto
{
Id = employee.EmployeeId,
FullName = $"{employee.FirstName} {employee.LastName}",
Email = employee.ContactInfo.Email,
Age = DateTime.Now.Year - employee.BirthDate.Year,
Department = employee.Department.Name ?? "Unknown"
};
}var personDtos = await dbContext.Employees
.Select(PersonMapper.MapToPersonExpression())
.ToListAsync();var employee = GetEmployee();
var personDto = PersonMapper.MapToPerson(employee);
var fullName = PersonMapper.GetFullName(employee);AlephMapper provides flexible handling of null-conditional operators (?.):
[Expressive(NullConditionalRewrite = NullConditionalRewrite.Rewrite)]
public static partial class SafeMapper
{
// This method uses null-conditional operators
public static PersonSummary GetSummary(Person person) => new PersonSummary
{
Name = person.Name,
Age = person.BirthInfo?.Age,
City = person.BirthInfo?.Address?.City,
HasAddress = person.BirthInfo?.Address != null
};
}Rewrite options:
None- Don't allow null-conditional operators (throws compile error)Ignore- Remove null-conditional operators (may cause NullReferenceException)Rewrite- Convert to explicit null checks:person.BirthInfo?.Agebecomesperson.BirthInfo != null ? person.BirthInfo.Age : null
From a single mapping, AlephMapper can emit an update-in-place overload that writes into an existing instance (instead of creating a new one). This is especially suitable for EF Core entity updates with change tracking: you keep the same tracked instance, so EF can detect modified properties and produce the correct UPDATE.
[Updatable]
public static Person MapToPerson(PersonUpdateDto dto) => new Person
{
FirstName = dto.FirstName,
LastName = dto.LastName,
Email = dto.Email
// ...
};
// Generated usage with EF Core change tracking:
var person = await db.People.FindAsync(id); // tracked entity
PersonMapper.MapToPerson(dto, target: person); // mutate in-place
await db.SaveChangesAsync(); // EF sees changes on the same instanceFor each method marked with [Expressive]:
MapToPersonDto(Employee employee)-> generatesMapToPersonDtoExpression()returningExpression<Func<Employee, PersonDto>>- Method calls are inlined - Calls to other methods are automatically inlined into the expression tree
- Null-conditional operators are handled according to your specified rewrite policy
For each method marked with [Updatable]:
MapToPersonDto(Employee employee)-> generatesMapToPersonDto(Employee employee, PersonDto target)returningPersonDto- Method calls are inlined - Calls to other methods in the same class are automatically inlined into the method
Methods must be:
- expression-bodied
- static
- members of a partial static class
// AutoMapper
CreateMap<Employee, PersonDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.FirstName + " " + src.LastName));
// AlephMapper
[Expressive]
public static partial class PersonMapper
{
public static PersonDto MapToPerson(Employee employee) => new PersonDto
{
FullName = employee.FirstName + " " + employee.LastName,
// ... other mappings
};
}// Manual Expression Trees
Expression<Func<Employee, PersonDto>> expression = e => new PersonDto
{
FullName = e.FirstName + " " + e.LastName
};
// AlephMapper - Same result, but with method reusability
[Expressive]
public static partial class PersonMapper
{
public static PersonDto MapToPerson(Employee employee) => new PersonDto
{
FullName = GetFullName(employee) // This gets inlined in the expression
};
public static string GetFullName(Employee employee) =>
employee.FirstName + " " + employee.LastName;
}We welcome contributions!
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the test suite
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by EntityFrameworkCore.Projectables and Expressionify
- Thanks to all contributors
- EntityFrameworkCore.Projectables - Similar concept with different approach
- Expressionify - Similar concept with different approach
- AutoMapper - Popular object-to-object mapper
- Mapster - Fast object mapper
- Facet
Star β this repository if AlephMapper helps you build better applications!