Skip to content

Commit 8017964

Browse files
committed
-fix tests
1 parent bc73606 commit 8017964

File tree

2 files changed

+369
-3
lines changed

2 files changed

+369
-3
lines changed

src/TurboMapper/Impl/Mapper.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,11 @@ private void ApplyDefaultNameBasedMapping<TSource, TTarget>(
145145
}
146146

147147
var nestedSourceValue = sourceValue;
148-
ApplyNameBasedMapping(nestedSourceValue, nestedTargetValue);
148+
// Use reflection to call the right generic method for nested mapping
149+
var genericMethod = typeof(Mapper).GetMethod(nameof(ApplyNameBasedMapping),
150+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
151+
var specificMethod = genericMethod.MakeGenericMethod(sourceProp.PropertyType, targetProp.PropertyType);
152+
specificMethod.Invoke(this, new object[] { nestedSourceValue, nestedTargetValue });
149153
}
150154
else
151155
{
@@ -189,8 +193,11 @@ internal void ApplyNameBasedMapping<TSource, TTarget>(TSource source, TTarget ta
189193
targetProp.SetValue(target, nestedTargetValue);
190194
}
191195

192-
// Recursively map the nested object properties
193-
ApplyNameBasedMapping(sourceValue, nestedTargetValue);
196+
// Recursively map the nested object properties using reflection
197+
var genericMethod = typeof(Mapper).GetMethod(nameof(ApplyNameBasedMapping),
198+
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
199+
var specificMethod = genericMethod.MakeGenericMethod(sourceProp.PropertyType, targetProp.PropertyType);
200+
specificMethod.Invoke(this, new object[] { sourceValue, nestedTargetValue });
194201
}
195202
else
196203
{

wiki.md

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
# TurboMapper Implementation Guide
2+
3+
## Overview
4+
TurboMapper is a high-performance object mapping library designed to simplify the process of copying data between objects with similar structures. It provides automatic property mapping, supports complex nested objects, and offers flexible configuration options.
5+
6+
## Core Concepts
7+
8+
### 1. Basic Mapping
9+
The fundamental operation in TurboMapper is mapping properties from a source object to a target object based on property names.
10+
11+
```csharp
12+
var source = new SourceObject { Name = "John", Age = 30 };
13+
var target = mapper.Map<SourceObject, TargetObject>(source);
14+
```
15+
16+
### 2. Complex Type Handling
17+
TurboMapper automatically identifies complex types (custom classes) and handles their mapping recursively.
18+
19+
### 3. Property Matching
20+
Properties are matched by name and write accessibility. Only properties with matching names and compatible types are mapped.
21+
22+
## Architecture
23+
24+
### Core Components
25+
26+
#### Mapper Class
27+
The main entry point for all mapping operations.
28+
29+
**Key Methods:**
30+
- `Map<TSource, TTarget>(TSource source)`: Creates a new target instance and maps all properties
31+
- `ApplyNameBasedMapping<TSource, TTarget>(TSource source, TTarget target)`: Maps properties from source to existing target
32+
- `CreateMap<TSource, TTarget>()`: Configures custom mapping rules
33+
34+
#### Mapping Process
35+
36+
1. **Type Analysis**: Determine source and target property types
37+
2. **Property Matching**: Match properties by name
38+
3. **Value Conversion**: Convert values between compatible types
39+
4. **Recursive Mapping**: Handle nested complex objects
40+
5. **Assignment**: Set values on target properties
41+
42+
### Complex Type Detection
43+
44+
The `IsComplexType(Type type)` method determines whether a type should be treated as a complex object:
45+
46+
```csharp
47+
private bool IsComplexType(Type type)
48+
{
49+
// Strings are not considered complex types
50+
if (type == typeof(string))
51+
return false;
52+
53+
// Value types (int, bool, etc.) are not complex
54+
if (type.IsValueType)
55+
return false;
56+
57+
// Arrays are not considered complex for mapping purposes
58+
if (type.IsArray)
59+
return false;
60+
61+
// Collections are not considered complex for mapping purposes
62+
if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) && type != typeof(string))
63+
return false;
64+
65+
// Everything else is considered a complex type
66+
return true;
67+
}
68+
```
69+
70+
### Nested Object Mapping
71+
72+
When mapping complex types, TurboMapper handles nested objects through recursive calls:
73+
74+
1. Check if source property value is not null
75+
2. Get or create target property instance
76+
3. Recursively map nested object properties
77+
4. Assign mapped instance to target property
78+
79+
### Value Conversion
80+
81+
The `ConvertValue(object value, Type targetType)` method handles type conversions:
82+
83+
- Null value handling
84+
- Direct assignment when types are compatible
85+
- Enum parsing from strings
86+
- String conversion for all types
87+
- GUID parsing from strings
88+
- Numeric type conversions with special handling for truncation
89+
- Complex type mapping through recursive calls
90+
91+
## Advanced Features
92+
93+
### Custom Mapping Configuration
94+
95+
TurboMapper supports custom mapping configurations through the `MappingModule` system:
96+
97+
```csharp
98+
public class UserMappingModule : MappingModule<UserSource, UserTarget>
99+
{
100+
public override Action<IMappingExpression<UserSource, UserTarget>> CreateMappings()
101+
{
102+
return config => config
103+
.ForMember(dest => dest.FullName, src => src.FirstName + " " + src.LastName)
104+
.ForMember(dest => dest.YearsOld, src => src.Age);
105+
}
106+
}
107+
```
108+
109+
### Property Path Mapping
110+
111+
Support for mapping deeply nested properties using dot notation:
112+
113+
```csharp
114+
config.ForMember(dest => dest.Address.PostalCode, src => src.ZipCode);
115+
```
116+
117+
## Error Handling
118+
119+
### Null Source Handling
120+
When a source object is null, TurboMapper returns the default value for the target type.
121+
122+
### Type Conversion Errors
123+
When type conversion fails, TurboMapper returns the default value for the target type rather than throwing exceptions.
124+
125+
### Circular Reference Prevention
126+
TurboMapper includes built-in protection against infinite recursion in circular object graphs.
127+
128+
## Performance Considerations
129+
130+
### Caching
131+
Mapped property information is cached to avoid repeated reflection calls.
132+
133+
### Memory Efficiency
134+
TurboMapper minimizes object allocation during mapping operations.
135+
136+
### Thread Safety
137+
All mapping operations are thread-safe and can be used concurrently.
138+
139+
## Extension Points
140+
141+
### Custom Value Resolvers
142+
Implement custom logic for calculating property values during mapping.
143+
144+
### Conditional Mapping
145+
Apply mappings only when certain conditions are met.
146+
147+
### Pre/Post Processing
148+
Execute custom code before or after mapping operations.
149+
150+
## Best Practices
151+
152+
### 1. Use Nullable Reference Types
153+
Enable nullable reference types to catch potential null reference issues at compile time.
154+
155+
### 2. Configure Mappings at Startup
156+
Register all mapping modules during application initialization for optimal performance.
157+
158+
### 3. Handle Exceptions Appropriately
159+
While TurboMapper minimizes exceptions, always validate mapped data when business rules require it.
160+
161+
### 4. Profile Performance
162+
For high-volume mapping scenarios, profile your application to ensure optimal performance.
163+
164+
## Common Patterns
165+
166+
### Simple DTO Mapping
167+
Map between similar domain objects and data transfer objects.
168+
169+
### Flattening Objects
170+
Map nested properties to flat target structures.
171+
172+
### Reverse Mapping
173+
Create bidirectional mappings for round-trip data transformations.
174+
175+
### Collection Mapping
176+
Map collections of objects using the same mapping configurations.
177+
178+
## Troubleshooting
179+
180+
### Properties Not Mapping
181+
- Check property names match exactly (case-sensitive)
182+
- Verify properties have public getters/setters
183+
- Ensure target properties are writable
184+
- Confirm property types are compatible or convertible
185+
186+
### Nested Objects Always Null
187+
- Verify complex type detection is working correctly
188+
- Check that source nested objects are not null
189+
- Ensure target object constructors properly initialize nested properties
190+
191+
### Performance Issues
192+
- Review mapping configurations for complexity
193+
- Consider caching mapped objects when appropriate
194+
- Profile reflection usage in hot paths
195+
196+
## Integration
197+
198+
### Dependency Injection
199+
TurboMapper integrates seamlessly with DI containers through extension methods:
200+
201+
```csharp
202+
services.AddTurboMapper();
203+
```
204+
205+
### ASP.NET Core
206+
Use TurboMapper in controllers for view model transformation:
207+
208+
```csharp
209+
[ApiController]
210+
public class UserController : ControllerBase
211+
{
212+
private readonly IMapper _mapper;
213+
214+
public UserController(IMapper mapper)
215+
{
216+
_mapper = mapper;
217+
}
218+
219+
[HttpGet("{id}")]
220+
public ActionResult<UserDto> GetUser(int id)
221+
{
222+
var user = _userRepository.GetById(id);
223+
var dto = _mapper.Map<User, UserDto>(user);
224+
return Ok(dto);
225+
}
226+
}
227+
```
228+
229+
## Testing Strategies
230+
231+
### Unit Testing Mappings
232+
Create dedicated tests for each mapping configuration to ensure correctness.
233+
234+
### Performance Testing
235+
Benchmark mapping operations for critical paths in your application.
236+
237+
### Integration Testing
238+
Test end-to-end scenarios that involve multiple mapping operations.
239+
240+
## Limitations
241+
242+
### Dynamic Properties
243+
TurboMapper does not support mapping to dynamic properties or ExpandoObjects.
244+
245+
### Private Members
246+
Only public properties are considered for mapping.
247+
248+
### Indexers
249+
Properties with indexers are not supported.
250+
251+
## Future Enhancements
252+
253+
### Expression-Based Mapping
254+
Compile mapping expressions for even better performance.
255+
256+
### Convention-Based Mapping
257+
Support configurable naming conventions (camelCase, PascalCase, etc.).
258+
259+
### Validation Integration
260+
Built-in validation during mapping operations.
261+
262+
## API Reference
263+
264+
### IMapper Interface
265+
266+
#### Methods:
267+
- `TTarget Map<TSource, TTarget>(TSource source)`: Primary mapping method
268+
- `void CreateMap<TSource, TTarget>()`: Configure mapping between types
269+
270+
### IMappingExpression Interface
271+
272+
#### Methods:
273+
- `IMappingExpression<TSource, TTarget> ForMember<TMember>(Expression<Func<TTarget, TMember>> destination, Func<TSource, TMember> source)`: Configure property mapping
274+
275+
### MappingModule<TSrc, TDest> Abstract Class
276+
277+
#### Methods:
278+
- `abstract Action<IMappingExpression<TSrc, TDest>> CreateMappings()`: Define custom mapping rules
279+
280+
## Examples
281+
282+
### Basic Mapping
283+
```csharp
284+
public class PersonSource
285+
{
286+
public string FirstName { get; set; }
287+
public string LastName { get; set; }
288+
public int Age { get; set; }
289+
}
290+
291+
public class PersonTarget
292+
{
293+
public string FirstName { get; set; }
294+
public string LastName { get; set; }
295+
public int Age { get; set; }
296+
}
297+
298+
// Usage
299+
var source = new PersonSource
300+
{
301+
FirstName = "John",
302+
LastName = "Doe",
303+
Age = 30
304+
};
305+
306+
var target = mapper.Map<PersonSource, PersonTarget>(source);
307+
```
308+
309+
### Nested Object Mapping
310+
```csharp
311+
public class OrderSource
312+
{
313+
public string OrderId { get; set; }
314+
public CustomerSource Customer { get; set; }
315+
}
316+
317+
public class CustomerSource
318+
{
319+
public string Name { get; set; }
320+
public AddressSource Address { get; set; }
321+
}
322+
323+
public class AddressSource
324+
{
325+
public string Street { get; set; }
326+
public string City { get; set; }
327+
}
328+
329+
// Target classes follow the same structure
330+
var source = new OrderSource
331+
{
332+
OrderId = "123",
333+
Customer = new CustomerSource
334+
{
335+
Name = "John Doe",
336+
Address = new AddressSource
337+
{
338+
Street = "123 Main St",
339+
City = "Anytown"
340+
}
341+
}
342+
};
343+
344+
var target = mapper.Map<OrderSource, OrderTarget>(source);
345+
// target.Customer.Address.Street will be "123 Main St"
346+
```
347+
348+
### Custom Mapping Module
349+
```csharp
350+
public class OrderMappingModule : MappingModule<OrderSource, OrderTarget>
351+
{
352+
public override Action<IMappingExpression<OrderSource, OrderTarget>> CreateMappings()
353+
{
354+
return config => config
355+
.ForMember(dest => dest.OrderNumber, src => src.OrderId)
356+
.ForMember(dest => dest.CustomerName, src => src.Customer.Name);
357+
}
358+
}
359+
```

0 commit comments

Comments
 (0)