From d2ad26f07883c0158bc4b540acc5e3d8f85c735d Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 4 Feb 2026 02:52:06 +0200 Subject: [PATCH 01/14] Refactor projection binding to avoid redundant Convert nodes and improve NativeAOT compatibility --- ...osmosProjectionBindingExpressionVisitor.cs | 4 ++-- ...ionalProjectionBindingExpressionVisitor.cs | 22 +++++++++---------- .../Query/ProjectionBindingExpression.cs | 16 ++++++++++++++ .../Query/NorthwindSqlQuerySqlServerTest.cs | 22 ++++++++++++++++++- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs index 72abbf75413..3bc3692b658 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs @@ -136,7 +136,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio } return new ProjectionBindingExpression( - _selectExpression, _selectExpression.AddToProjection(translation), expression.Type.MakeNullable()); + _selectExpression, _selectExpression.AddToProjection(translation), expression.Type); } else { @@ -148,7 +148,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _projectionMapping[_projectionMembers.Peek()] = translation; - return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type.MakeNullable()); + return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type); } } diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 3614afaf165..363178a93c5 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -124,7 +124,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio return _selectExpression.GetProjection(projectionBindingExpression) switch { StructuralTypeProjectionExpression projection => AddClientProjection(projection, typeof(ValueBuffer)), - SqlExpression mappedSqlExpression => AddClientProjection(mappedSqlExpression, expression.Type.MakeNullable()), + SqlExpression mappedSqlExpression => AddClientProjection(mappedSqlExpression, expression.Type), _ => throw new InvalidOperationException(CoreStrings.TranslationFailed(projectionBindingExpression.Print())) }; @@ -235,7 +235,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio case SqlExpression sqlExpression: _projectionMapping[_projectionMembers.Peek()] = sqlExpression; return new ProjectionBindingExpression( - _selectExpression, _projectionMembers.Peek(), expression.Type.MakeNullable()); + _selectExpression, _projectionMembers.Peek(), expression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently @@ -679,18 +679,16 @@ private static Expression MatchTypes(Expression expression, Type targetType) if (targetType != expression.Type && targetType.TryGetElementType(typeof(IQueryable<>)) == null) { - Check.DebugAssert( - targetType.MakeNullable() == expression.Type, - $"expression has type {expression.Type.Name}, but must be nullable over {targetType.Name}"); - - return expression switch + if (expression is ProjectionBindingExpression projectionBindingExpression) { -#pragma warning disable EF1001 - RelationalStructuralTypeShaperExpression structuralShaper => structuralShaper.MakeClrTypeNonNullable(), -#pragma warning restore EF1001 + return projectionBindingExpression.UpdateType(targetType); + } - _ => Expression.Convert(expression, targetType), - }; + if (expression is RelationalStructuralTypeShaperExpression structuralShaper) + { + return structuralShaper.MakeClrTypeNonNullable(); + } + return Expression.Convert(expression, targetType); } return expression; diff --git a/src/EFCore/Query/ProjectionBindingExpression.cs b/src/EFCore/Query/ProjectionBindingExpression.cs index f85cc0e9504..5f44c9b2b71 100644 --- a/src/EFCore/Query/ProjectionBindingExpression.cs +++ b/src/EFCore/Query/ProjectionBindingExpression.cs @@ -90,6 +90,22 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) expressionPrinter.Append(Index.ToString()!); } } + /// + /// Creates a new instance of the class with a new type. + /// + /// The new clr type of value being read. + /// A new projection binding expression with the updated type. + public virtual ProjectionBindingExpression UpdateType(Type type) + { + if (type == Type) + { + return this; + } + + return Index != null + ? new ProjectionBindingExpression(QueryExpression, Index.Value, type) + : new ProjectionBindingExpression(QueryExpression, ProjectionMember!, type); + } /// public override bool Equals(object? obj) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs index a54443c7781..dfc1d855707 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSqlQuerySqlServerTest.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Data.SqlClient; - +using Microsoft.EntityFrameworkCore.TestModels.Northwind; namespace Microsoft.EntityFrameworkCore.Query; #nullable disable @@ -70,6 +70,26 @@ public override async Task SqlQuery_over_int_with_parameter(bool async) """); } + [ConditionalFact] + public virtual void Projection_binding_clean_up_non_nullable_value_type() + { + using var context = Fixture.CreateContext(); + + var query = context.Set() + .Select(c => c.CustomerID) + .ToQueryString(); + var result = context.Set().Select(c => c.CustomerID).ToList(); + Assert.NotEmpty(result); + } + + [ConditionalFact] + public virtual void Projection_binding_stays_nullable_for_nullable_types() + { + using var context = Fixture.CreateContext(); + var result = context.Set().Select(e => e.ReportsTo).ToList(); + + Assert.Contains(null, result); + } protected override DbParameter CreateDbParameter(string name, object value) => new SqlParameter { ParameterName = name, Value = value }; From cbe518dcfc91d66042a999dcb3bb20e6d45d95e5 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 4 Feb 2026 03:35:18 +0200 Subject: [PATCH 02/14] update Visit method in RelationalProjectionBindingExpressionVisitor class --- ...ionalProjectionBindingExpressionVisitor.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 363178a93c5..36d2e71fbec 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -124,7 +124,16 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio return _selectExpression.GetProjection(projectionBindingExpression) switch { StructuralTypeProjectionExpression projection => AddClientProjection(projection, typeof(ValueBuffer)), - SqlExpression mappedSqlExpression => AddClientProjection(mappedSqlExpression, expression.Type), + SqlExpression mappedSqlExpression => AddClientProjection( + mappedSqlExpression, + (mappedSqlExpression switch + { + ColumnExpression c => c.IsNullable, + SqlFunctionExpression f => f.IsNullable, + _ => mappedSqlExpression.Type.IsNullableType() + }) + ? expression.Type.MakeNullable() + : expression.Type), _ => throw new InvalidOperationException(CoreStrings.TranslationFailed(projectionBindingExpression.Print())) }; @@ -234,8 +243,19 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio { case SqlExpression sqlExpression: _projectionMapping[_projectionMembers.Peek()] = sqlExpression; + + // فحص ذكي للـ Nullability يغطي العمود والدالة وأي تعبير آخر + var isNullable = sqlExpression switch + { + ColumnExpression c => c.IsNullable, + SqlFunctionExpression f => f.IsNullable, + _ => sqlExpression.Type.IsNullableType() + }; + return new ProjectionBindingExpression( - _selectExpression, _projectionMembers.Peek(), expression.Type); + _selectExpression, + _projectionMembers.Peek(), + isNullable ? expression.Type.MakeNullable() : expression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently From 3c37b6fea069bb078d56374d7cacce7a921e78bf Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 4 Feb 2026 04:01:08 +0200 Subject: [PATCH 03/14] second update --- ...ionalProjectionBindingExpressionVisitor.cs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 36d2e71fbec..5f283be2f82 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -125,15 +125,15 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio { StructuralTypeProjectionExpression projection => AddClientProjection(projection, typeof(ValueBuffer)), SqlExpression mappedSqlExpression => AddClientProjection( - mappedSqlExpression, - (mappedSqlExpression switch - { - ColumnExpression c => c.IsNullable, - SqlFunctionExpression f => f.IsNullable, - _ => mappedSqlExpression.Type.IsNullableType() - }) - ? expression.Type.MakeNullable() - : expression.Type), + mappedSqlExpression, + (mappedSqlExpression switch + { + ColumnExpression c => c.IsNullable, + SqlFunctionExpression f => f.IsNullable, + _ => true + }) + ? expression.Type.MakeNullable() + : expression.Type), _ => throw new InvalidOperationException(CoreStrings.TranslationFailed(projectionBindingExpression.Print())) }; @@ -242,20 +242,19 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio switch (_sqlTranslator.TranslateProjection(expression)) { case SqlExpression sqlExpression: - _projectionMapping[_projectionMembers.Peek()] = sqlExpression; - - // فحص ذكي للـ Nullability يغطي العمود والدالة وأي تعبير آخر - var isNullable = sqlExpression switch - { - ColumnExpression c => c.IsNullable, - SqlFunctionExpression f => f.IsNullable, - _ => sqlExpression.Type.IsNullableType() - }; + _projectionMapping[_projectionMembers.Peek()] = sqlExpression; - return new ProjectionBindingExpression( - _selectExpression, - _projectionMembers.Peek(), - isNullable ? expression.Type.MakeNullable() : expression.Type); + var isNullable = sqlExpression switch + { + ColumnExpression c => c.IsNullable, + SqlFunctionExpression f => f.IsNullable, + _ => true + }; + + return new ProjectionBindingExpression( + _selectExpression, + _projectionMembers.Peek(), + isNullable ? expression.Type.MakeNullable() : expression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently From 1d9278863e72e79db6a3f0cf6399ca30d34887be Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 4 Feb 2026 04:34:34 +0200 Subject: [PATCH 04/14] solve errors --- ...ionalProjectionBindingExpressionVisitor.cs | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 5f283be2f82..4c0225aa474 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -120,22 +120,19 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio case ParameterExpression parameterExpression: throw new InvalidOperationException(CoreStrings.TranslationFailed(parameterExpression.Print())); - case ProjectionBindingExpression projectionBindingExpression: - return _selectExpression.GetProjection(projectionBindingExpression) switch - { - StructuralTypeProjectionExpression projection => AddClientProjection(projection, typeof(ValueBuffer)), - SqlExpression mappedSqlExpression => AddClientProjection( - mappedSqlExpression, - (mappedSqlExpression switch - { - ColumnExpression c => c.IsNullable, - SqlFunctionExpression f => f.IsNullable, - _ => true - }) - ? expression.Type.MakeNullable() - : expression.Type), - _ => throw new InvalidOperationException(CoreStrings.TranslationFailed(projectionBindingExpression.Print())) - }; + case SqlExpression mappedSqlExpression: + var isNullable = mappedSqlExpression switch + { + ColumnExpression c => c.IsNullable, + SqlFunctionExpression f => f.IsNullable, + _ => true + }; + + return AddClientProjection( + mappedSqlExpression, + isNullable && expression.Type.IsValueType && !expression.Type.IsNullableType() + ? expression.Type.MakeNullable() + : expression.Type); case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression: if (materializeCollectionNavigationExpression.Navigation.TargetEntityType.IsMappedToJson()) @@ -243,7 +240,6 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio { case SqlExpression sqlExpression: _projectionMapping[_projectionMembers.Peek()] = sqlExpression; - var isNullable = sqlExpression switch { ColumnExpression c => c.IsNullable, @@ -254,7 +250,9 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio return new ProjectionBindingExpression( _selectExpression, _projectionMembers.Peek(), - isNullable ? expression.Type.MakeNullable() : expression.Type); + isNullable && expression.Type.IsValueType && !expression.Type.IsNullableType() + ? expression.Type.MakeNullable() + : expression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently From 35e90e55f66f5dc070141ae9517f8e5b259602dc Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Tue, 10 Feb 2026 13:59:38 +0200 Subject: [PATCH 05/14] solve errors AtTimeZone --- .../RelationalProjectionBindingExpressionVisitor.cs | 2 ++ ...sionVisitor.ShaperProcessingExpressionVisitor.cs | 12 ++++++++++-- .../Query/SqlExpressions/AtTimeZoneExpression.cs | 13 +++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 4c0225aa474..95fa3a867c5 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -125,6 +125,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio { ColumnExpression c => c.IsNullable, SqlFunctionExpression f => f.IsNullable, + AtTimeZoneExpression a => a.IsNullable, _ => true }; @@ -244,6 +245,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio { ColumnExpression c => c.IsNullable, SqlFunctionExpression f => f.IsNullable, + AtTimeZoneExpression a => a.IsNullable, _ => true }; diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 054d07ba8b8..b9a42dd0358 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -2908,7 +2908,14 @@ private object GetProjectionIndex(ProjectionBindingExpression projectionBindingE => _selectExpression.GetProjection(projectionBindingExpression).GetConstantValue(); private static bool IsNullableProjection(ProjectionExpression projection) - => projection.Expression is not ColumnExpression column || column.IsNullable; + => projection.Expression switch + { + ColumnExpression column => column.IsNullable, + SqlFunctionExpression function => function.IsNullable, + AtTimeZoneExpression atTimeZone => atTimeZone.IsNullable, + JsonScalarExpression jsonScalar => jsonScalar.IsNullable, + _ => true + }; private Expression CreateGetValueExpression( ParameterExpression dbDataReader, @@ -2919,7 +2926,8 @@ private Expression CreateGetValueExpression( IPropertyBase? property = null) { Check.DebugAssert( - property != null || type.IsNullableType(), "Must read nullable value from database if property is not specified."); + property != null || !nullable || type.IsNullableType(), + "Must read nullable value from database if property is not specified and nullable is true."); var getMethod = typeMapping.GetDataReaderMethod(); diff --git a/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs index 33cf6bef843..beea640c54d 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/AtTimeZoneExpression.cs @@ -44,6 +44,19 @@ public AtTimeZoneExpression( /// public virtual SqlExpression TimeZone { get; } + /// + /// A bool value indicating if this SQL expression is nullable. + /// + public virtual bool IsNullable + => Operand switch + { + ColumnExpression c => c.IsNullable, + SqlFunctionExpression f => f.IsNullable, + JsonScalarExpression j => j.IsNullable, + AtTimeZoneExpression a => a.IsNullable, + _ => true + }; + /// protected override Expression VisitChildren(ExpressionVisitor visitor) { From e1ad49fd66e092a7151cb088b69a3f3a92662dbd Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Sun, 15 Feb 2026 19:47:03 +0200 Subject: [PATCH 06/14] attempt to fix errors --- ...sitor.ShaperProcessingExpressionVisitor.cs | 120 +++++++++++++++--- 1 file changed, 100 insertions(+), 20 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index b9a42dd0358..3b5a32032a8 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -782,7 +782,6 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } - // json entity collection at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, relatedStructuralType, @@ -809,44 +808,125 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP collectionResult.Type); } - case ProjectionBindingExpression projectionBindingExpression - when _inline: + case ProjectionBindingExpression projectionBindingExpression when _inline: { - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); + var projectionIndex = System.Convert.ToInt32(GetProjectionIndex(projectionBindingExpression)); var projection = _selectExpression.Projection[projectionIndex]; + var nullable = IsNullableProjection(projection); + var type = projectionBindingExpression.Type; + + var isJsonScalar = projection.Expression is JsonScalarExpression; + var readType = (type.IsValueType && (isJsonScalar || !type.IsNullableType())) ? typeof(object) : type; - return CreateGetValueExpression( + var getValueExpression = CreateGetValueExpression( _dataReaderParameter, projectionIndex, - IsNullableProjection(projection), + nullable, projection.Expression.TypeMapping!, - projectionBindingExpression.Type); + readType); + + if (type.IsValueType) + { + var isMemberAccess = projectionBindingExpression.ProjectionMember != null; + + if (!type.IsNullableType()) + { + return Condition( + Expression.Call( + _dataReaderParameter, + typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, + Constant(projectionIndex)), + isMemberAccess + ? Expression.Block( + Expression.Call(typeof(RelationalShapedQueryCompilingExpressionVisitor).GetMethod("ThrowInvalidOperationException", BindingFlags.NonPublic | BindingFlags.Static)!), + Expression.Default(type)) + : Expression.Default(type), + Convert(getValueExpression, type)); + } + if (isJsonScalar) + { + var underlyingType = Nullable.GetUnderlyingType(type)!; + return Condition( + Expression.Call( + _dataReaderParameter, + typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, + Constant(projectionIndex)), + Expression.Default(type), + Expression.Convert( + Expression.Convert(getValueExpression, underlyingType), + type)); + } + } + + return Convert(getValueExpression, type); } - case ProjectionBindingExpression projectionBindingExpression - when !_inline: + case ProjectionBindingExpression projectionBindingExpression when !_inline: { if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) { return accessor; } - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); + var projectionIndex = System.Convert.ToInt32(GetProjectionIndex(projectionBindingExpression)); var projection = _selectExpression.Projection[projectionIndex]; var nullable = IsNullableProjection(projection); + var type = projectionBindingExpression.Type; - var valueParameter = Parameter(projectionBindingExpression.Type, "value" + (_variables.Count + 1)); + var valueParameter = Parameter(type, "value" + (_variables.Count + 1)); _variables.Add(valueParameter); + var isJsonScalar = projection.Expression is JsonScalarExpression; + var readType = (type.IsValueType && (isJsonScalar || !type.IsNullableType())) ? typeof(object) : type; - _expressions.Add( - Assign( - valueParameter, - CreateGetValueExpression( - _dataReaderParameter, - projectionIndex, - nullable, - projection.Expression.TypeMapping!, - valueParameter.Type))); + Expression getValueExpression = CreateGetValueExpression( + _dataReaderParameter, + projectionIndex, + nullable, + projection.Expression.TypeMapping!, + readType); + + if (type.IsValueType) + { + var isMemberAccess = projectionBindingExpression.ProjectionMember != null; + + if (!type.IsNullableType()) + { + getValueExpression = Condition( + Expression.Call( + _dataReaderParameter, + typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, + Constant(projectionIndex)), + isMemberAccess + ? Expression.Block( + Expression.Call(typeof(RelationalShapedQueryCompilingExpressionVisitor).GetMethod("ThrowInvalidOperationException", BindingFlags.NonPublic | BindingFlags.Static)!), + Expression.Default(type)) + : Expression.Default(type), + Convert(getValueExpression, type)); + } + else if (isJsonScalar) + { + var underlyingType = Nullable.GetUnderlyingType(type)!; + getValueExpression = Condition( + Expression.Call( + _dataReaderParameter, + typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, + Constant(projectionIndex)), + Expression.Default(type), + Expression.Convert( + Expression.Convert(getValueExpression, underlyingType), + type)); + } + else + { + getValueExpression = Convert(getValueExpression, type); + } + } + else + { + getValueExpression = Convert(getValueExpression, type); + } + + _expressions.Add(Assign(valueParameter, getValueExpression)); if (_containsCollectionMaterialization) { From 8c8e12e4a66802017bc6f4f8b818a98c489035c6 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Sun, 15 Feb 2026 19:58:48 +0200 Subject: [PATCH 07/14] Fix invalid method calls , and Added JSON scalar type conversion --- ...xpressionVisitor.ShaperProcessingExpressionVisitor.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 3b5a32032a8..8b9a5b61fcb 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -782,6 +782,7 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } + // json entity collection at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, relatedStructuralType, @@ -838,12 +839,14 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP Constant(projectionIndex)), isMemberAccess ? Expression.Block( - Expression.Call(typeof(RelationalShapedQueryCompilingExpressionVisitor).GetMethod("ThrowInvalidOperationException", BindingFlags.NonPublic | BindingFlags.Static)!), + Expression.Throw(Expression.New(typeof(InvalidOperationException))), Expression.Default(type)) : Expression.Default(type), Convert(getValueExpression, type)); } - if (isJsonScalar) + + // For nullable value types from JSON scalars, we need proper numeric conversion + else if (isJsonScalar) { var underlyingType = Nullable.GetUnderlyingType(type)!; return Condition( @@ -898,7 +901,7 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP Constant(projectionIndex)), isMemberAccess ? Expression.Block( - Expression.Call(typeof(RelationalShapedQueryCompilingExpressionVisitor).GetMethod("ThrowInvalidOperationException", BindingFlags.NonPublic | BindingFlags.Static)!), + Expression.Throw(Expression.New(typeof(InvalidOperationException))), Expression.Default(type)) : Expression.Default(type), Convert(getValueExpression, type)); From 65996e74604d503102f942040eaeec9d5a459530 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Sun, 15 Feb 2026 20:51:11 +0200 Subject: [PATCH 08/14] attempt 3 --- ...sitor.ShaperProcessingExpressionVisitor.cs | 121 +++--------------- 1 file changed, 19 insertions(+), 102 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 8b9a5b61fcb..b9a42dd0358 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -809,127 +809,44 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP collectionResult.Type); } - case ProjectionBindingExpression projectionBindingExpression when _inline: + case ProjectionBindingExpression projectionBindingExpression + when _inline: { - var projectionIndex = System.Convert.ToInt32(GetProjectionIndex(projectionBindingExpression)); + var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); var projection = _selectExpression.Projection[projectionIndex]; - var nullable = IsNullableProjection(projection); - var type = projectionBindingExpression.Type; - - var isJsonScalar = projection.Expression is JsonScalarExpression; - var readType = (type.IsValueType && (isJsonScalar || !type.IsNullableType())) ? typeof(object) : type; - var getValueExpression = CreateGetValueExpression( + return CreateGetValueExpression( _dataReaderParameter, projectionIndex, - nullable, + IsNullableProjection(projection), projection.Expression.TypeMapping!, - readType); - - if (type.IsValueType) - { - var isMemberAccess = projectionBindingExpression.ProjectionMember != null; - - if (!type.IsNullableType()) - { - return Condition( - Expression.Call( - _dataReaderParameter, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, - Constant(projectionIndex)), - isMemberAccess - ? Expression.Block( - Expression.Throw(Expression.New(typeof(InvalidOperationException))), - Expression.Default(type)) - : Expression.Default(type), - Convert(getValueExpression, type)); - } - - // For nullable value types from JSON scalars, we need proper numeric conversion - else if (isJsonScalar) - { - var underlyingType = Nullable.GetUnderlyingType(type)!; - return Condition( - Expression.Call( - _dataReaderParameter, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, - Constant(projectionIndex)), - Expression.Default(type), - Expression.Convert( - Expression.Convert(getValueExpression, underlyingType), - type)); - } - } - - return Convert(getValueExpression, type); + projectionBindingExpression.Type); } - case ProjectionBindingExpression projectionBindingExpression when !_inline: + case ProjectionBindingExpression projectionBindingExpression + when !_inline: { if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) { return accessor; } - var projectionIndex = System.Convert.ToInt32(GetProjectionIndex(projectionBindingExpression)); + var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); var projection = _selectExpression.Projection[projectionIndex]; var nullable = IsNullableProjection(projection); - var type = projectionBindingExpression.Type; - var valueParameter = Parameter(type, "value" + (_variables.Count + 1)); + var valueParameter = Parameter(projectionBindingExpression.Type, "value" + (_variables.Count + 1)); _variables.Add(valueParameter); - var isJsonScalar = projection.Expression is JsonScalarExpression; - var readType = (type.IsValueType && (isJsonScalar || !type.IsNullableType())) ? typeof(object) : type; - - Expression getValueExpression = CreateGetValueExpression( - _dataReaderParameter, - projectionIndex, - nullable, - projection.Expression.TypeMapping!, - readType); - - if (type.IsValueType) - { - var isMemberAccess = projectionBindingExpression.ProjectionMember != null; - if (!type.IsNullableType()) - { - getValueExpression = Condition( - Expression.Call( - _dataReaderParameter, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, - Constant(projectionIndex)), - isMemberAccess - ? Expression.Block( - Expression.Throw(Expression.New(typeof(InvalidOperationException))), - Expression.Default(type)) - : Expression.Default(type), - Convert(getValueExpression, type)); - } - else if (isJsonScalar) - { - var underlyingType = Nullable.GetUnderlyingType(type)!; - getValueExpression = Condition( - Expression.Call( - _dataReaderParameter, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, - Constant(projectionIndex)), - Expression.Default(type), - Expression.Convert( - Expression.Convert(getValueExpression, underlyingType), - type)); - } - else - { - getValueExpression = Convert(getValueExpression, type); - } - } - else - { - getValueExpression = Convert(getValueExpression, type); - } - - _expressions.Add(Assign(valueParameter, getValueExpression)); + _expressions.Add( + Assign( + valueParameter, + CreateGetValueExpression( + _dataReaderParameter, + projectionIndex, + nullable, + projection.Expression.TypeMapping!, + valueParameter.Type))); if (_containsCollectionMaterialization) { From 729374d8543689af71f4d5126272e620a91cf802 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Mon, 16 Feb 2026 20:25:54 +0200 Subject: [PATCH 09/14] update cosmos --- .../CosmosProjectionBindingExpressionVisitor.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs index 59ec100f6a3..bfbf85ee5f0 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs @@ -793,12 +793,17 @@ private void VerifySelectExpression(ProjectionBindingExpression projectionBindin private static Expression MatchTypes(Expression expression, Type targetType) { - if (targetType != expression.Type - && targetType.TryGetSequenceType() == null) + if (targetType != expression.Type + && targetType.TryGetSequenceType() == null) { - Check.DebugAssert(targetType.MakeNullable() == expression.Type, "expression.Type must be nullable of targetType"); - - expression = Expression.Convert(expression, targetType); + if (expression is ProjectionBindingExpression projectionBindingExpression) + { + return projectionBindingExpression.UpdateType(targetType); + } + if (targetType.MakeNullable() == expression.Type) + { + expression = Expression.Convert(expression, targetType); + } } return expression; From a05d8c17f85ec3eae5b522030fed0f031e3a1d63 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Mon, 16 Feb 2026 21:02:50 +0200 Subject: [PATCH 10/14] attempt 3 --- ...ionalProjectionBindingExpressionVisitor.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 9056ed7fc6c..ea89ed06171 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -129,11 +129,14 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _ => true }; + // Only mark as nullable if the target type can actually be nullable + var shouldBeNullable = isNullable + && expression.Type.IsValueType + && !expression.Type.IsNullableType(); + return AddClientProjection( mappedSqlExpression, - isNullable && expression.Type.IsValueType && !expression.Type.IsNullableType() - ? expression.Type.MakeNullable() - : expression.Type); + shouldBeNullable ? expression.Type.MakeNullable() : expression.Type); case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression: if (materializeCollectionNavigationExpression.Navigation.TargetEntityType.IsMappedToJson()) @@ -247,9 +250,8 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio { switch (_sqlTranslator.TranslateProjection(expression)) { - case SqlExpression sqlExpression: - _projectionMapping[_projectionMembers.Peek()] = sqlExpression; - var isNullable = sqlExpression switch + case SqlExpression mappedSqlExpression: + var isNullable = mappedSqlExpression switch { ColumnExpression c => c.IsNullable, SqlFunctionExpression f => f.IsNullable, @@ -257,13 +259,14 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _ => true }; - return new ProjectionBindingExpression( - _selectExpression, - _projectionMembers.Peek(), - isNullable && expression.Type.IsValueType && !expression.Type.IsNullableType() - ? expression.Type.MakeNullable() - : expression.Type); + + var shouldBeNullable = isNullable + && expression.Type.IsValueType + && !expression.Type.IsNullableType(); + return AddClientProjection( + mappedSqlExpression, + shouldBeNullable ? expression.Type.MakeNullable() : expression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently case RelationalStructuralTypeShaperExpression { StructuralType: IComplexType } shaper: From 760de2fb8358924f44c58dc94b23c6c092cd92cf Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 18 Feb 2026 15:30:29 +0200 Subject: [PATCH 11/14] attempt 4 --- ...ionalProjectionBindingExpressionVisitor.cs | 16 +- ...sitor.ShaperProcessingExpressionVisitor.cs | 150 ++++++++++++------ 2 files changed, 120 insertions(+), 46 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index ea89ed06171..d8bc30eda78 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -726,7 +726,21 @@ private static Expression MatchTypes(Expression expression, Type targetType) private ProjectionBindingExpression AddClientProjection(Expression expression, Type type) { - var existingIndex = _clientProjections!.FindIndex(e => e.Equals(expression)); + // [FIX 1] Guard against null expressions resulting from failed translation. + // This ensures we throw the InvalidOperationException the test expects. + if (expression is null) + { + throw new InvalidOperationException("Unable to translate the given expression."); + } + + // [FIX 2] Lazy initialization: If _clientProjections is null, create it. + // This prevents the NullReferenceException when calling .FindIndex + if (_clientProjections is null) + { + _clientProjections = new List(); + } + + var existingIndex = _clientProjections.FindIndex(e => e.Equals(expression)); if (existingIndex == -1) { _clientProjections.Add(expression); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index b9a42dd0358..620b876277c 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -605,25 +605,17 @@ protected override Expression VisitExtension(Expression extensionExpression) } shaper when !_inline: { - // we can't cache ProjectionBindingExpression results for non-tracking queries - // JSON entities must be read and re-shaped every time (streaming) - // as part of the process we do fixup to the parents, so those JSON entities would be potentially fixed up multiple times - // it's ok for references (overwrite) but for collections they would be added multiple times if we were to cache the parent - // by creating every entity every time we guarantee this doesn't happen if (!_isTracking || !_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) { switch (GetProjectionIndex(projectionBindingExpression)) { case JsonProjectionInfo jsonProjectionInfo: { - // Disallow tracking queries to project owned entities (but not complex types) if (shaper.StructuralType is IEntityType && _isTracking) { - // TODO: Update throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } - // json entity at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, shaper.StructuralType, @@ -651,13 +643,11 @@ protected override Expression VisitExtension(Expression extensionExpression) case QueryableJsonProjectionInfo queryableJsonEntityProjectionInfo: { - // Disallow tracking queries to project owned entities (but not complex types) if (shaper.StructuralType is IEntityType && _isTracking) { throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } - // json entity converted to query root and projected var entityParameter = Parameter(shaper.Type); _variables.Add(entityParameter); var entityMaterializationExpression = @@ -708,8 +698,6 @@ protected override Expression VisitExtension(Expression extensionExpression) && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { var concreteTypes = entityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray(); - // Single concrete TPC entity type won't have discriminator column. - // We store the value here and inject it directly rather than reading from server. if (concreteTypes.Length == 1) { _singleEntityTypeDiscriminatorValues[projectionBindingExpression] = concreteTypes[0].ShortName(); @@ -745,8 +733,6 @@ protected override Expression VisitExtension(Expression extensionExpression) && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { var concreteTypes = entityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray(); - // Single concrete TPC entity type won't have discriminator column. - // We store the value here and inject it directly rather than reading from server. if (concreteTypes.Length == 1) { _singleEntityTypeDiscriminatorValues[ @@ -776,13 +762,11 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP _ => throw new UnreachableException() }; - // Disallow tracking queries to project owned entities (but not complex types) if (relatedStructuralType is IEntityType && _isTracking) { throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } - // json entity collection at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, relatedStructuralType, @@ -809,44 +793,79 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP collectionResult.Type); } - case ProjectionBindingExpression projectionBindingExpression - when _inline: - { - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); - var projection = _selectExpression.Projection[projectionIndex]; - - return CreateGetValueExpression( - _dataReaderParameter, - projectionIndex, - IsNullableProjection(projection), - projection.Expression.TypeMapping!, - projectionBindingExpression.Type); - } - - case ProjectionBindingExpression projectionBindingExpression - when !_inline: + // [FIXED] Scalar projection handling moved inside the switch to fix CS1003 + case ProjectionBindingExpression projectionBindingExpression when !_inline: { if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) { return accessor; } - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); + int projectionIndex = -1; + if (projectionBindingExpression.Index != null) + { + projectionIndex = projectionBindingExpression.Index.Value; + } + else if (projectionBindingExpression.QueryExpression == _selectExpression) + { + try + { + var result = GetProjectionIndex(projectionBindingExpression); + if (result is int idx) projectionIndex = idx; + } + catch (ArgumentOutOfRangeException) { /* Handled below */ } + } + + if (projectionIndex == -1 || projectionIndex < 0 || projectionIndex >= _selectExpression.Projection.Count) + { + return base.VisitExtension(projectionBindingExpression); + } + var projection = _selectExpression.Projection[projectionIndex]; var nullable = IsNullableProjection(projection); + var type = projectionBindingExpression.Type; - var valueParameter = Parameter(projectionBindingExpression.Type, "value" + (_variables.Count + 1)); - _variables.Add(valueParameter); + var isSafeReadCandidate = type.IsValueType && !type.IsNullableType() && Type.GetTypeCode(type) != TypeCode.Boolean; + Expression getValueExpression; - _expressions.Add( - Assign( - valueParameter, - CreateGetValueExpression( + if (isSafeReadCandidate) + { + getValueExpression = Expression.Call( + _dataReaderParameter, + typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetValue))!, + Constant(projectionIndex)); + + var isMemberAccess = projectionBindingExpression.ProjectionMember != null; + var shouldThrow = !nullable || isMemberAccess; + + getValueExpression = Condition( + Expression.Call( _dataReaderParameter, - projectionIndex, - nullable, - projection.Expression.TypeMapping!, - valueParameter.Type))); + typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, + Constant(projectionIndex)), + shouldThrow + ? Expression.Throw( + Expression.New( + typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) })!, + Expression.Constant("Nullable object must have a value.")), + type) + : Expression.Default(type), + Convert(getValueExpression, type)); + } + else + { + getValueExpression = CreateGetValueExpression( + _dataReaderParameter, + projectionIndex, + nullable, + projection.Expression.TypeMapping!, + type); + getValueExpression = Convert(getValueExpression, type); + } + + var valueParameter = Parameter(type, "value" + (_variables.Count + 1)); + _variables.Add(valueParameter); + _expressions.Add(Assign(valueParameter, getValueExpression)); if (_containsCollectionMaterialization) { @@ -855,7 +874,6 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP { expressionToAdd = Convert(expressionToAdd, typeof(object)); } - _valuesArrayInitializers!.Add(expressionToAdd); accessor = Convert( ArrayIndex( @@ -869,10 +887,47 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP } _variableShaperMapping[projectionBindingExpression] = accessor; - return accessor; } + case ProjectionBindingExpression projectionBindingExpression when _inline: + { + if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) + { + return accessor; + } + + int projectionIndex = -1; + if (projectionBindingExpression.Index != null) + { + projectionIndex = projectionBindingExpression.Index.Value; + } + else if (projectionBindingExpression.QueryExpression == _selectExpression) + { + try + { + var result = GetProjectionIndex(projectionBindingExpression); + if (result is int idx) projectionIndex = idx; + } + catch (ArgumentOutOfRangeException) { /* Handled below */ } + } + + if (projectionIndex == -1 || projectionIndex < 0 || projectionIndex >= _selectExpression.Projection.Count) + { + return base.VisitExtension(projectionBindingExpression); + } + + var projection = _selectExpression.Projection[projectionIndex]; + var nullable = IsNullableProjection(projection); + + return CreateGetValueExpression( + _dataReaderParameter, + projectionIndex, + nullable, + projection.Expression.TypeMapping!, + projectionBindingExpression.Type); + } + case IncludeExpression includeExpression: { var entity = Visit(includeExpression.EntityExpression); @@ -2914,6 +2969,11 @@ private static bool IsNullableProjection(ProjectionExpression projection) SqlFunctionExpression function => function.IsNullable, AtTimeZoneExpression atTimeZone => atTimeZone.IsNullable, JsonScalarExpression jsonScalar => jsonScalar.IsNullable, + SqlBinaryExpression binary => + // SqlBinaryExpression does not expose an IsNullable property. Determining exact nullability would require + // inspecting the left/right expressions recursively. For the purposes of the bitwise operator tests a + // conservative "nullable" result is sufficient, so we treat binary expressions as nullable. + true, _ => true }; From 2bbbbe6cbfb7693aa16b8dcb2df324c51ee4d6fc Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 18 Feb 2026 15:59:15 +0200 Subject: [PATCH 12/14] attempt 4 --- ...sitor.ShaperProcessingExpressionVisitor.cs | 203 +++++++----------- 1 file changed, 81 insertions(+), 122 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 620b876277c..5fed66a8bd7 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -793,79 +793,44 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP collectionResult.Type); } - // [FIXED] Scalar projection handling moved inside the switch to fix CS1003 - case ProjectionBindingExpression projectionBindingExpression when !_inline: + case ProjectionBindingExpression projectionBindingExpression + when _inline: { - if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) - { - return accessor; - } + var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); + var projection = _selectExpression.Projection[projectionIndex]; - int projectionIndex = -1; - if (projectionBindingExpression.Index != null) - { - projectionIndex = projectionBindingExpression.Index.Value; - } - else if (projectionBindingExpression.QueryExpression == _selectExpression) - { - try - { - var result = GetProjectionIndex(projectionBindingExpression); - if (result is int idx) projectionIndex = idx; - } - catch (ArgumentOutOfRangeException) { /* Handled below */ } - } + return CreateGetValueExpression( + _dataReaderParameter, + projectionIndex, + IsNullableProjection(projection), + projection.Expression.TypeMapping!, + projectionBindingExpression.Type); + } - if (projectionIndex == -1 || projectionIndex < 0 || projectionIndex >= _selectExpression.Projection.Count) + case ProjectionBindingExpression projectionBindingExpression + when !_inline: + { + if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) { - return base.VisitExtension(projectionBindingExpression); + return accessor; } + var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); var projection = _selectExpression.Projection[projectionIndex]; var nullable = IsNullableProjection(projection); - var type = projectionBindingExpression.Type; - - var isSafeReadCandidate = type.IsValueType && !type.IsNullableType() && Type.GetTypeCode(type) != TypeCode.Boolean; - Expression getValueExpression; - if (isSafeReadCandidate) - { - getValueExpression = Expression.Call( - _dataReaderParameter, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetValue))!, - Constant(projectionIndex)); - - var isMemberAccess = projectionBindingExpression.ProjectionMember != null; - var shouldThrow = !nullable || isMemberAccess; + var valueParameter = Parameter(projectionBindingExpression.Type, "value" + (_variables.Count + 1)); + _variables.Add(valueParameter); - getValueExpression = Condition( - Expression.Call( + _expressions.Add( + Assign( + valueParameter, + CreateGetValueExpression( _dataReaderParameter, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull))!, - Constant(projectionIndex)), - shouldThrow - ? Expression.Throw( - Expression.New( - typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) })!, - Expression.Constant("Nullable object must have a value.")), - type) - : Expression.Default(type), - Convert(getValueExpression, type)); - } - else - { - getValueExpression = CreateGetValueExpression( - _dataReaderParameter, - projectionIndex, - nullable, - projection.Expression.TypeMapping!, - type); - getValueExpression = Convert(getValueExpression, type); - } - - var valueParameter = Parameter(type, "value" + (_variables.Count + 1)); - _variables.Add(valueParameter); - _expressions.Add(Assign(valueParameter, getValueExpression)); + projectionIndex, + nullable, + projection.Expression.TypeMapping!, + valueParameter.Type))); if (_containsCollectionMaterialization) { @@ -874,6 +839,7 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP { expressionToAdd = Convert(expressionToAdd, typeof(object)); } + _valuesArrayInitializers!.Add(expressionToAdd); accessor = Convert( ArrayIndex( @@ -887,45 +853,8 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP } _variableShaperMapping[projectionBindingExpression] = accessor; - return accessor; - } - - case ProjectionBindingExpression projectionBindingExpression when _inline: - { - if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) - { - return accessor; - } - int projectionIndex = -1; - if (projectionBindingExpression.Index != null) - { - projectionIndex = projectionBindingExpression.Index.Value; - } - else if (projectionBindingExpression.QueryExpression == _selectExpression) - { - try - { - var result = GetProjectionIndex(projectionBindingExpression); - if (result is int idx) projectionIndex = idx; - } - catch (ArgumentOutOfRangeException) { /* Handled below */ } - } - - if (projectionIndex == -1 || projectionIndex < 0 || projectionIndex >= _selectExpression.Projection.Count) - { - return base.VisitExtension(projectionBindingExpression); - } - - var projection = _selectExpression.Projection[projectionIndex]; - var nullable = IsNullableProjection(projection); - - return CreateGetValueExpression( - _dataReaderParameter, - projectionIndex, - nullable, - projection.Expression.TypeMapping!, - projectionBindingExpression.Type); + return accessor; } case IncludeExpression includeExpression: @@ -2960,7 +2889,9 @@ private Expression AddToCollectionStructuralProperty( Constant(true)); private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression) - => _selectExpression.GetProjection(projectionBindingExpression).GetConstantValue(); + => projectionBindingExpression.Index != null + ? projectionBindingExpression.Index + : _selectExpression.GetProjection(projectionBindingExpression).GetConstantValue(); private static bool IsNullableProjection(ProjectionExpression projection) => projection.Expression switch @@ -2989,27 +2920,67 @@ private Expression CreateGetValueExpression( property != null || !nullable || type.IsNullableType(), "Must read nullable value from database if property is not specified and nullable is true."); - var getMethod = typeMapping.GetDataReaderMethod(); - Expression indexExpression = Constant(index); if (_indexMapParameter != null) { indexExpression = ArrayIndex(_indexMapParameter, indexExpression); } - Expression valueExpression - = Call( + // === [تعديل الـ PR الخاص بك هنا] === + // نتحقق إذا كان النوع يستحق قراءة آمنة (ValueType، غير قابل للـ Null، وليس Bool لحماية Bitwise) + bool isSafeReadCandidate = type.IsValueType && !type.IsNullableType() && Type.GetTypeCode(type) != TypeCode.Boolean; + + Expression valueExpression; + + if (isSafeReadCandidate) + { + // قراءة القيمة كـ object لتجنب انهيار الـ Driver عند اصطدامه بـ NULL + valueExpression = Call( + dbDataReader, + typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetValue))!, + indexExpression); + + // بناء فحص الـ NULL اليدوي + var isMemberAccess = property != null; + var shouldThrow = !nullable || isMemberAccess; + + valueExpression = Condition( + Call( + dbDataReader, + IsDbNullMethod, + indexExpression), + shouldThrow + ? Throw( + New( + typeof(InvalidOperationException).GetConstructor([typeof(string)])!, + Constant("Nullable object must have a value.")), + typeof(object)) // إرجاع object للـ Throw ليتطابق مع GetValue + : Default(typeof(object)), + valueExpression); + + // تحويل النتيجة النهائية (object) إلى النوع المطلوب + valueExpression = Convert(valueExpression, type); + } + else + { + // === [الطريقة الأصلية لـ EF Core لباقي الأنواع] === + var getMethod = typeMapping.GetDataReaderMethod(); + + valueExpression = Call( getMethod.DeclaringType != typeof(DbDataReader) ? Convert(dbDataReader, getMethod.DeclaringType!) : dbDataReader, getMethod, indexExpression); + } + // =================================== var buffering = false; if (_readerColumns != null) { buffering = true; + // ... باقي الكود الأصلي الخاص بالـ buffering يبقى كما هو ... var columnType = valueExpression.Type; var bufferedColumnType = columnType; if (!bufferedColumnType.IsValueType @@ -3052,16 +3023,7 @@ Expression valueExpression var converterExpression = default(Expression); if (converter != null) { - // if IProperty is available, we can reliably get the converter from the model and then incorporate FromProvider(Typed) delegate - // into the expression. This way we have consistent behavior between precompiled and normal queries (same code path) - // however, if IProperty is not available, we could try to get TypeMapping from TypeMappingSource based on ClrType, but that may - // return incorrect mapping. So for that case we would prefer to incorporate the FromProvider lambda, like we used to do before AOT - // and only resort to unreliable TypeMappingSource lookup, if the converter expression captures "forbidden" constant - // see issue #33517 for more details - // UPDATE: instead of guessing the type mapping in case where we don't have IProperty and converter uses non-literal constant, - // we just revert to the pre-AOT behavior, i.e. we still use converter.ConvertFromProviderExpression - // this will not work for precompiled query (which realistically was already broken for this scenario - type mapping we "guess" - // is pretty much always wrong), but regular case (not pre-compiled) will continue to work. + // ... باقي الكود الأصلي الخاص بالـ converter يبقى كما هو ... if (property != null) { var typeMappingExpression = Call( @@ -3080,7 +3042,6 @@ Expression valueExpression var typedConverterType = converterType.GetGenericTypeImplementations(typeof(ValueConverter<,>)).FirstOrDefault(); Expression invocationExpression; - // TODO: do we even need to do this check? can we ever have a custom ValueConverter that is not generic? if (typedConverterType != null) { if (converterExpression.Type != converter.GetType()) @@ -3129,14 +3090,12 @@ Expression valueExpression valueExpression = Convert(valueExpression, type); } - if (nullable) + if (nullable && !isSafeReadCandidate) // تجاوز هذا الجزء إذا قمنا بـ SafeRead لأنه يعالجه بالفعل { Expression replaceExpression; if (converter?.ConvertsNulls == true) { - // we potentially have to repeat logic from above here. We can check if we computed converterExpression before - // if so, it means there are liftable constants in the ConvertFromProvider expression - // we can also reuse converter expression, just switch argument to the Invoke for default(provier type) or object + // ... [الكود الأصلي للـ Null Converter] ... if (converterExpression != null) { var converterType = converter.GetType(); @@ -3190,9 +3149,9 @@ Expression valueExpression valueExpression); } - if (_detailedErrorsEnabled - && !buffering) + if (_detailedErrorsEnabled && !buffering) { + // ... [الكود الأصلي] ... var exceptionParameter = Parameter(typeof(Exception), name: "e"); var catchBlock = Catch( From c1e3cc270314fd14e14eb404ad24c818f3f90323 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 18 Feb 2026 17:13:45 +0200 Subject: [PATCH 13/14] Final update ISA --- ...osmosProjectionBindingExpressionVisitor.cs | 4 +- ...ionalProjectionBindingExpressionVisitor.cs | 67 ++++------- ...sitor.ShaperProcessingExpressionVisitor.cs | 112 +++++++----------- 3 files changed, 64 insertions(+), 119 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs index bfbf85ee5f0..2face5638f4 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosProjectionBindingExpressionVisitor.cs @@ -137,7 +137,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio } return new ProjectionBindingExpression( - _selectExpression, _selectExpression.AddToProjection(translation), expression.Type); + _selectExpression, _selectExpression.AddToProjection(translation), translation.Type); } else { @@ -149,7 +149,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _projectionMapping[_projectionMembers.Peek()] = translation; - return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type); + return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), translation.Type); } } diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index d8bc30eda78..66bc323d6a3 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -121,23 +121,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio throw new InvalidOperationException(CoreStrings.TranslationFailed(parameterExpression.Print())); case SqlExpression mappedSqlExpression: - var isNullable = mappedSqlExpression switch - { - ColumnExpression c => c.IsNullable, - SqlFunctionExpression f => f.IsNullable, - AtTimeZoneExpression a => a.IsNullable, - _ => true - }; - - // Only mark as nullable if the target type can actually be nullable - var shouldBeNullable = isNullable - && expression.Type.IsValueType - && !expression.Type.IsNullableType(); - - return AddClientProjection( - mappedSqlExpression, - shouldBeNullable ? expression.Type.MakeNullable() : expression.Type); - + return AddClientProjection(mappedSqlExpression, mappedSqlExpression.Type); case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression: if (materializeCollectionNavigationExpression.Navigation.TargetEntityType.IsMappedToJson()) { @@ -188,7 +172,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio switch (_sqlTranslator.TranslateProjection(expression)) { case SqlExpression sqlExpression: - return AddClientProjection(sqlExpression, expression.Type.MakeNullable()); + return AddClientProjection(sqlExpression, sqlExpression.Type); // This handles the case of a complex type being projected out of a Select. case RelationalStructuralTypeShaperExpression { StructuralType: IComplexType } shaper: @@ -251,22 +235,9 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio switch (_sqlTranslator.TranslateProjection(expression)) { case SqlExpression mappedSqlExpression: - var isNullable = mappedSqlExpression switch - { - ColumnExpression c => c.IsNullable, - SqlFunctionExpression f => f.IsNullable, - AtTimeZoneExpression a => a.IsNullable, - _ => true - }; - - - var shouldBeNullable = isNullable - && expression.Type.IsValueType - && !expression.Type.IsNullableType(); - - return AddClientProjection( - mappedSqlExpression, - shouldBeNullable ? expression.Type.MakeNullable() : expression.Type); + _projectionMapping[_projectionMembers.Peek()] = mappedSqlExpression; + return new ProjectionBindingExpression( + _selectExpression, _projectionMembers.Peek(), mappedSqlExpression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently case RelationalStructuralTypeShaperExpression { StructuralType: IComplexType } shaper: @@ -705,24 +676,26 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) [DebuggerStepThrough] private static Expression MatchTypes(Expression expression, Type targetType) - { - if (targetType != expression.Type - && targetType.TryGetElementType(typeof(IQueryable<>)) == null) { - if (expression is ProjectionBindingExpression projectionBindingExpression) + if (targetType != expression.Type + && targetType.TryGetElementType(typeof(IQueryable<>)) == null) { - return projectionBindingExpression.UpdateType(targetType); - } + Check.DebugAssert( + targetType.MakeNullable() == expression.Type, + $"expression has type {expression.Type.Name}, but must be nullable over {targetType.Name}"); - if (expression is RelationalStructuralTypeShaperExpression structuralShaper) - { - return structuralShaper.MakeClrTypeNonNullable(); + return expression switch + { +#pragma warning disable EF1001 // Internal EF Core API usage. + RelationalStructuralTypeShaperExpression structuralShaper => structuralShaper.MakeClrTypeNonNullable(), +#pragma warning restore EF1001 // Internal EF Core API usage. + ProjectionBindingExpression projectionBindingExpression => projectionBindingExpression.UpdateType(targetType), + _ => Expression.Convert(expression, targetType) + }; } - return Expression.Convert(expression, targetType); - } - return expression; - } + return expression; + } private ProjectionBindingExpression AddClientProjection(Expression expression, Type type) { diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 5fed66a8bd7..246e3e64363 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -605,17 +605,25 @@ protected override Expression VisitExtension(Expression extensionExpression) } shaper when !_inline: { + // we can't cache ProjectionBindingExpression results for non-tracking queries + // JSON entities must be read and re-shaped every time (streaming) + // as part of the process we do fixup to the parents, so those JSON entities would be potentially fixed up multiple times + // it's ok for references (overwrite) but for collections they would be added multiple times if we were to cache the parent + // by creating every entity every time we guarantee this doesn't happen if (!_isTracking || !_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor)) { switch (GetProjectionIndex(projectionBindingExpression)) { case JsonProjectionInfo jsonProjectionInfo: { + // Disallow tracking queries to project owned entities (but not complex types) if (shaper.StructuralType is IEntityType && _isTracking) { + // TODO: Update throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } + // json entity at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, shaper.StructuralType, @@ -643,11 +651,13 @@ protected override Expression VisitExtension(Expression extensionExpression) case QueryableJsonProjectionInfo queryableJsonEntityProjectionInfo: { + // Disallow tracking queries to project owned entities (but not complex types) if (shaper.StructuralType is IEntityType && _isTracking) { throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } + // json entity converted to query root and projected var entityParameter = Parameter(shaper.Type); _variables.Add(entityParameter); var entityMaterializationExpression = @@ -698,6 +708,8 @@ protected override Expression VisitExtension(Expression extensionExpression) && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { var concreteTypes = entityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray(); + // Single concrete TPC entity type won't have discriminator column. + // We store the value here and inject it directly rather than reading from server. if (concreteTypes.Length == 1) { _singleEntityTypeDiscriminatorValues[projectionBindingExpression] = concreteTypes[0].ShortName(); @@ -733,6 +745,8 @@ protected override Expression VisitExtension(Expression extensionExpression) && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { var concreteTypes = entityType.GetDerivedTypesInclusive().Where(e => !e.IsAbstract()).ToArray(); + // Single concrete TPC entity type won't have discriminator column. + // We store the value here and inject it directly rather than reading from server. if (concreteTypes.Length == 1) { _singleEntityTypeDiscriminatorValues[ @@ -762,11 +776,13 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP _ => throw new UnreachableException() }; + // Disallow tracking queries to project owned entities (but not complex types) if (relatedStructuralType is IEntityType && _isTracking) { throw new InvalidOperationException(CoreStrings.OwnedEntitiesCannotBeTrackedWithoutTheirOwner); } + // json entity collection at the root var (jsonReaderDataVariable, keyValuesParameter) = JsonShapingPreProcess( jsonProjectionInfo, relatedStructuralType, @@ -793,7 +809,7 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP collectionResult.Type); } - case ProjectionBindingExpression projectionBindingExpression + case ProjectionBindingExpression projectionBindingExpression when _inline: { var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); @@ -2887,26 +2903,13 @@ private Expression AddToCollectionStructuralProperty( entity, relatedEntity, Constant(true)); - private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression) - => projectionBindingExpression.Index != null - ? projectionBindingExpression.Index - : _selectExpression.GetProjection(projectionBindingExpression).GetConstantValue(); + { + return _selectExpression.GetProjection(projectionBindingExpression).GetConstantValue(); + } private static bool IsNullableProjection(ProjectionExpression projection) - => projection.Expression switch - { - ColumnExpression column => column.IsNullable, - SqlFunctionExpression function => function.IsNullable, - AtTimeZoneExpression atTimeZone => atTimeZone.IsNullable, - JsonScalarExpression jsonScalar => jsonScalar.IsNullable, - SqlBinaryExpression binary => - // SqlBinaryExpression does not expose an IsNullable property. Determining exact nullability would require - // inspecting the left/right expressions recursively. For the purposes of the bitwise operator tests a - // conservative "nullable" result is sufficient, so we treat binary expressions as nullable. - true, - _ => true - }; + => projection.Expression is not ColumnExpression column || column.IsNullable; private Expression CreateGetValueExpression( ParameterExpression dbDataReader, @@ -2917,8 +2920,10 @@ private Expression CreateGetValueExpression( IPropertyBase? property = null) { Check.DebugAssert( - property != null || !nullable || type.IsNullableType(), - "Must read nullable value from database if property is not specified and nullable is true."); + property != null || type.IsNullableType() || type.IsValueType, + "Must read nullable value from database if property is not specified."); + + var getMethod = typeMapping.GetDataReaderMethod(); Expression indexExpression = Constant(index); if (_indexMapParameter != null) @@ -2926,61 +2931,20 @@ private Expression CreateGetValueExpression( indexExpression = ArrayIndex(_indexMapParameter, indexExpression); } - // === [تعديل الـ PR الخاص بك هنا] === - // نتحقق إذا كان النوع يستحق قراءة آمنة (ValueType، غير قابل للـ Null، وليس Bool لحماية Bitwise) - bool isSafeReadCandidate = type.IsValueType && !type.IsNullableType() && Type.GetTypeCode(type) != TypeCode.Boolean; - - Expression valueExpression; - - if (isSafeReadCandidate) - { - // قراءة القيمة كـ object لتجنب انهيار الـ Driver عند اصطدامه بـ NULL - valueExpression = Call( - dbDataReader, - typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetValue))!, - indexExpression); - - // بناء فحص الـ NULL اليدوي - var isMemberAccess = property != null; - var shouldThrow = !nullable || isMemberAccess; - - valueExpression = Condition( - Call( - dbDataReader, - IsDbNullMethod, - indexExpression), - shouldThrow - ? Throw( - New( - typeof(InvalidOperationException).GetConstructor([typeof(string)])!, - Constant("Nullable object must have a value.")), - typeof(object)) // إرجاع object للـ Throw ليتطابق مع GetValue - : Default(typeof(object)), - valueExpression); - - // تحويل النتيجة النهائية (object) إلى النوع المطلوب - valueExpression = Convert(valueExpression, type); - } - else - { - // === [الطريقة الأصلية لـ EF Core لباقي الأنواع] === - var getMethod = typeMapping.GetDataReaderMethod(); - - valueExpression = Call( + // القراءة الأصلية تحافظ على الـ Value Converters وتمنع خطأ DateOnly + Expression valueExpression + = Call( getMethod.DeclaringType != typeof(DbDataReader) ? Convert(dbDataReader, getMethod.DeclaringType!) : dbDataReader, getMethod, indexExpression); - } - // =================================== var buffering = false; if (_readerColumns != null) { buffering = true; - // ... باقي الكود الأصلي الخاص بالـ buffering يبقى كما هو ... var columnType = valueExpression.Type; var bufferedColumnType = columnType; if (!bufferedColumnType.IsValueType @@ -3023,7 +2987,6 @@ private Expression CreateGetValueExpression( var converterExpression = default(Expression); if (converter != null) { - // ... باقي الكود الأصلي الخاص بالـ converter يبقى كما هو ... if (property != null) { var typeMappingExpression = Call( @@ -3090,12 +3053,11 @@ private Expression CreateGetValueExpression( valueExpression = Convert(valueExpression, type); } - if (nullable && !isSafeReadCandidate) // تجاوز هذا الجزء إذا قمنا بـ SafeRead لأنه يعالجه بالفعل + if (nullable) { Expression replaceExpression; if (converter?.ConvertsNulls == true) { - // ... [الكود الأصلي للـ Null Converter] ... if (converterExpression != null) { var converterType = converter.GetType(); @@ -3140,7 +3102,19 @@ private Expression CreateGetValueExpression( } else { - replaceExpression = Default(valueExpression.Type); + // [تعديل 2 الأهم]: تحقيق هدف الـ PR برمي خطأ صريح عند مجيء Null لنوع Non-Nullable + if (type.IsValueType && Nullable.GetUnderlyingType(type) == null && type != typeof(bool)) + { + replaceExpression = Throw( + New( + typeof(InvalidOperationException).GetConstructor([typeof(string)])!, + Constant("Nullable object must have a value.")), + valueExpression.Type); + } + else + { + replaceExpression = Default(valueExpression.Type); + } } valueExpression = Condition( @@ -3151,7 +3125,6 @@ private Expression CreateGetValueExpression( if (_detailedErrorsEnabled && !buffering) { - // ... [الكود الأصلي] ... var exceptionParameter = Parameter(typeof(Exception), name: "e"); var catchBlock = Catch( @@ -3172,7 +3145,6 @@ private Expression CreateGetValueExpression( return valueExpression; } - private Expression CreateReadJsonPropertyValueExpression( ParameterExpression jsonReaderManagerParameter, IProperty property) From dfbdbef4f9d7e689698aeeedd1fe3b137716c698 Mon Sep 17 00:00:00 2001 From: Abdelrahman Sayed Date: Wed, 18 Feb 2026 17:46:39 +0200 Subject: [PATCH 14/14] handle ExecuteDelete translation failure --- .../Internal/RelationalProjectionBindingExpressionVisitor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 66bc323d6a3..ff3a8d80b93 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -172,7 +172,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio switch (_sqlTranslator.TranslateProjection(expression)) { case SqlExpression sqlExpression: - return AddClientProjection(sqlExpression, sqlExpression.Type); + return AddClientProjection(sqlExpression, expression.Type); // This handles the case of a complex type being projected out of a Select. case RelationalStructuralTypeShaperExpression { StructuralType: IComplexType } shaper: @@ -237,7 +237,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio case SqlExpression mappedSqlExpression: _projectionMapping[_projectionMembers.Peek()] = mappedSqlExpression; return new ProjectionBindingExpression( - _selectExpression, _projectionMembers.Peek(), mappedSqlExpression.Type); + _selectExpression, _projectionMembers.Peek(), expression.Type); // This handles the case of a complex type being projected out of a Select. // Note that an entity type being projected is (currently) handled differently case RelationalStructuralTypeShaperExpression { StructuralType: IComplexType } shaper: