@@ -24,7 +24,9 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
2424 {
2525 var visitedExpression = base . VisitBinary ( binaryExpression ) ;
2626
27- return TryOptimizeConditionalEquality ( visitedExpression ) ?? visitedExpression ;
27+ return TryOptimizeConditionalEquality ( visitedExpression )
28+ ?? ProcessNullCheck ( visitedExpression )
29+ ?? visitedExpression ;
2830 }
2931
3032 /// <summary>
@@ -39,14 +41,14 @@ protected override Expression VisitConditional(ConditionalExpression conditional
3941
4042 if ( test is BinaryExpression { NodeType : ExpressionType . Equal or ExpressionType . NotEqual } binaryTest )
4143 {
42- var isLeftNullConstant = IsNullConstant ( binaryTest . Left ) ;
43- var isRightNullConstant = IsNullConstant ( binaryTest . Right ) ;
44+ var isLeftNullConstant = binaryTest . Left is ConstantExpression { Value : null } ;
45+ var isRightNullConstant = binaryTest . Right is ConstantExpression { Value : null } ;
4446
4547 if ( ( isLeftNullConstant == isRightNullConstant )
4648 || ( binaryTest . NodeType == ExpressionType . Equal
47- && ! IsNullConstant ( conditionalExpression . IfTrue ) )
49+ && conditionalExpression . IfTrue is not ConstantExpression { Value : null } )
4850 || ( binaryTest . NodeType == ExpressionType . NotEqual
49- && ! IsNullConstant ( conditionalExpression . IfFalse ) ) )
51+ && conditionalExpression . IfFalse is not ConstantExpression { Value : null } ) )
5052 {
5153 return conditionalExpression ;
5254 }
@@ -75,7 +77,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional
7577 return base . VisitConditional ( conditionalExpression ) ;
7678 }
7779
78- private static Expression ? TryOptimizeConditionalEquality ( Expression expression )
80+ private static BinaryExpression ? TryOptimizeConditionalEquality ( Expression expression )
7981 {
8082 // Simplify (a ? b : null) == null => !a || b == null
8183 // Simplify (a ? null : b) == null => a || b == null
@@ -115,6 +117,47 @@ protected override Expression VisitConditional(ConditionalExpression conditional
115117 return null ;
116118 }
117119
120+ private static ConstantExpression ? ProcessNullCheck ( Expression expression )
121+ {
122+ // Optimize IQueryable/DbSet null checks for expressions that are guaranteed to be non-null:
123+ // * queryableMethodCall != null => true
124+ // * queryableMethodCall == null => false
125+ // This applies to method calls and member accesses that produce IQueryable results, which can never be null.
126+ // We do NOT optimize null checks for parameters/variables, as they could legitimately be null.
127+ if ( expression is BinaryExpression
128+ {
129+ NodeType : ExpressionType . Equal or ExpressionType . NotEqual
130+ } binaryExpression )
131+ {
132+ var isLeftNull = binaryExpression . Left is ConstantExpression { Value : null } ;
133+ var isRightNull = binaryExpression . Right is ConstantExpression { Value : null } ;
134+
135+ if ( isLeftNull || isRightNull )
136+ {
137+ // null == null => true
138+ // null != null => false
139+ if ( isLeftNull && isRightNull )
140+ {
141+ return Expression . Constant ( binaryExpression . NodeType is ExpressionType . Equal ) ;
142+ }
143+
144+ var nonNullExpression = isLeftNull ? binaryExpression . Right : binaryExpression . Left ;
145+
146+ // Only optimize if the expression is a query operation that cannot be null
147+ // (method call returning IQueryable, DbSet property access, or QueryRootExpression)
148+ if ( nonNullExpression . Type . IsAssignableTo ( typeof ( IQueryable ) )
149+ && ( nonNullExpression is MethodCallExpression or QueryRootExpression ) )
150+ // || (nonNullExpression is MemberExpression { Member.DeclaringType: not null } memberExpression
151+ // && memberExpression.Member.DeclaringType.IsAssignableTo(typeof(DbContext)))))
152+ {
153+ return Expression . Constant ( binaryExpression . NodeType is ExpressionType . NotEqual ) ;
154+ }
155+ }
156+ }
157+
158+ return null ;
159+ }
160+
118161 private sealed class NullSafeAccessVerifyingExpressionVisitor : ExpressionVisitor
119162 {
120163 private readonly ISet < Expression > _nullSafeAccesses = new HashSet < Expression > ( ExpressionEqualityComparer . Instance ) ;
@@ -158,7 +201,4 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression)
158201 return unaryExpression ;
159202 }
160203 }
161-
162- private static bool IsNullConstant ( Expression expression )
163- => expression is ConstantExpression { Value : null } ;
164204}
0 commit comments