@@ -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
138150public 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
151164namespace 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 )]
155169public 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 )]
166183public 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 )]
173194public 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)
181209public 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
191221public 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
202242using Atc .Mapping ;
243+ using Atc .SourceGenerators .Mapping .Contract ;
203244using Atc .SourceGenerators .Mapping .Domain ;
204245using 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 />
253294namespace 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