@@ -11,6 +11,19 @@ namespace NpgsqlRest;
1111 FrozenDictionary < string , NpgsqlRestMetadataEntry > overloads ,
1212 bool hasStreamingEvents ) ;
1313
14+ public class NpgsqlRestMetadataEntry
15+ {
16+ internal NpgsqlRestMetadataEntry ( RoutineEndpoint endpoint , IRoutineSourceParameterFormatter formatter , string key )
17+ {
18+ Endpoint = endpoint ;
19+ Formatter = formatter ;
20+ Key = key ;
21+ }
22+ public RoutineEndpoint Endpoint { get ; }
23+ public IRoutineSourceParameterFormatter Formatter { get ; }
24+ public string Key { get ; }
25+ }
26+
1427public static class NpgsqlRestBuilder
1528{
1629 public static IApplicationBuilder UseNpgsqlRest ( this WebApplication builder , NpgsqlRestOptions options )
@@ -31,8 +44,11 @@ public static IApplicationBuilder UseNpgsqlRest(this WebApplication builder, Npg
3144 var factory = builder . Services . GetRequiredService < ILoggerFactory > ( ) ;
3245 ILogger ? logger = factory . CreateLogger ( options . LoggerName ?? typeof ( NpgsqlRestBuilder ) . Namespace ?? "NpgsqlRest" ) ;
3346
34- var ( entries , overloads , hasStreamingEvents ) =
35- NpgsqlRestBuilder . Build ( logger , builder ) ;
47+ var (
48+ entries ,
49+ overloads ,
50+ hasStreamingEvents
51+ ) = Build ( logger , builder ) ;
3652 if ( entries . Count == 0 )
3753 {
3854 return builder ;
@@ -46,20 +62,22 @@ public static IApplicationBuilder UseNpgsqlRest(this WebApplication builder, Npg
4662 foreach ( var entry in entries )
4763 {
4864 var handler = new NpgsqlRestEndpoint ( entry , overloads , logger ) ;
49- var routeBuilder = builder . MapMethods ( entry . Endpoint . Path , [ entry . Endpoint . Method . ToString ( ) ] , handler . InvokeAsync ) ;
50-
65+ var endpoint = entry . Endpoint ;
66+ var methodStr = endpoint . Method . ToString ( ) ;
67+ var routeBuilder = builder . MapMethods ( endpoint . Path , [ methodStr ] , handler . InvokeAsync ) ;
68+
5169 if ( options . RouteHandlerCreated is not null )
5270 {
53- options . RouteHandlerCreated ( routeBuilder , entry . Endpoint ) ;
71+ options . RouteHandlerCreated ( routeBuilder , endpoint ) ;
5472 }
55-
73+
5674 if ( options . LogEndpointCreatedInfo )
5775 {
58- var urlInfo = string . Concat ( entry . Endpoint . Method , " " , entry . Endpoint . Path ) ;
76+ var urlInfo = string . Concat ( methodStr , " " , endpoint . Path ) ;
5977 logger ? . EndpointCreated ( urlInfo ) ;
60- if ( entry . Endpoint . InfoEventsStreamingPath is not null )
78+ if ( endpoint . InfoEventsStreamingPath is not null )
6179 {
62- logger ? . EndpointInfoStreamingPath ( urlInfo , entry . Endpoint . InfoEventsStreamingPath ) ;
80+ logger ? . EndpointInfoStreamingPath ( urlInfo , endpoint . InfoEventsStreamingPath ) ;
6381 }
6482 }
6583 }
@@ -71,8 +89,9 @@ public static IApplicationBuilder UseNpgsqlRest(this WebApplication builder, Npg
7189
7290 private static Metadata Build ( ILogger ? logger , IApplicationBuilder ? builder )
7391 {
74- Dictionary < string , NpgsqlRestMetadataEntry > lookup = [ ] ;
75- Dictionary < string , NpgsqlRestMetadataEntry > overloads = [ ] ;
92+ // Pre-size dictionaries with reasonable capacity to reduce allocations
93+ Dictionary < string , NpgsqlRestMetadataEntry > lookup = new ( capacity : 128 ) ;
94+ Dictionary < string , NpgsqlRestMetadataEntry > overloads = new ( capacity : 16 ) ;
7695
7796 // Create default upload handlers from upload handler options
7897 Options . UploadOptions . UploadHandlers ??= Options . UploadOptions . CreateUploadHandlers ( ) ;
@@ -114,28 +133,27 @@ private static Metadata Build(ILogger? logger, IApplicationBuilder? builder)
114133
115134 foreach ( var ( routine , formatter ) in source . Read ( builder ? . ApplicationServices , defaultStrategy , logger ) )
116135 {
117- RoutineEndpoint endpoint = DefaultEndpoint . Create ( routine , logger ) ! ;
136+ RoutineEndpoint ? endpoint = DefaultEndpoint . Create ( routine , logger ) ;
118137
119138 if ( endpoint is null )
120139 {
121140 continue ;
122141 }
123-
142+
124143 if ( Options . EndpointCreated is not null )
125144 {
126145 Options . EndpointCreated ( endpoint ) ;
146+ if ( endpoint is null )
147+ {
148+ continue ;
149+ }
127150 }
128151
129- if ( endpoint is null )
130- {
131- continue ;
132- }
133-
134152 if ( defaultStrategy is not null && endpoint . RetryStrategy is null )
135153 {
136154 endpoint . RetryStrategy = defaultStrategy ;
137155 }
138-
156+
139157 if ( endpoint . Path . Length == 0 )
140158 {
141159 throw new ArgumentException ( $ "URL path for URL { endpoint . Path } , routine { routine . Name } is empty.") ;
@@ -146,18 +164,20 @@ private static Metadata Build(ILogger? logger, IApplicationBuilder? builder)
146164 throw new ArgumentException ( $ "URL path for URL { endpoint . Path } , routine { routine . Name } length exceeds { MaxPathLength } characters.") ;
147165 }
148166
167+ // Cache method string to avoid repeated ToString() calls
149168 var method = endpoint . Method . ToString ( ) ;
150169 if ( endpoint . HasBodyParameter is true && endpoint . RequestParamType == RequestParamType . BodyJson )
151170 {
152171 endpoint . RequestParamType = RequestParamType . QueryString ;
153- logger ? . EndpointTypeChangedBodyParam ( method , endpoint . Path , endpoint ! . BodyParameterName ?? "" ) ;
172+ logger ? . EndpointTypeChangedBodyParam ( method , endpoint . Path , endpoint . BodyParameterName ?? "" ) ;
154173 }
155174 if ( endpoint . Upload is true )
156175 {
157176 if ( endpoint . Method != Method . POST )
158177 {
159178 logger ? . EndpointMethodChangedUpload ( method , endpoint . Path , Method . POST . ToString ( ) ) ;
160179 endpoint . Method = Method . POST ;
180+ method = "POST" ; // Update cached string
161181 }
162182 if ( endpoint . RequestParamType == RequestParamType . BodyJson )
163183 {
@@ -166,8 +186,8 @@ private static Metadata Build(ILogger? logger, IApplicationBuilder? builder)
166186 }
167187 }
168188
169- var key = string . Concat ( method , endpoint ? . Path ) ;
170- var value = new NpgsqlRestMetadataEntry ( endpoint ! , formatter , key ) ;
189+ var key = string . Concat ( method , endpoint . Path ) ;
190+ var value = new NpgsqlRestMetadataEntry ( endpoint , formatter , key ) ;
171191 if ( lookup . TryGetValue ( key , out var existing ) )
172192 {
173193 overloads [ string . Concat ( key , existing . Endpoint . Routine . ParamCount ) ] = existing ;
@@ -196,40 +216,36 @@ private static Metadata Build(ILogger? logger, IApplicationBuilder? builder)
196216 {
197217 foreach ( var handler in Options . EndpointCreateHandlers )
198218 {
199- handler . Handle ( endpoint ! ) ;
219+ handler . Handle ( endpoint ) ;
200220 }
201221 }
202222
203- if ( endpoint ? . InfoEventsStreamingPath is not null )
223+ if ( endpoint . InfoEventsStreamingPath is not null )
204224 {
205225 if ( endpoint . InfoEventsStreamingPath . StartsWith ( endpoint . Path ) is false )
206226 {
207- endpoint . InfoEventsStreamingPath = string . Concat (
208- endpoint . Path . EndsWith ( '/' ) ? endpoint . Path [ ..^ 1 ] : endpoint . Path , "/" ,
209- endpoint . InfoEventsStreamingPath . StartsWith ( '/' ) ? endpoint . InfoEventsStreamingPath [ 1 ..] : endpoint . InfoEventsStreamingPath ) ;
227+ // Optimize path concatenation
228+ var basePath = endpoint . Path . EndsWith ( '/' ) ? endpoint . Path [ ..^ 1 ] : endpoint . Path ;
229+ var streamPath = endpoint . InfoEventsStreamingPath . StartsWith ( '/' )
230+ ? endpoint . InfoEventsStreamingPath [ 1 ..]
231+ : endpoint . InfoEventsStreamingPath ;
232+ endpoint . InfoEventsStreamingPath = string . Concat ( basePath , "/" , streamPath ) ;
210233 }
211234
212235 NpgsqlRestNoticeEventSource . Paths . Add ( endpoint . InfoEventsStreamingPath ) ;
213-
214- if ( hasStreamingEvents is false )
215- {
216- hasStreamingEvents = true ;
217- }
236+ hasStreamingEvents = true ;
218237 }
219-
220- if ( endpoint ? . Login is true )
238+
239+ if ( endpoint . Login is true )
221240 {
222- if ( hasLogin is false )
223- {
224- hasLogin = true ;
225- }
241+ hasLogin = true ;
226242 if ( routine . IsVoid is true || routine . ReturnsUnnamedSet is true )
227243 {
228244 throw new ArgumentException ( $ "{ routine . Type . ToString ( ) . ToLowerInvariant ( ) } { routine . Schema } .{ routine . Name } is marked as login and it can't be void or returning unnamed data sets.") ;
229245 }
230246 }
231247
232- if ( endpoint ? . Cached is true && hasCachedRoutine is false )
248+ if ( endpoint . Cached is true && hasCachedRoutine is false )
233249 {
234250 hasCachedRoutine = true ;
235251 }
@@ -264,19 +280,41 @@ private static Metadata Build(ILogger? logger, IApplicationBuilder? builder)
264280 Options . UploadOptions . DefaultUploadHandler ) ;
265281 }
266282
267- var entries = lookup . Values . ToList ( ) ;
283+ // Avoid multiple enumerations by creating the list once
284+ var entries = new List < NpgsqlRestMetadataEntry > ( lookup . Values ) ;
285+
286+ // Create array once if needed by callbacks or handlers
287+ RoutineEndpoint [ ] ? endpointsArray = null ;
288+ bool arrayPopulated = false ;
289+
268290 if ( Options . EndpointsCreated is not null )
269291 {
270- Options . EndpointsCreated ( [ .. entries . Select ( x => x . Endpoint ) ] ) ;
292+ endpointsArray = new RoutineEndpoint [ entries . Count ] ;
293+ for ( int i = 0 ; i < entries . Count ; i ++ )
294+ {
295+ endpointsArray [ i ] = entries [ i ] . Endpoint ;
296+ }
297+ arrayPopulated = true ;
298+ Options . EndpointsCreated ( endpointsArray ) ;
271299 }
272300
273301 if ( builder is not null )
274302 {
275- RoutineEndpoint [ ] ? array = null ;
276303 foreach ( var handler in Options . EndpointCreateHandlers )
277304 {
278- array ??= [ .. entries . Select ( x => x . Endpoint ) ] ;
279- handler . Cleanup ( array ) ;
305+ if ( endpointsArray is null )
306+ {
307+ endpointsArray = new RoutineEndpoint [ entries . Count ] ;
308+ }
309+ if ( ! arrayPopulated )
310+ {
311+ for ( int i = 0 ; i < entries . Count ; i ++ )
312+ {
313+ endpointsArray [ i ] = entries [ i ] . Endpoint ;
314+ }
315+ arrayPopulated = true ;
316+ }
317+ handler . Cleanup ( endpointsArray ) ;
280318 handler . Cleanup ( ) ;
281319 }
282320 }
0 commit comments