-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSelectExprInfoAnonymous.cs
More file actions
167 lines (146 loc) · 6.5 KB
/
SelectExprInfoAnonymous.cs
File metadata and controls
167 lines (146 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Linqraft.Core.Formatting;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Linqraft.Core;
/// <summary>
/// SelectExprInfo for anonymous type Select expressions
/// </summary>
public record SelectExprInfoAnonymous : SelectExprInfo
{
/// <summary>
/// The anonymous object creation expression
/// </summary>
public required AnonymousObjectCreationExpressionSyntax AnonymousObject { get; init; }
/// <summary>
/// Generates DTO classes (anonymous types don't generate separate classes)
/// </summary>
public override List<GenerateDtoClassInfo> GenerateDtoClasses() => [];
/// <summary>
/// Generates the DTO structure for unique ID generation
/// </summary>
public override DtoStructure GenerateDtoStructure()
{
return DtoStructure.AnalyzeAnonymousType(
AnonymousObject,
SemanticModel,
SourceType,
configuration: Configuration
)!;
}
/// <summary>
/// Gets the DTO class name (empty for anonymous types)
/// </summary>
public override string GetClassName(DtoStructure structure) => "";
/// <summary>
/// Gets the parent DTO class name (empty for anonymous types)
/// </summary>
public override string GetParentDtoClassName(DtoStructure structure) => "";
/// <summary>
/// Gets the namespace where DTOs will be placed
/// Anonymous types don't generate separate DTOs, but return caller namespace for consistency
/// </summary>
public override string GetDtoNamespace() => CallerNamespace;
// Get expression type string (for documentation)
public override string GetExprTypeString() => "anonymous";
/// <summary>
/// Generates static field declarations for pre-built expressions (if enabled)
/// </summary>
public override string? GenerateStaticFields()
{
// Anonymous types cannot use pre-built expressions because we don't know the result type at compile time
// The result type is inferred from the lambda and is an compiler-generated anonymous type
return null;
}
// Generate SelectExpr method
protected override string GenerateSelectExprMethod(
string dtoName,
DtoStructure structure,
InterceptableLocation location
)
{
var sourceTypeFullName = structure.SourceTypeFullName;
var returnTypePrefix = GetReturnTypePrefix();
var sb = new StringBuilder();
var id = GetUniqueId();
// Anonymous types cannot use pre-built expressions (result type is unknown)
// so we always use inline lambda
sb.AppendLine(GenerateMethodHeaderPart("anonymous type", location));
// Determine if we have capture parameters
var hasCapture = CaptureArgumentExpression != null && CaptureArgumentType != null;
if (hasCapture)
{
// Generate method with capture parameter that creates closure variables
// Extract property names and values from the capture object to create properly-typed closure variables
sb.AppendLine(
$"public static {returnTypePrefix}<TResult> SelectExpr_{id}<TIn, TResult>("
);
sb.AppendLine(
$" this {returnTypePrefix}<TIn> query, Func<TIn, TResult> selector, object captureParam)"
);
sb.AppendLine($"{{");
sb.AppendLine(
$" var matchedQuery = query as object as {returnTypePrefix}<{sourceTypeFullName}>;"
);
// For anonymous types, use dynamic to extract properties as closure variables
// This allows the lambda to reference them with the correct types (closure will capture the typed values)
var isAnonymousType =
CaptureArgumentType != null && CaptureArgumentType.IsAnonymousType;
if (isAnonymousType && CaptureArgumentType != null)
{
// For anonymous types, get the properties and create closure variables using dynamic
var properties = CaptureArgumentType.GetMembers().OfType<IPropertySymbol>();
sb.AppendLine($" dynamic captureObj = captureParam;");
foreach (var prop in properties)
{
var propTypeName = prop.Type.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat
);
sb.AppendLine($" {propTypeName} {prop.Name} = captureObj.{prop.Name};");
}
}
else
{
// For non-anonymous types, just cast it
var captureTypeName =
CaptureArgumentType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
?? "object";
sb.AppendLine($" var capture = ({captureTypeName})captureParam;");
}
// Anonymous types always use inline lambda (cannot use pre-built expressions)
sb.AppendLine($" var converted = matchedQuery.Select({LambdaParameterName} => new");
}
else
{
// Generate method without capture parameter
sb.AppendLine(
$"public static {returnTypePrefix}<TResult> SelectExpr_{id}<TIn, TResult>("
);
sb.AppendLine($" this {returnTypePrefix}<TIn> query, Func<TIn, TResult> selector)");
sb.AppendLine($"{{");
sb.AppendLine(
$" var matchedQuery = query as object as {returnTypePrefix}<{sourceTypeFullName}>;"
);
// Anonymous types always use inline lambda (cannot use pre-built expressions)
sb.AppendLine($" var converted = matchedQuery.Select({LambdaParameterName} => new");
}
sb.AppendLine($" {{");
// Generate property assignments
var propertyAssignments = structure
.Properties.Select(prop =>
{
var assignment = GeneratePropertyAssignment(prop, CodeFormatter.IndentSize * 2);
return $"{CodeFormatter.Indent(2)}{prop.Name} = {assignment}";
})
.ToList();
sb.AppendLine(string.Join($",{CodeFormatter.DefaultNewLine}", propertyAssignments));
sb.AppendLine(" });");
sb.AppendLine($" return converted as object as {returnTypePrefix}<TResult>;");
sb.AppendLine("}");
sb.AppendLine();
return sb.ToString();
}
}