-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSelectExprInfoNamed.cs
More file actions
244 lines (214 loc) · 9.53 KB
/
SelectExprInfoNamed.cs
File metadata and controls
244 lines (214 loc) · 9.53 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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 named (predefined DTO) Select expressions
/// </summary>
public record SelectExprInfoNamed : SelectExprInfo
{
/// <summary>
/// The object creation expression for the named type
/// </summary>
public required ObjectCreationExpressionSyntax ObjectCreation { get; init; }
/// <summary>
/// Generates DTO classes (predefined types don't generate new classes)
/// </summary>
public override List<GenerateDtoClassInfo> GenerateDtoClasses() => [];
/// <summary>
/// Generates the DTO structure for unique ID generation
/// </summary>
public override DtoStructure GenerateDtoStructure()
{
return DtoStructure.AnalyzeNamedType(
ObjectCreation,
SemanticModel,
SourceType,
configuration: Configuration
)!;
}
/// <summary>
/// Gets the DTO class name (uses the source type name)
/// </summary>
public override string GetClassName(DtoStructure structure) => structure.SourceTypeName;
/// <summary>
/// Gets the parent DTO class name (fully qualified)
/// </summary>
public override string GetParentDtoClassName(DtoStructure structure) =>
structure.SourceTypeFullName;
/// <summary>
/// Gets the namespace where DTOs will be placed
/// Named types use the DTO's own namespace
/// </summary>
public override string GetDtoNamespace() =>
SourceType.ContainingNamespace?.ToDisplayString() ?? CallerNamespace;
// Get expression type string (for documentation)
public override string GetExprTypeString() => "predefined";
/// <summary>
/// Generates static field declarations for pre-built expressions (if enabled)
/// </summary>
public override string? GenerateStaticFields()
{
// Check if we should use pre-built expressions (only for IQueryable, not IEnumerable)
var usePrebuildExpression =
Configuration.UsePrebuildExpression && !IsEnumerableInvocation();
// Don't generate fields if captures are used (they don't work well with closures)
var hasCapture = CaptureArgumentExpression != null && CaptureArgumentType != null;
if (!usePrebuildExpression || hasCapture)
{
return null;
}
var querySourceTypeFullName = SourceType.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat
);
var structure = GenerateDtoStructure();
var dtoName = GetParentDtoClassName(structure);
var id = GetUniqueId();
// Build the lambda body
var lambdaBodyBuilder = new StringBuilder();
lambdaBodyBuilder.AppendLine($"new {dtoName}");
lambdaBodyBuilder.AppendLine($" {{");
var propertyAssignments = structure
.Properties.Select(prop =>
{
var assignment = GeneratePropertyAssignment(prop, CodeFormatter.IndentSize * 2);
return $"{CodeFormatter.Indent(2)}{prop.Name} = {assignment}";
})
.ToList();
lambdaBodyBuilder.AppendLine(
string.Join($",{CodeFormatter.DefaultNewLine}", propertyAssignments)
);
lambdaBodyBuilder.Append(" }");
var (fieldDecl, _) = ExpressionTreeBuilder.GenerateExpressionTreeField(
querySourceTypeFullName,
dtoName,
LambdaParameterName,
lambdaBodyBuilder.ToString(),
id
);
return fieldDecl;
}
/// <summary>
/// Generates the SelectExpr method code
/// </summary>
protected override string GenerateSelectExprMethod(
string dtoName,
DtoStructure structure,
InterceptableLocation location
)
{
// Use SourceType for the query source (not the DTO return type)
var querySourceTypeFullName = SourceType.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat
);
var returnTypePrefix = GetReturnTypePrefix();
var sb = new StringBuilder();
var id = GetUniqueId();
// Check if we should use pre-built expressions (only for IQueryable, not IEnumerable)
var usePrebuildExpression =
Configuration.UsePrebuildExpression && !IsEnumerableInvocation();
sb.AppendLine(GenerateMethodHeaderPart(dtoName, location));
// Determine if we have capture parameters
var hasCapture = CaptureArgumentExpression != null && CaptureArgumentType != null;
if (usePrebuildExpression && !hasCapture)
{
// Get the field name (we need to generate the same hash as in GenerateStaticFields)
var hash = HashUtility.GenerateSha256Hash(id).Substring(0, 8);
var fieldName = $"_cachedExpression_{hash}";
// Use the cached expression directly (no initialization needed, it's done in the field declaration)
sb.AppendLine(
$$"""
public static {{returnTypePrefix}}<TResult> SelectExpr_{{id}}<TIn, TResult>(
this {{returnTypePrefix}}<TIn> query, Func<TIn, TResult> selector)
{
return query.Provider.CreateQuery<TResult>(
Expression.Call(null, _methodInfo_{{id}}, query.Expression, _unaryExpression_{{id}}));
}
private static readonly UnaryExpression _unaryExpression_{{id}} = Expression.Quote({{fieldName}});
private static readonly System.Reflection.MethodInfo _methodInfo_{{id}} = new Func<
IQueryable<{{querySourceTypeFullName}}>,
Expression<Func<{{querySourceTypeFullName}}, {{dtoName}}>>,
IQueryable<{{dtoName}}>>(Queryable.Select).Method;
"""
);
return sb.ToString();
}
if (hasCapture)
{
// Generate method with capture parameter that creates 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}<{querySourceTypeFullName}>;"
);
// For anonymous types, use dynamic to extract properties as closure variables
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;");
}
// Note: Pre-built expressions don't work well with captures because the closure
// variables would be captured at compile time, not at runtime. So we disable
// pre-built expressions when captures are used.
sb.AppendLine(
$" var converted = matchedQuery.Select({LambdaParameterName} => new {dtoName}"
);
}
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}<{querySourceTypeFullName}>;"
);
sb.AppendLine(
$" var converted = matchedQuery.Select({LambdaParameterName} => new {dtoName}"
);
}
sb.AppendLine($" {{");
// Generate property assignments using GeneratePropertyAssignment to properly handle null-conditional operators
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("}");
return sb.ToString();
}
}