Skip to content

Commit 23c9c27

Browse files
committed
chore: example cleanup
1 parent de74534 commit 23c9c27

24 files changed

+275
-183
lines changed

Atc.SourceGenerators.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<Project Path="sample/Atc.SourceGenerators.DependencyRegistration.Domain/Atc.SourceGenerators.DependencyRegistration.Domain.csproj" />
2020
<Project Path="sample/Atc.SourceGenerators.DependencyRegistration/Atc.SourceGenerators.DependencyRegistration.csproj" />
2121
<Project Path="sample/Atc.SourceGenerators.EnumMapping/Atc.SourceGenerators.EnumMapping.csproj" />
22+
<Project Path="sample/Atc.SourceGenerators.Mapping.Contract/Atc.SourceGenerators.Mapping.Contract.csproj" />
2223
<Project Path="sample/Atc.SourceGenerators.Mapping.DataAccess/Atc.SourceGenerators.Mapping.DataAccess.csproj" />
2324
<Project Path="sample/Atc.SourceGenerators.Mapping.Domain/Atc.SourceGenerators.Mapping.Domain.csproj" />
2425
<Project Path="sample/Atc.SourceGenerators.Mapping/Atc.SourceGenerators.Mapping.csproj" />

docs/samples/Mapping.md

Lines changed: 160 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ This sample demonstrates the **MappingGenerator** in a realistic 3-layer archite
1616
- **Atc.SourceGenerators.Mapping** - ASP.NET Core Minimal API (entry point)
1717
- **Atc.SourceGenerators.Mapping.Domain** - Domain models and business logic
1818
- **Atc.SourceGenerators.Mapping.DataAccess** - Data access layer with entities
19+
- **Atc.SourceGenerators.Mapping.Contract** - API contracts (DTOs)
1920

2021
## 🏗️ Architecture
2122

2223
```mermaid
23-
graph LR
24+
graph TB
2425
subgraph "API Layer"
2526
API[Minimal API Endpoints]
26-
DTO[DTOs: UserDto, AddressDto]
27+
end
28+
29+
subgraph "Contract Layer"
30+
DTO[DTOs: UserDto, AddressDto, UserStatusDto]
2731
end
2832
2933
subgraph "Domain Layer"
@@ -36,32 +40,36 @@ graph LR
3640
EE[Entity Enums: UserStatusEntity]
3741
end
3842
39-
subgraph "Generated Mappings"
40-
M1["UserEntity.MapToUser()"]
41-
M2["User.MapToUserDto()"]
42-
M3["AddressEntity.MapToAddress()"]
43-
M4["Address.MapToAddressDto()"]
43+
subgraph "Generated Bidirectional Mappings"
44+
M1["User ↔ UserEntity"]
45+
M2["User → UserDto"]
46+
M3["Address ↔ AddressEntity"]
47+
M4["Address → AddressDto"]
48+
M5["UserStatus ↔ UserStatusEntity"]
49+
M6["UserStatus → UserStatusDto"]
4450
end
4551
46-
ENT -->|MapToUser| M1
47-
M1 --> DM
48-
DM -->|MapToUserDto| M2
49-
M2 --> DTO
50-
51-
EE -.->|auto cast| DE
52-
DE -.->|auto cast| DTO
53-
54-
ENT -.->|nested| M3
55-
M3 --> DM
56-
DM -.->|nested| M4
57-
5852
API --> DTO
59-
DTO --> API
53+
DTO --> DM
54+
DM --> M1
55+
DM --> M2
56+
DM --> M3
57+
DM --> M4
58+
DE --> M5
59+
DE --> M6
60+
M1 --> ENT
61+
M3 --> ENT
62+
M5 --> EE
63+
M2 --> DTO
64+
M4 --> DTO
65+
M6 --> DTO
6066
6167
style M1 fill:#2ea44f
6268
style M2 fill:#2ea44f
6369
style M3 fill:#2ea44f
6470
style M4 fill:#2ea44f
71+
style M5 fill:#2ea44f
72+
style M6 fill:#2ea44f
6573
```
6674

6775
## 🔄 Mapping Flow
@@ -109,37 +117,42 @@ sequenceDiagram
109117
### Data Access Layer
110118

111119
```csharp
112-
using Atc.SourceGenerators.Annotations;
113-
114-
namespace Atc.SourceGenerators.Mapping.DataAccess;
120+
namespace Atc.SourceGenerators.Mapping.DataAccess.Entities;
115121

116-
// Entity with mapping to Domain
117-
[MapTo(typeof(Domain.User))]
118-
public partial class UserEntity
122+
// Entity - NO mapping attribute (mapping defined in Domain)
123+
public class UserEntity
119124
{
120125
public int DatabaseId { get; set; }
121126
public Guid Id { get; set; }
122-
public string Name { get; set; } = string.Empty;
127+
public string FirstName { get; set; } = string.Empty;
128+
public string LastName { get; set; } = string.Empty;
123129
public string Email { get; set; } = string.Empty;
124130
public UserStatusEntity Status { get; set; }
125131
public AddressEntity? Address { get; set; }
126132
public DateTimeOffset CreatedAt { get; set; }
133+
public DateTimeOffset? UpdatedAt { get; set; }
127134
public bool IsDeleted { get; set; } // DB-specific field
135+
public byte[] RowVersion { get; set; } = []; // DB-specific field
128136
}
129137

130-
[MapTo(typeof(Domain.Address))]
131-
public partial class AddressEntity
138+
public class AddressEntity
132139
{
140+
public int Id { get; set; }
133141
public string Street { get; set; } = string.Empty;
134142
public string City { get; set; } = string.Empty;
143+
public string State { get; set; } = string.Empty;
135144
public string PostalCode { get; set; } = string.Empty;
145+
public string Country { get; set; } = string.Empty;
146+
public DateTime CreatedAt { get; set; }
147+
public DateTime? UpdatedAt { get; set; }
136148
}
137149

138150
public enum UserStatusEntity
139151
{
140152
Active = 0,
141153
Inactive = 1,
142-
Suspended = 2
154+
Suspended = 2,
155+
Deleted = 3
143156
}
144157
```
145158

@@ -150,131 +163,159 @@ using Atc.SourceGenerators.Annotations;
150163

151164
namespace Atc.SourceGenerators.Mapping.Domain;
152165

153-
// Domain model with mapping to DTO
166+
// Domain model with BIDIRECTIONAL mapping to Entity and forward mapping to DTO
154167
[MapTo(typeof(UserDto))]
168+
[MapTo(typeof(UserEntity), Bidirectional = true)]
155169
public partial class User
156170
{
157-
public Guid Id { get; init; }
158-
public string Name { get; init; } = string.Empty;
159-
public string Email { get; init; } = string.Empty;
160-
public UserStatus Status { get; init; }
161-
public Address? Address { get; init; }
162-
public DateTimeOffset CreatedAt { get; init; }
171+
public Guid Id { get; set; }
172+
public string FirstName { get; set; } = string.Empty;
173+
public string LastName { get; set; } = string.Empty;
174+
public string Email { get; set; } = string.Empty;
175+
public UserStatus Status { get; set; }
176+
public Address? Address { get; set; }
177+
public DateTimeOffset CreatedAt { get; set; }
178+
public DateTimeOffset? UpdatedAt { get; set; }
163179
}
164180

165181
[MapTo(typeof(AddressDto))]
182+
[MapTo(typeof(AddressEntity), Bidirectional = true)]
166183
public partial class Address
167184
{
168-
public string Street { get; init; } = string.Empty;
169-
public string City { get; init; } = string.Empty;
170-
public string PostalCode { get; init; } = string.Empty;
185+
public string Street { get; set; } = string.Empty;
186+
public string City { get; set; } = string.Empty;
187+
public string State { get; set; } = string.Empty;
188+
public string PostalCode { get; set; } = string.Empty;
189+
public string Country { get; set; } = string.Empty;
171190
}
172191

192+
[MapTo(typeof(UserStatusDto))]
193+
[MapTo(typeof(UserStatusEntity), Bidirectional = true)]
173194
public enum UserStatus
174195
{
175196
Active = 0,
176197
Inactive = 1,
177-
Suspended = 2
198+
Suspended = 2,
199+
Deleted = 3
178200
}
201+
```
202+
203+
### Contract Layer
204+
205+
```csharp
206+
namespace Atc.SourceGenerators.Mapping.Contract;
179207

180-
// DTOs
208+
// DTOs - no mapping attributes needed (mapped from Domain)
181209
public class UserDto
182210
{
183211
public Guid Id { get; set; }
184-
public string Name { get; set; } = string.Empty;
212+
public string FirstName { get; set; } = string.Empty;
213+
public string LastName { get; set; } = string.Empty;
185214
public string Email { get; set; } = string.Empty;
186-
public string Status { get; set; } = string.Empty; // Different type!
215+
public UserStatusDto Status { get; set; }
187216
public AddressDto? Address { get; set; }
188217
public DateTimeOffset CreatedAt { get; set; }
218+
public DateTimeOffset? UpdatedAt { get; set; }
189219
}
190220

191221
public class AddressDto
192222
{
193223
public string Street { get; set; } = string.Empty;
194224
public string City { get; set; } = string.Empty;
225+
public string State { get; set; } = string.Empty;
195226
public string PostalCode { get; set; } = string.Empty;
227+
public string Country { get; set; } = string.Empty;
228+
}
229+
230+
public enum UserStatusDto
231+
{
232+
Active = 0,
233+
Inactive = 1,
234+
Suspended = 2,
235+
Deleted = 3
196236
}
197237
```
198238

199239
### API Usage
200240

201241
```csharp
202242
using Atc.Mapping;
243+
using Atc.SourceGenerators.Mapping.Contract;
203244
using Atc.SourceGenerators.Mapping.Domain;
204245
using Microsoft.AspNetCore.Mvc;
205246

206-
var app = WebApplication.Create();
247+
var builder = WebApplication.CreateBuilder(args);
248+
249+
// Register services
250+
builder.Services.AddSingleton<IUserRepository, UserRepository>();
251+
builder.Services.AddSingleton<UserService>();
207252

208-
// POST endpoint - Create user
209-
app.MapPost("/users", async ([FromBody] CreateUserRequest request) =>
253+
var app = builder.Build();
254+
255+
// GET endpoint - Retrieve user by ID
256+
app.MapGet("/users/{id:guid}", (Guid id, UserService userService) =>
210257
{
211-
// Convert DTO → Domain
212-
var user = new User
258+
var user = userService.GetById(id);
259+
if (user is null)
213260
{
214-
Id = Guid.NewGuid(),
215-
Name = request.Name,
216-
Email = request.Email,
217-
Status = UserStatus.Active,
218-
CreatedAt = DateTimeOffset.UtcNow
219-
};
220-
221-
// Convert Domain → Entity
222-
var entity = user.MapToUserEntity();
223-
// Save to database...
224-
225-
// Convert Domain → DTO for response
226-
var dto = user.MapToUserDto();
227-
return Results.Created($"/users/{user.Id}", dto);
228-
});
229-
230-
// GET endpoint - Retrieve user
231-
app.MapGet("/users/{id:guid}", async (Guid id) =>
232-
{
233-
// Fetch from database
234-
var entity = await repository.GetByIdAsync(id);
235-
if (entity == null) return Results.NotFound();
236-
237-
// Complete mapping chain: Entity → Domain → DTO
238-
var user = entity.MapToUser(); // Auto-converts enum, nested Address
239-
var dto = user.MapToUserDto(); // Auto-maps all properties
261+
return Results.NotFound(new { message = $"User with ID {id} not found" });
262+
}
240263

241-
return Results.Ok(dto);
242-
});
264+
// ✨ Use generated mapping: Domain → DTO
265+
var data = user.MapToUserDto();
266+
return Results.Ok(data);
267+
})
268+
.WithName("GetUserById")
269+
.Produces<UserDto>(StatusCodes.Status200OK)
270+
.Produces(StatusCodes.Status404NotFound);
243271

244-
app.Run();
272+
// GET endpoint - Retrieve all users
273+
app.MapGet("/users", (UserService userService) =>
274+
{
275+
// ✨ Use generated mapping: Domain → DTO
276+
var data = userService
277+
.GetAll()
278+
.Select(u => u.MapToUserDto())
279+
.ToList();
280+
return Results.Ok(data);
281+
})
282+
.WithName("GetAllUsers")
283+
.Produces<List<UserDto>>(StatusCodes.Status200OK);
284+
285+
await app.RunAsync();
245286
```
246287

247288
## 📝 Generated Code
248289

249-
The generator creates extension methods for each mapping:
290+
The generator creates extension methods for bidirectional mappings:
250291

251292
```csharp
252293
// <auto-generated />
253294
namespace Atc.Mapping;
254295

255-
public static partial class UserEntityExtensions
296+
// Bidirectional mapping: User ↔ UserEntity
297+
public static partial class UserExtensions
256298
{
257-
public static Domain.User MapToUser(this UserEntity source)
299+
public static UserEntity MapToUserEntity(this User source)
258300
{
259301
if (source is null)
260302
{
261303
return default!;
262304
}
263305

264-
return new Domain.User
306+
return new UserEntity
265307
{
266308
Id = source.Id,
267-
Name = source.Name,
309+
FirstName = source.FirstName,
310+
LastName = source.LastName,
268311
Email = source.Email,
269-
Status = (Domain.UserStatus)source.Status, // ✨ Auto enum conversion
270-
Address = source.Address?.MapToAddress()!, // ✨ Auto nested mapping
271-
CreatedAt = source.CreatedAt
312+
Status = source.Status.MapToUserStatusEntity(), // ✨ Safe enum mapping (bidirectional)
313+
Address = source.Address?.MapToAddressEntity()!, // ✨ Auto nested mapping
314+
CreatedAt = source.CreatedAt,
315+
UpdatedAt = source.UpdatedAt
272316
};
273317
}
274-
}
275318

276-
public static partial class UserExtensions
277-
{
278319
public static UserDto MapToUserDto(this User source)
279320
{
280321
if (source is null)
@@ -285,11 +326,37 @@ public static partial class UserExtensions
285326
return new UserDto
286327
{
287328
Id = source.Id,
288-
Name = source.Name,
329+
FirstName = source.FirstName,
330+
LastName = source.LastName,
331+
Email = source.Email,
332+
Status = source.Status.MapToUserStatusDto(), // ✨ Safe enum mapping
333+
Address = source.Address?.MapToAddressDto()!, // ✨ Nested mapping
334+
CreatedAt = source.CreatedAt,
335+
UpdatedAt = source.UpdatedAt
336+
};
337+
}
338+
}
339+
340+
public static partial class UserEntityExtensions
341+
{
342+
// Reverse mapping (from Bidirectional = true)
343+
public static User MapToUser(this UserEntity source)
344+
{
345+
if (source is null)
346+
{
347+
return default!;
348+
}
349+
350+
return new User
351+
{
352+
Id = source.Id,
353+
FirstName = source.FirstName,
354+
LastName = source.LastName,
289355
Email = source.Email,
290-
Status = source.Status.ToString(), // ✨ Enum to string
291-
Address = source.Address?.MapToAddressDto()!, // ✨ Nested mapping
292-
CreatedAt = source.CreatedAt
356+
Status = source.Status.MapToUserStatus(), // ✨ Safe enum mapping (reverse)
357+
Address = source.Address?.MapToAddress()!, // ✨ Auto nested mapping
358+
CreatedAt = source.CreatedAt,
359+
UpdatedAt = source.UpdatedAt
293360
};
294361
}
295362
}

0 commit comments

Comments
 (0)