Skip to content

Raffinert/AlephMapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

56 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Stand With Ukraine

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

NuGet License

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.

πŸš€ Features

  • 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

πŸ“¦ Installation

dotnet add package AlephMapper

πŸƒβ€β™‚οΈ Quick Start

1. Mark your mapping class or method with [Expressive]

Ensure 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;
}

2. AlephMapper generates an expression-friendly companion method

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"
    };
}

3. Use generated expressions in EF Core queries

var personDtos = await dbContext.Employees
    .Select(PersonMapper.MapToPersonExpression())
    .ToListAsync();

4. Use the original methods for in-memory operations

var employee = GetEmployee();
var personDto = PersonMapper.MapToPerson(employee);
var fullName = PersonMapper.GetFullName(employee);

πŸ”§ Advanced Features

Null-conditional operator handling

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?.Age becomes person.BirthInfo != null ? person.BirthInfo.Age : null

Updatable methods

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 instance

πŸ” How It Works

For each method marked with [Expressive]:

  1. MapToPersonDto(Employee employee) -> generates MapToPersonDtoExpression() returning Expression<Func<Employee, PersonDto>>
  2. Method calls are inlined - Calls to other methods are automatically inlined into the expression tree
  3. Null-conditional operators are handled according to your specified rewrite policy

For each method marked with [Updatable]:

  1. MapToPersonDto(Employee employee) -> generates MapToPersonDto(Employee employee, PersonDto target) returning PersonDto
  2. Method calls are inlined - Calls to other methods in the same class are automatically inlined into the method

Limitations

Methods must be:

πŸ”„ Migration from Other Mappers

From AutoMapper

// 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
    };
}

From Manual Expression Trees

// 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;
}

🀝 Contributing

We welcome contributions!

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Run the test suite
  6. Submit a pull request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

πŸ”— Related Projects


Star ⭐ this repository if AlephMapper helps you build better applications!

About

Your manual mapping companion.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages