@@ -154,22 +154,22 @@ protected string GenerateAnonymousCaptureMemberAccessAliases()
154154
155155 if ( initializer . Expression is MemberAccessExpressionSyntax memberAccess )
156156 {
157- var rootName = GetRootIdentifierName ( memberAccess . Expression ) ;
157+ var rootName = GetRootIdentifierName ( memberAccess ) ;
158158 if ( rootName == null )
159159 {
160160 continue ;
161161 }
162162
163- var memberName = memberAccess . Name . Identifier . Text ;
164- var captureName = capturePropertyName ?? memberName ;
163+ var memberPath = GetMemberPathFromRoot ( memberAccess , rootName ) ;
164+ var captureName = capturePropertyName ?? memberAccess . Name . Identifier . Text ;
165165
166166 if ( ! aliasMap . TryGetValue ( rootName , out var members ) )
167167 {
168168 members = new List < ( string MemberName , string CapturePropertyName ) > ( ) ;
169169 aliasMap [ rootName ] = members ;
170170 }
171171
172- members . Add ( ( memberName , captureName ) ) ;
172+ members . Add ( ( memberPath , captureName ) ) ;
173173 }
174174 }
175175
@@ -189,27 +189,70 @@ protected string GenerateAnonymousCaptureMemberAccessAliases()
189189 {
190190 var rootName = kvp . Key ;
191191 var members = kvp . Value ;
192- sb . AppendLine ( $ " var { rootName } = new") ;
193- sb . AppendLine ( " {" ) ;
194-
195- var usedMemberNames = new HashSet < string > ( ) ;
192+
193+ // Build a tree structure for nested member paths
194+ var tree = new Dictionary < string , object > ( ) ;
196195 foreach ( var memberPair in members )
197196 {
198- var memberName = memberPair . MemberName ;
197+ var memberPath = memberPair . MemberName ;
199198 var captureName = memberPair . CapturePropertyName ;
200- if ( ! usedMemberNames . Add ( memberName ) )
199+
200+ var pathParts = memberPath . Split ( '.' ) ;
201+ var current = tree ;
202+
203+ for ( int i = 0 ; i < pathParts . Length - 1 ; i ++ )
201204 {
202- continue ;
205+ var part = pathParts [ i ] ;
206+ if ( ! current . TryGetValue ( part , out var child ) )
207+ {
208+ child = new Dictionary < string , object > ( ) ;
209+ current [ part ] = child ;
210+ }
211+ current = ( Dictionary < string , object > ) child ;
212+ }
213+
214+ // Add the leaf node with the capture name
215+ var lastPart = pathParts [ ^ 1 ] ;
216+ if ( ! current . ContainsKey ( lastPart ) )
217+ {
218+ current [ lastPart ] = captureName ;
203219 }
204- sb . AppendLine ( $ " { memberName } = captureObj.{ captureName } ,") ;
205220 }
206-
221+
222+ // Generate the nested anonymous object
223+ sb . AppendLine ( $ " var { rootName } = new") ;
224+ sb . AppendLine ( " {" ) ;
225+ GenerateNestedAnonymousObject ( sb , tree , 2 , "captureObj" ) ;
207226 sb . AppendLine ( " };" ) ;
208227 }
209228
210229 return sb . ToString ( ) ;
211230 }
212231
232+ private static void GenerateNestedAnonymousObject ( StringBuilder sb , Dictionary < string , object > tree , int indentLevel , string captureObjName )
233+ {
234+ var indent = new string ( ' ' , indentLevel * 4 ) ;
235+ foreach ( var kvp in tree )
236+ {
237+ var memberName = kvp . Key ;
238+ var value = kvp . Value ;
239+
240+ if ( value is string captureName )
241+ {
242+ // Leaf node: simple assignment
243+ sb . AppendLine ( $ "{ indent } { memberName } = { captureObjName } .{ captureName } ,") ;
244+ }
245+ else if ( value is Dictionary < string , object > nested )
246+ {
247+ // Nested node: create nested anonymous object
248+ sb . AppendLine ( $ "{ indent } { memberName } = new") ;
249+ sb . AppendLine ( $ "{ indent } {{") ;
250+ GenerateNestedAnonymousObject ( sb , nested , indentLevel + 1 , captureObjName ) ;
251+ sb . AppendLine ( $ "{ indent } }},") ;
252+ }
253+ }
254+ }
255+
213256 private static string ? GetCapturePropertyName ( AnonymousObjectMemberDeclaratorSyntax initializer )
214257 {
215258 if ( initializer . NameEquals != null )
@@ -235,6 +278,30 @@ protected string GenerateAnonymousCaptureMemberAccessAliases()
235278 } ;
236279 }
237280
281+ private static string GetMemberPathFromRoot ( ExpressionSyntax expression , string rootName )
282+ {
283+ var parts = new List < string > ( ) ;
284+ var current = expression ;
285+
286+ while ( current is MemberAccessExpressionSyntax memberAccess )
287+ {
288+ parts . Insert ( 0 , memberAccess . Name . Identifier . Text ) ;
289+ current = memberAccess . Expression ;
290+ }
291+
292+ // If current is the root identifier, we've collected all member parts
293+ if ( current is IdentifierNameSyntax identifier && identifier . Identifier . Text == rootName )
294+ {
295+ return string . Join ( "." , parts ) ;
296+ }
297+
298+ // Fallback: This should not happen in normal usage since we only call this method
299+ // after verifying GetRootIdentifierName succeeds. Return the last member name as
300+ // a safe fallback to avoid breaking the generation if the expression structure
301+ // is unexpected (e.g., complex expressions that aren't simple member chains).
302+ return parts . Count > 0 ? parts [ ^ 1 ] : "" ;
303+ }
304+
238305 /// <summary>
239306 /// Gets the full name for a nested DTO class using the structure.
240307 /// This allows derived classes to compute namespace-based naming using the structure's hash.
0 commit comments