Skip to content

Commit d662d32

Browse files
pdevito3claude
andcommitted
fix: handle null expressions in GetFullPropertyPath for complex conditionals
Previously, complex conditional expressions with null values would throw: System.ArgumentNullException: Value cannot be null. (Parameter 'expression') This occurred when using conditional expressions like: x.DueDate.HasValue ? (calculation) : (int?)null The fix adds comprehensive null checking throughout GetFullPropertyPath to: - Handle null operands in Convert/Cast expressions - Handle null arguments in method calls, constructors, and invocations - Handle null expressions in member access, conditionals, and binary operations - Return appropriate string representations ("null") for null values Changes made: - Added null checks in Convert, MemberAccess, Binary, Conditional expressions - Added null checks for method call arguments and objects - Added null checks for New expression arguments and constructors - Added null checks for Invoke expression arguments - All null expressions now return "null" instead of throwing Test added to verify complex conditional expressions with nullable types no longer throw ArgumentNullException and are handled gracefully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2f0051a commit d662d32

File tree

2 files changed

+62
-14
lines changed

2 files changed

+62
-14
lines changed

QueryKit.IntegrationTests/Tests/DatabaseFilteringTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3796,4 +3796,47 @@ public async Task can_filter_with_derived_property_containing_complex_method_cal
37963796
// Note: The actual filtering logic may need separate investigation
37973797
}
37983798

3799+
[Fact]
3800+
public async Task can_filter_with_derived_property_containing_complex_conditional_expression()
3801+
{
3802+
// Arrange
3803+
var testingServiceScope = new TestingServiceScope();
3804+
var uniqueId = Guid.NewGuid().ToString();
3805+
3806+
var futureDate = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(10));
3807+
var pastDate = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-5));
3808+
3809+
var fakePersonOne = new FakeTestingPersonBuilder()
3810+
.WithFirstName($"Future_{uniqueId}")
3811+
.WithDate(futureDate)
3812+
.Build();
3813+
var fakePersonTwo = new FakeTestingPersonBuilder()
3814+
.WithFirstName($"Past_{uniqueId}")
3815+
.WithDate(pastDate)
3816+
.Build();
3817+
var fakePersonThree = new FakeTestingPersonBuilder()
3818+
.WithFirstName($"NoDate_{uniqueId}")
3819+
.WithDate(null)
3820+
.Build();
3821+
3822+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo, fakePersonThree);
3823+
3824+
// This should not throw "Value cannot be null" anymore
3825+
Action act = () =>
3826+
{
3827+
var config = new QueryKitConfiguration(config =>
3828+
{
3829+
config.DerivedProperty<TestingPerson>(x =>
3830+
x.Date.HasValue
3831+
? (DateOnly.FromDateTime(DateTime.UtcNow).ToDateTime(TimeOnly.MinValue) -
3832+
x.Date.Value.ToDateTime(TimeOnly.MinValue)).Days
3833+
: (int?)null
3834+
).HasQueryName("daysUntilDue");
3835+
});
3836+
};
3837+
3838+
// Assert - Should not throw ArgumentNullException
3839+
act.Should().NotThrow<ArgumentNullException>("null values in expressions should be handled properly");
3840+
}
3841+
37993842
}

QueryKit/QueryKitPropertyMappings.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private static string GetFullPropertyPath(Expression? expression)
174174
var argumentsList = new List<string>();
175175
foreach (var arg in call.Arguments)
176176
{
177-
argumentsList.Add(GetFullPropertyPath(arg));
177+
argumentsList.Add(arg != null ? GetFullPropertyPath(arg) : "null");
178178
}
179179
var argumentsString = string.Join(", ", argumentsList);
180180

@@ -195,12 +195,14 @@ private static string GetFullPropertyPath(Expression? expression)
195195
return GetFullPropertyPath(lambda.Body);
196196
case ExpressionType.Convert:
197197
var unary = (UnaryExpression)expression;
198-
return GetFullPropertyPath(unary.Operand);
198+
return unary.Operand != null ? GetFullPropertyPath(unary.Operand) : "null";
199199
case ExpressionType.MemberAccess:
200200
var memberExpression = (MemberExpression)expression;
201-
return memberExpression?.Expression?.NodeType == ExpressionType.Parameter
202-
? memberExpression.Member.Name
203-
: $"{GetFullPropertyPath(memberExpression?.Expression)}.{memberExpression?.Member?.Name}";
201+
if (memberExpression?.Expression == null)
202+
return memberExpression?.Member?.Name ?? "null";
203+
return memberExpression.Expression.NodeType == ExpressionType.Parameter
204+
? memberExpression.Member.Name
205+
: $"{GetFullPropertyPath(memberExpression.Expression)}.{memberExpression.Member?.Name}";
204206
case ExpressionType.Add:
205207
case ExpressionType.Subtract:
206208
case ExpressionType.Multiply:
@@ -220,8 +222,8 @@ private static string GetFullPropertyPath(Expression? expression)
220222
case ExpressionType.MultiplyChecked:
221223
case ExpressionType.SubtractChecked:
222224
var binary = (BinaryExpression)expression;
223-
var left = GetFullPropertyPath(binary.Left);
224-
var right = GetFullPropertyPath(binary.Right);
225+
var left = binary.Left != null ? GetFullPropertyPath(binary.Left) : "null";
226+
var right = binary.Right != null ? GetFullPropertyPath(binary.Right) : "null";
225227
var op = GetOperator(binary.NodeType);
226228
return $"{left} {op} {right}";
227229
case ExpressionType.Constant:
@@ -230,20 +232,23 @@ private static string GetFullPropertyPath(Expression? expression)
230232

231233
case ExpressionType.Conditional:
232234
var conditional = (ConditionalExpression)expression;
233-
var test = GetFullPropertyPath(conditional.Test);
234-
var ifTrue = GetFullPropertyPath(conditional.IfTrue);
235-
var ifFalse = GetFullPropertyPath(conditional.IfFalse);
235+
var test = conditional.Test != null ? GetFullPropertyPath(conditional.Test) : "null";
236+
var ifTrue = conditional.IfTrue != null ? GetFullPropertyPath(conditional.IfTrue) : "null";
237+
var ifFalse = conditional.IfFalse != null ? GetFullPropertyPath(conditional.IfFalse) : "null";
236238
return $"({test}) ? ({ifTrue}) : ({ifFalse})";
237239

238240
case ExpressionType.New:
239241
var newExpression = (NewExpression)expression;
240-
var arguments = newExpression.Arguments.Select(GetFullPropertyPath);
241-
return $"{newExpression.Constructor.DeclaringType.Name}({string.Join(", ", arguments)})";
242+
var arguments = newExpression.Arguments.Select(arg => arg != null ? GetFullPropertyPath(arg) : "null");
243+
return newExpression.Constructor != null
244+
? $"{newExpression.Constructor.DeclaringType.Name}({string.Join(", ", arguments)})"
245+
: $"new({string.Join(", ", arguments)})";
242246

243247
case ExpressionType.Invoke:
244248
var invocation = (InvocationExpression)expression;
245-
var invocationArguments = invocation.Arguments.Select(GetFullPropertyPath);
246-
return $"{GetFullPropertyPath(invocation.Expression)}({string.Join(", ", invocationArguments)})";
249+
var invocationArguments = invocation.Arguments.Select(arg => arg != null ? GetFullPropertyPath(arg) : "null");
250+
var invocationExpr = invocation.Expression != null ? GetFullPropertyPath(invocation.Expression) : "null";
251+
return $"{invocationExpr}({string.Join(", ", invocationArguments)})";
247252

248253
case ExpressionType.ListInit:
249254
var listInit = (ListInitExpression)expression;

0 commit comments

Comments
 (0)