@@ -88,20 +88,12 @@ private static Result<Expression> BuildPropertyExpression(ParameterExpression pa
8888 var nullCheck = Expression . NotEqual ( current , Expression . Constant ( null ) ) ;
8989 var propertyAccess = Expression . Property ( current , segment ) ;
9090
91- // For the final segment, we'll handle null checks in the comparison
92- if ( segment == segments . Last ( ) )
93- {
94- current = propertyAccess ;
95- }
96- else
97- {
98- // For intermediate segments, we need to ensure they're not null
99- current = Expression . Condition (
100- nullCheck ,
101- propertyAccess ,
102- Expression . Constant ( null , propertyInfo . PropertyType )
103- ) ;
104- }
91+ // Create null-safe navigation for all segments
92+ current = Expression . Condition (
93+ nullCheck ,
94+ propertyAccess ,
95+ Expression . Constant ( null , propertyInfo . PropertyType )
96+ ) ;
10597 }
10698 else
10799 {
@@ -223,15 +215,92 @@ private static Result<Expression> CreatePropertyPathComparison(Expression proper
223215
224216 private static Expression CreateNullCheckExpression ( Expression property , List < string > segments )
225217 {
226- // Work backwards from the property expression to create null checks for each level
227- // For now, we'll use a simple approach - if any level is null, the comparison is false
218+ // For property paths, we need to ensure that all intermediate reference types are not null
219+ // The BuildPropertyExpression already handles null-safe navigation with conditional expressions,
220+ // but we need to create an additional check for the final comparison
221+
222+ if ( segments . Count <= 1 )
223+ {
224+ // Single property, no intermediate null checks needed
225+ return Expression . Constant ( true ) ;
226+ }
227+
228+ // For a path like "manager.firstName", we need to check that "manager" is not null
229+ // We need to traverse the property path and create null checks for each reference type level
230+
231+ var parameterExpression = GetParameterFromProperty ( property ) ;
232+ if ( parameterExpression == null )
233+ {
234+ return Expression . Constant ( true ) ;
235+ }
236+
237+ Expression current = parameterExpression ;
238+ Expression nullCheckExpression = null ;
239+
240+ // Check all segments except the last one (since the last one is handled in the comparison)
241+ for ( int i = 0 ; i < segments . Count - 1 ; i ++ )
242+ {
243+ var segment = segments [ i ] ;
244+ var propertyInfo = current . Type . GetProperty ( segment ) ;
245+
246+ if ( propertyInfo != null )
247+ {
248+ var propertyAccess = Expression . Property ( current , segment ) ;
249+
250+ // Only add null checks for reference types (not value types)
251+ if ( ! propertyInfo . PropertyType . IsValueType )
252+ {
253+ var nullCheck = Expression . NotEqual ( propertyAccess , Expression . Constant ( null ) ) ;
254+
255+ if ( nullCheckExpression == null )
256+ {
257+ nullCheckExpression = nullCheck ;
258+ }
259+ else
260+ {
261+ nullCheckExpression = Expression . AndAlso ( nullCheckExpression , nullCheck ) ;
262+ }
263+ }
264+
265+ current = propertyAccess ;
266+ }
267+ }
228268
229- // This is a simplified implementation. In a more robust version, you'd want to
230- // traverse the expression tree and create proper null checks for each level.
231- // For the current implementation, we'll rely on the property mapping and expression building
232- // to handle the null safety.
269+ return nullCheckExpression ?? Expression . Constant ( true ) ;
270+ }
233271
234- return Expression . Constant ( true ) ; // Placeholder - will be enhanced based on testing
272+ private static ParameterExpression GetParameterFromProperty ( Expression expression )
273+ {
274+ // Traverse the expression tree to find the root parameter
275+ while ( expression != null )
276+ {
277+ if ( expression is ParameterExpression parameter )
278+ {
279+ return parameter ;
280+ }
281+ else if ( expression is MemberExpression memberExpression )
282+ {
283+ expression = memberExpression . Expression ;
284+ }
285+ else if ( expression is ConditionalExpression conditionalExpression )
286+ {
287+ // Handle the case where we have conditional expressions from BuildPropertyExpression
288+ expression = conditionalExpression . Test ;
289+ if ( expression is BinaryExpression binaryExpression && binaryExpression . Left is MemberExpression memberExpr )
290+ {
291+ expression = memberExpr . Expression ;
292+ }
293+ else
294+ {
295+ break ;
296+ }
297+ }
298+ else
299+ {
300+ break ;
301+ }
302+ }
303+ return null ;
235304 }
236305
237306 private static Result < Expression > CreateStandardComparison ( Expression property , ConstantExpression value , string operatorKeyword , QueryExpression leftExpression )
0 commit comments