diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index f33c85fd726..a90b94ed6fe 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -320,8 +320,13 @@ protected override Expression VisitExtension(Expression extensionExpression) _projectionMapping[_projectionMembers.Peek()] = jsonQueryExpression; +#pragma warning disable EF1001 return shaper.Update( - new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer))); + new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer))) + // This is to handle have correct type for the shaper expression. It is later fixed in MatchTypes. + // This mirrors for structural types what we do for scalars. + .MakeClrTypeNullable(); +#pragma warning restore EF1001 } if (shaper.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression) @@ -342,13 +347,23 @@ protected override Expression VisitExtension(Expression extensionExpression) { var projectionBinding = AddClientProjection(jsonQuery, typeof(ValueBuffer)); - return shaper.Update(projectionBinding); +#pragma warning disable EF1001 + return shaper.Update(projectionBinding) + // This is to handle have correct type for the shaper expression. It is later fixed in MatchTypes. + // This mirrors for structural types what we do for scalars. + .MakeClrTypeNullable(); +#pragma warning restore EF1001 } _projectionMapping[_projectionMembers.Peek()] = jsonQuery; +#pragma warning disable EF1001 return shaper.Update( - new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer))); + new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer))) + // This is to handle have correct type for the shaper expression. It is later fixed in MatchTypes. + // This mirrors for structural types what we do for scalars. + .MakeClrTypeNullable(); +#pragma warning restore EF1001 } projection = (StructuralTypeProjectionExpression)projection2; @@ -366,14 +381,19 @@ protected override Expression VisitExtension(Expression extensionExpression) _projectionBindingCache[projection] = entityProjectionBinding; } - return shaper.Update(entityProjectionBinding); +#pragma warning disable EF1001 + return shaper.Update(entityProjectionBinding) + // This is to handle have correct type for the shaper expression. It is later fixed in MatchTypes. + // This mirrors for structural types what we do for scalars. + .MakeClrTypeNullable(); +#pragma warning restore EF1001 } _projectionMapping[_projectionMembers.Peek()] = projection; +#pragma warning disable EF1001 return shaper .Update(new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer))) -#pragma warning disable EF1001 // This is to handle have correct type for the shaper expression. It is later fixed in MatchTypes. // This mirrors for structural types what we do for scalars. .MakeClrTypeNullable(); diff --git a/src/EFCore.Relational/Query/Internal/Translators/NullableMemberTranslator.cs b/src/EFCore.Relational/Query/Internal/Translators/NullableMemberTranslator.cs deleted file mode 100644 index 618d28e347b..00000000000 --- a/src/EFCore.Relational/Query/Internal/Translators/NullableMemberTranslator.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; - -// ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore.Query.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public class NullableMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMemberTranslator -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual SqlExpression? Translate( - SqlExpression? instance, - MemberInfo member, - Type returnType, - IDiagnosticsLogger logger) - { - if (member.DeclaringType?.IsNullableValueType() == true - && instance != null) - { - return member.Name switch - { - nameof(Nullable.Value) => instance, - nameof(Nullable.HasValue) => sqlExpressionFactory.IsNotNull(instance), - _ => null - }; - } - - return null; - } -} diff --git a/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs b/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs index 6b673e7c98e..eec2398d3ea 100644 --- a/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs +++ b/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; namespace Microsoft.EntityFrameworkCore.Query; @@ -21,7 +20,6 @@ public RelationalMemberTranslatorProvider(RelationalMemberTranslatorProviderDepe Dependencies = dependencies; _plugins.AddRange(dependencies.Plugins.SelectMany(p => p.Translators)); - _translators.AddRange([new NullableMemberTranslator(dependencies.SqlExpressionFactory)]); } /// diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 6bdb8d1f2ef..e420c1586fe 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -628,6 +628,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var shaperResult = CreateJsonShapers( shaper.StructuralType, + shaper.Type, shaper.IsNullable, jsonReaderDataVariable, keyValuesParameter, @@ -674,8 +675,10 @@ protected override Expression VisitExtension(Expression extensionExpression) childProjectionInfo.Navigation.TargetEntityType, childProjectionInfo.Navigation.IsCollection); + var targetEntityType = childProjectionInfo.Navigation.TargetEntityType; var shaperResult = CreateJsonShapers( - childProjectionInfo.Navigation.TargetEntityType, + targetEntityType, + targetEntityType.ClrType, nullable: true, jsonReaderDataVariable, keyValuesParameter, @@ -784,6 +787,7 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP var shaperResult = CreateJsonShapers( relatedStructuralType, + relationship.ClrType, nullable: true, jsonReaderDataVariable, keyValuesParameter, @@ -1151,8 +1155,10 @@ when GetProjectionIndex(projectionBindingExpression) is JsonProjectionInfo jsonP includeExpression.Navigation.TargetEntityType, includeExpression.Navigation.IsCollection); + var targetEntityType = includeExpression.Navigation.TargetEntityType; var shaperResult = CreateJsonShapers( - includeExpression.Navigation.TargetEntityType, + targetEntityType, + targetEntityType.ClrType, nullable: true, jsonReaderDataVariable, keyValuesParameter, @@ -1563,6 +1569,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private Expression CreateJsonShapers( ITypeBase structuralType, + Type clrType, bool nullable, ParameterExpression jsonReaderDataParameter, Expression? keyValuesParameter, @@ -1628,6 +1635,7 @@ private Expression CreateJsonShapers( var innerShaper = CreateJsonShapers( relatedStructuralType, + nestedRelationship.ClrType, nullable || isRelationshipNullable, jsonReaderDataShaperLambdaParameter, keyValuesShaperLambdaParameter, @@ -1847,15 +1855,13 @@ private Expression CreateJsonShapers( return materializeJsonEntityCollectionMethodCall; } - // Return the materializer for this JSON object, including null checks which would return null. MethodInfo method; - if (relationship is not null && Nullable.GetUnderlyingType(relationship.ClrType) is { } underlyingType) + if (Nullable.GetUnderlyingType(clrType) is { } underlyingType) { - // The association property into which we're assigning has a nullable value type, so generate - // a materializer that returns that nullable value type (note that the shaperLambda that - // we pass itself always returns a non-nullable value (the null checks are outside of it.)) + // We need to project out a nullable value type. Note that the shaperLambda that we pass itself always returns a + // non-nullable value (the null checks are outside of it.)) Check.DebugAssert(nullable, "On non-nullable relationship but the relationship's ClrType is Nullable"); Check.DebugAssert(underlyingType == structuralType.ClrType); @@ -2538,6 +2544,7 @@ internal void ProcessTopLevelComplexJsonProperties( var shaperResult = CreateJsonShapers( complexType, + complexProperty.ClrType, nullable: shaper.IsNullable || complexProperty.IsNullable, jsonReaderDataVariable, keyValuesParameter: null, // For owned entities only diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs index cfbf85177b4..aa521f8e93a 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs @@ -369,7 +369,7 @@ SqlExpression Process(Expression expression) => new JsonScalarExpression( jsonQuery.JsonColumn, jsonQuery.Path, - jsonQuery.Type, + jsonQuery.Type.UnwrapNullableType(), jsonQuery.JsonColumn.TypeMapping, jsonQuery.IsNullable), @@ -378,7 +378,7 @@ SqlExpression Process(Expression expression) => new JsonScalarExpression( jsonQuery.JsonColumn, jsonQuery.Path, - jsonQuery.Type, + jsonQuery.Type.UnwrapNullableType(), jsonQuery.JsonColumn.TypeMapping, jsonQuery.IsNullable), diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 5eed4a051e7..814adf1e349 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -635,18 +635,44 @@ protected override Expression VisitMember(MemberExpression memberExpression) Expression.Condition( cond.Test, Expression.MakeMemberAccess(cond.IfTrue, memberExpression.Member), - Expression.MakeMemberAccess(cond.IfFalse, memberExpression.Member) - )); + Expression.MakeMemberAccess(cond.IfFalse, memberExpression.Member))); } - var innerExpression = Visit(memberExpression.Expression); + var inner = Visit(memberExpression.Expression); - return TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member), out var expression) - ? expression - : (TranslationFailed(memberExpression.Expression, innerExpression, out var sqlInnerExpression) + var member = memberExpression.Member; + + // Try binding the member to a property on the structural type + if (TryBindMember(inner, MemberIdentity.Create(member), out var expression)) + { + return expression; + } + + // We handle translations for Nullable<> members here. + // These can't be handled in regular IMemberTranslators, since those only support scalars (SqlExpressions); + // but we also need to handle nullable value complex types. + if (member.DeclaringType?.IsGenericType == true + && member.DeclaringType.GetGenericTypeDefinition() == typeof(Nullable<>) + && inner is not null) + { + switch (member.Name) + { + case nameof(Nullable<>.Value): + return inner; + case nameof(Nullable<>.HasValue) when inner is SqlExpression sqlInner: + return _sqlExpressionFactory.IsNotNull(sqlInner); + case nameof(Nullable<>.HasValue) + when inner is StructuralTypeReferenceExpression + && TryRewriteStructuralTypeEquality( + ExpressionType.NotEqual, inner, new SqlConstantExpression(null, memberExpression.Expression!.Type, null), equalsMethod: false, out var result): + return result; + } + } + + return (TranslationFailed(memberExpression.Expression, inner, out var sqlInnerExpression) ? QueryCompilationContext.NotTranslatedExpression : Dependencies.MemberTranslatorProvider.Translate( - sqlInnerExpression, memberExpression.Member, memberExpression.Type, _queryCompilationContext.Logger)) + sqlInnerExpression, member, memberExpression.Type, _queryCompilationContext.Logger)) ?? QueryCompilationContext.NotTranslatedExpression; } diff --git a/src/EFCore.Relational/Query/RelationalStructuralTypeShaperExpression.cs b/src/EFCore.Relational/Query/RelationalStructuralTypeShaperExpression.cs index 6f8d765dc9b..aac3d722bd9 100644 --- a/src/EFCore.Relational/Query/RelationalStructuralTypeShaperExpression.cs +++ b/src/EFCore.Relational/Query/RelationalStructuralTypeShaperExpression.cs @@ -187,13 +187,13 @@ public override StructuralTypeShaperExpression Update(Expression valueBufferExpr : this; /// - public override StructuralTypeShaperExpression MakeClrTypeNullable() + public override RelationalStructuralTypeShaperExpression MakeClrTypeNullable() => Type != Type.MakeNullable() ? new RelationalStructuralTypeShaperExpression(StructuralType, ValueBufferExpression, IsNullable, MaterializationCondition, Type.MakeNullable()) : this; /// - public override StructuralTypeShaperExpression MakeClrTypeNonNullable() + public override RelationalStructuralTypeShaperExpression MakeClrTypeNonNullable() => Type != Type.UnwrapNullableType() ? new RelationalStructuralTypeShaperExpression(StructuralType, ValueBufferExpression, IsNullable, MaterializationCondition, Type.UnwrapNullableType()) : this; diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs index c46df253e71..9fbb436ee7e 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs @@ -1,12 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Xunit.Sdk; +using Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexTableSplitting; -public abstract class ComplexTableSplittingProjectionRelationalTestBase - : RelationshipsProjectionTestBase +public abstract class ComplexTableSplittingProjectionRelationalTestBase : ComplexPropertiesProjectionTestBase where TFixture : ComplexTableSplittingRelationalFixtureBase, new() { public ComplexTableSplittingProjectionRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs index ec85f9610e4..1b7be2c2184 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesMiscellaneousTestBase.cs @@ -5,4 +5,23 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; public abstract class ComplexPropertiesMiscellaneousTestBase(TFixture fixture) : RelationshipsMiscellaneousTestBase(fixture) - where TFixture : ComplexPropertiesFixtureBase, new(); + where TFixture : ComplexPropertiesFixtureBase, new() +{ + #region Value types + + [ConditionalFact] + public virtual Task Where_property_on_non_nullable_value_type() + => AssertQuery(ss => ss.Set().Where(e => e.RequiredRelated.Int == 8)); + + [ConditionalFact] + public virtual Task Where_property_on_nullable_value_type_Value() + => AssertQuery( + ss => ss.Set().Where(e => e.OptionalRelated!.Value.Int == 8), + ss => ss.Set().Where(e => e.OptionalRelated.HasValue && e.OptionalRelated!.Value.Int == 8)); + + [ConditionalFact] + public virtual Task Where_HasValue_on_nullable_value_type() + => AssertQuery(ss => ss.Set().Where(e => e.OptionalRelated.HasValue)); + + #endregion Value types +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs index dd6bc7cb295..420116abf73 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesProjectionTestBase.cs @@ -16,5 +16,32 @@ public virtual Task Select_root_with_value_types(QueryTrackingBehavior queryTrac ss => ss.Set(), queryTrackingBehavior: queryTrackingBehavior); + + [ConditionalTheory] + [MemberData(nameof(TrackingData))] + public virtual Task Select_non_nullable_value_type(QueryTrackingBehavior queryTrackingBehavior) + => AssertQuery( + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.RequiredRelated), + assertOrder: true, + queryTrackingBehavior: queryTrackingBehavior); + + + [ConditionalTheory] + [MemberData(nameof(TrackingData))] + public virtual Task Select_nullable_value_type(QueryTrackingBehavior queryTrackingBehavior) + => AssertQuery( + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated), + assertOrder: true, + queryTrackingBehavior: queryTrackingBehavior); + + [ConditionalTheory] + [MemberData(nameof(TrackingData))] + public virtual Task Select_nullable_value_type_with_Value(QueryTrackingBehavior queryTrackingBehavior) + => AssertQuery( + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.Value), + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated == null ? default : x.OptionalRelated!.Value), + assertOrder: true, + queryTrackingBehavior: queryTrackingBehavior); + #endregion Value types } diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs index 94ad8f43aff..d779e39c53d 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs @@ -133,4 +133,12 @@ await AssertQuery( ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == nestedCollection), ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection.SequenceEqual(nestedCollection))); } + + #region Value types + + [ConditionalFact] + public virtual Task Nullable_value_type_with_null() + => AssertQuery(ss => ss.Set().Where(e => e.OptionalRelated == null)); + + #endregion Value types } diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs index ba939b24c6b..06b7f408813 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs @@ -14,17 +14,28 @@ public RelationshipsQueryFixtureBase() { Data = CreateData(); - EntityAsserters = new Dictionary> + EntityAsserters = new Dictionary { - [typeof(RootEntity)] = (e, a) => NullSafeAssert(e, a, AssertRootEntity), - [typeof(RelatedType)] = (e, a) => NullSafeAssert(e, a, AssertRelatedType), - [typeof(NestedType)] = (e, a) => NullSafeAssert(e, a, AssertNestedType), - [typeof(RootReferencingEntity)] = (e, a) => NullSafeAssert(e, a, AssertPreRootEntity), - - [typeof(ValueRootEntity)] = (e, a) => NullSafeAssert(e, a, AssertValueRootEntity), - [typeof(ValueRelatedType)] = (e, a) => NullSafeAssert(e, a, AssertValueRelatedType), - [typeof(ValueNestedType)] = (e, a) => NullSafeAssert(e, a, AssertValueNestedType), - }.ToDictionary(e => e.Key, e => (object)e.Value); + [typeof(RootEntity)] = (RootEntity e, RootEntity a) + => NullSafeAssert(e, a, AssertRootEntity), + [typeof(RelatedType)] = (RelatedType e, RelatedType a) + => NullSafeAssert(e, a, AssertRelatedType), + [typeof(NestedType)] = (NestedType e, NestedType a) + => NullSafeAssert(e, a, AssertNestedType), + [typeof(RootReferencingEntity)] = (RootReferencingEntity e, RootReferencingEntity a) + => NullSafeAssert(e, a, AssertPreRootEntity), + + [typeof(ValueRootEntity)] = (ValueRootEntity e, ValueRootEntity a) + => NullSafeAssert(e, a, AssertValueRootEntity), + [typeof(ValueRelatedType)] = (ValueRelatedType e, ValueRelatedType a) + => NullSafeAssert(e, a, AssertValueRelatedType), + [typeof(ValueRelatedType?)] = (ValueRelatedType? e, ValueRelatedType? a) + => NullSafeAssert(e, a, AssertValueRelatedType), + [typeof(ValueNestedType)] = (ValueNestedType e, ValueNestedType a) + => NullSafeAssert(e, a, AssertValueNestedType), + [typeof(ValueNestedType?)] = (ValueNestedType? e, ValueNestedType? a) + => NullSafeAssert(e, a, AssertValueNestedType) + }.ToDictionary(e => e.Key, e => e.Value); } public Func GetContextCreator() @@ -60,17 +71,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .IsRequired(false); } - public virtual IReadOnlyDictionary EntitySorters { get; } = new Dictionary> + public virtual IReadOnlyDictionary EntitySorters { get; } = new Dictionary { - { typeof(RootEntity), e => ((RootEntity?)e)?.Id }, - { typeof(RelatedType), e => ((RelatedType?)e)?.Id }, - { typeof(NestedType), e => ((NestedType?)e)?.Id }, - { typeof(RootReferencingEntity), e => ((RootReferencingEntity?)e)?.Id }, - - { typeof(ValueRootEntity), e => ((ValueRootEntity?)e)?.Id }, - { typeof(ValueRelatedType), e => ((ValueRelatedType?)e)?.Id }, - { typeof(ValueNestedType), e => ((ValueNestedType?)e)?.Id }, - }.ToDictionary(e => e.Key, e => (object)e.Value); + { typeof(RootEntity), object? (RootEntity e) => ((RootEntity?)e)?.Id }, + { typeof(RelatedType), object? (RelatedType e) => ((RelatedType?)e)?.Id }, + { typeof(NestedType), object? (NestedType e) => ((NestedType?)e)?.Id }, + { typeof(RootReferencingEntity), object? (RootReferencingEntity e) => ((RootReferencingEntity?)e)?.Id }, + + { typeof(ValueRootEntity), object? (ValueRootEntity e) => ((ValueRootEntity?)e)?.Id }, + { typeof(ValueRelatedType), object? (ValueRelatedType e) => e.Id }, + { typeof(ValueRelatedType?), object? (ValueRelatedType? e) => e?.Id }, + { typeof(ValueNestedType), object? (ValueNestedType e) => e.Id }, + { typeof(ValueNestedType?), object? (ValueNestedType? e) => e?.Id } + }.ToDictionary(e => e.Key, e => e.Value); public virtual IReadOnlyDictionary EntityAsserters { get; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs index 3882772fb41..a065d0f8017 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs @@ -82,6 +82,70 @@ WHERE CAST(JSON_VALUE([r].[RequiredRelated], '$.RequiredNested.Int') AS int) = 8 #endregion Simple filters + #region Value types + + public override async Task Where_property_on_non_nullable_value_type() + { + await base.Where_property_on_non_nullable_value_type(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated], [v].[RelatedCollection], [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +WHERE JSON_VALUE([v].[RequiredRelated], '$.Int' RETURNING int) = 8 +"""); + } + else + { + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated], [v].[RelatedCollection], [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +WHERE CAST(JSON_VALUE([v].[RequiredRelated], '$.Int') AS int) = 8 +"""); + } + } + + public override async Task Where_property_on_nullable_value_type_Value() + { + await base.Where_property_on_nullable_value_type_Value(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated], [v].[RelatedCollection], [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +WHERE JSON_VALUE([v].[OptionalRelated], '$.Int' RETURNING int) = 8 +"""); + } + else + { + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated], [v].[RelatedCollection], [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +WHERE CAST(JSON_VALUE([v].[OptionalRelated], '$.Int') AS int) = 8 +"""); + } + } + + public override async Task Where_HasValue_on_nullable_value_type() + { + await base.Where_HasValue_on_nullable_value_type(); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated], [v].[RelatedCollection], [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +WHERE [v].[OptionalRelated] IS NOT NULL +"""); + } + + #endregion Value types + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs index 98414e0abfb..0130684b9c0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs @@ -349,6 +349,42 @@ FROM [ValueRootEntity] AS [v] """); } + public override async Task Select_non_nullable_value_type(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_non_nullable_value_type(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +ORDER BY [v].[Id] +"""); + } + + public override async Task Select_nullable_value_type(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[OptionalRelated] +FROM [ValueRootEntity] AS [v] +ORDER BY [v].[Id] +"""); + } + + public override async Task Select_nullable_value_type_with_Value(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_with_Value(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[OptionalRelated] +FROM [ValueRootEntity] AS [v] +ORDER BY [v].[Id] +"""); + } + #endregion Value types [ConditionalFact] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs index c5b5eef08a0..f940c74799a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs @@ -258,6 +258,22 @@ WHERE JSON_QUERY([r].[RequiredRelated], '$.NestedCollection') = @entity_equality } } + #region Value types + + public override async Task Nullable_value_type_with_null() + { + await base.Nullable_value_type_with_null(); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated], [v].[RelatedCollection], [v].[RequiredRelated] +FROM [ValueRootEntity] AS [v] +WHERE [v].[OptionalRelated] IS NULL +"""); + } + + #endregion Value types + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs index 67f0df5075f..b783447ee89 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs @@ -44,6 +44,46 @@ FROM [RootEntity] AS [r] """); } + #region Value types + + public override async Task Where_property_on_non_nullable_value_type() + { + await base.Where_property_on_non_nullable_value_type(); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String], [v].[RequiredRelated_Id], [v].[RequiredRelated_Int], [v].[RequiredRelated_Name], [v].[RequiredRelated_String], [v].[RequiredRelated_OptionalNested_Id], [v].[RequiredRelated_OptionalNested_Int], [v].[RequiredRelated_OptionalNested_Name], [v].[RequiredRelated_OptionalNested_String], [v].[RequiredRelated_RequiredNested_Id], [v].[RequiredRelated_RequiredNested_Int], [v].[RequiredRelated_RequiredNested_Name], [v].[RequiredRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +WHERE [v].[RequiredRelated_Int] = 8 +"""); + } + + public override async Task Where_property_on_nullable_value_type_Value() + { + await base.Where_property_on_nullable_value_type_Value(); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String], [v].[RequiredRelated_Id], [v].[RequiredRelated_Int], [v].[RequiredRelated_Name], [v].[RequiredRelated_String], [v].[RequiredRelated_OptionalNested_Id], [v].[RequiredRelated_OptionalNested_Int], [v].[RequiredRelated_OptionalNested_Name], [v].[RequiredRelated_OptionalNested_String], [v].[RequiredRelated_RequiredNested_Id], [v].[RequiredRelated_RequiredNested_Int], [v].[RequiredRelated_RequiredNested_Name], [v].[RequiredRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +WHERE [v].[OptionalRelated_Int] = 8 +"""); + } + + public override async Task Where_HasValue_on_nullable_value_type() + { + await base.Where_HasValue_on_nullable_value_type(); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String], [v].[RequiredRelated_Id], [v].[RequiredRelated_Int], [v].[RequiredRelated_Name], [v].[RequiredRelated_String], [v].[RequiredRelated_OptionalNested_Id], [v].[RequiredRelated_OptionalNested_Int], [v].[RequiredRelated_OptionalNested_Name], [v].[RequiredRelated_OptionalNested_String], [v].[RequiredRelated_RequiredNested_Id], [v].[RequiredRelated_RequiredNested_Int], [v].[RequiredRelated_RequiredNested_Name], [v].[RequiredRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +WHERE [v].[OptionalRelated_Id] IS NOT NULL +"""); + } + + #endregion Value types + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs index 038a00678a0..419967c9d76 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs @@ -241,6 +241,57 @@ ORDER BY [r0].[Id] """); } + #region Value types + + public override async Task Select_root_with_value_types(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_root_with_value_types(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String], [v].[RequiredRelated_Id], [v].[RequiredRelated_Int], [v].[RequiredRelated_Name], [v].[RequiredRelated_String], [v].[RequiredRelated_OptionalNested_Id], [v].[RequiredRelated_OptionalNested_Int], [v].[RequiredRelated_OptionalNested_Name], [v].[RequiredRelated_OptionalNested_String], [v].[RequiredRelated_RequiredNested_Id], [v].[RequiredRelated_RequiredNested_Int], [v].[RequiredRelated_RequiredNested_Name], [v].[RequiredRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +"""); + } + + public override async Task Select_non_nullable_value_type(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_non_nullable_value_type(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[RequiredRelated_Id], [v].[RequiredRelated_Int], [v].[RequiredRelated_Name], [v].[RequiredRelated_String], [v].[RequiredRelated_OptionalNested_Id], [v].[RequiredRelated_OptionalNested_Int], [v].[RequiredRelated_OptionalNested_Name], [v].[RequiredRelated_OptionalNested_String], [v].[RequiredRelated_RequiredNested_Id], [v].[RequiredRelated_RequiredNested_Int], [v].[RequiredRelated_RequiredNested_Name], [v].[RequiredRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +ORDER BY [v].[Id] +"""); + } + + public override async Task Select_nullable_value_type(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +ORDER BY [v].[Id] +"""); + } + + public override async Task Select_nullable_value_type_with_Value(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_with_Value(queryTrackingBehavior); + + AssertSql( + """ +SELECT [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +ORDER BY [v].[Id] +"""); + } + + #endregion Value types + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs index dc2c4c98ed2..347c712ae4d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs @@ -130,6 +130,22 @@ public override async Task Nested_collection_with_parameter() AssertSql(); } + #region Value types + + public override async Task Nullable_value_type_with_null() + { + await base.Nullable_value_type_with_null(); + + AssertSql( + """ +SELECT [v].[Id], [v].[Name], [v].[OptionalRelated_Id], [v].[OptionalRelated_Int], [v].[OptionalRelated_Name], [v].[OptionalRelated_String], [v].[OptionalRelated_OptionalNested_Id], [v].[OptionalRelated_OptionalNested_Int], [v].[OptionalRelated_OptionalNested_Name], [v].[OptionalRelated_OptionalNested_String], [v].[OptionalRelated_RequiredNested_Id], [v].[OptionalRelated_RequiredNested_Int], [v].[OptionalRelated_RequiredNested_Name], [v].[OptionalRelated_RequiredNested_String], [v].[RequiredRelated_Id], [v].[RequiredRelated_Int], [v].[RequiredRelated_Name], [v].[RequiredRelated_String], [v].[RequiredRelated_OptionalNested_Id], [v].[RequiredRelated_OptionalNested_Int], [v].[RequiredRelated_OptionalNested_Name], [v].[RequiredRelated_OptionalNested_String], [v].[RequiredRelated_RequiredNested_Id], [v].[RequiredRelated_RequiredNested_Int], [v].[RequiredRelated_RequiredNested_Name], [v].[RequiredRelated_RequiredNested_String] +FROM [ValueRootEntity] AS [v] +WHERE [v].[OptionalRelated_Id] IS NULL +"""); + } + + #endregion Value types + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs index 78ce7f94413..3c8ce4cdf4a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs @@ -4,145 +4,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexJson; public class ComplexJsonStructuralEqualitySqliteTest(ComplexJsonSqliteFixture fixture, ITestOutputHelper testOutputHelper) - : ComplexJsonStructuralEqualityRelationalTestBase(fixture, testOutputHelper) -{ - public override async Task Two_related() - { - await base.Two_related(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" = "r"."OptionalRelated" -"""); - } - - public override async Task Two_nested() - { - await base.Two_nested(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'RequiredNested' = "r"."OptionalRelated" ->> 'RequiredNested' -"""); - } - - public override async Task Not_equals() - { - await base.Not_equals(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" <> "r"."OptionalRelated" OR "r"."OptionalRelated" IS NULL -"""); - } - - public override async Task Related_with_inline_null() - { - await base.Related_with_inline_null(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."OptionalRelated" IS NULL -"""); - } - - public override async Task Related_with_parameter_null() - { - await base.Related_with_parameter_null(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."OptionalRelated" IS NULL -"""); - } - - public override async Task Nested_with_inline_null() - { - await base.Nested_with_inline_null(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'OptionalNested' IS NULL -"""); - } - - public override async Task Nested_with_inline() - { - await base.Nested_with_inline(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'RequiredNested' = '{"Id":1000,"Int":8,"Name":"Root1_RequiredRelated_RequiredNested","String":"foo"}' -"""); - } - - public override async Task Nested_with_parameter() - { - await base.Nested_with_parameter(); - - AssertSql( - """ -@entity_equality_nested='?' (Size = 80) - -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'RequiredNested' = @entity_equality_nested -"""); - } - - public override async Task Two_nested_collections() - { - await base.Two_nested_collections(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'NestedCollection' = "r"."OptionalRelated" ->> 'NestedCollection' -"""); - } - - public override async Task Nested_collection_with_inline() - { - await base.Nested_collection_with_inline(); - - AssertSql( - """ -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'NestedCollection' = '[{"Id":1002,"Int":8,"Name":"Root1_RequiredRelated_NestedCollection_1","String":"foo"},{"Id":1003,"Int":8,"Name":"Root1_RequiredRelated_NestedCollection_2","String":"foo"}]' -"""); - } - - public override async Task Nested_collection_with_parameter() - { - await base.Nested_collection_with_parameter(); - - AssertSql( - """ -@entity_equality_nestedCollection='?' (Size = 171) - -SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" -FROM "RootEntity" AS "r" -WHERE "r"."RequiredRelated" ->> 'NestedCollection' = @entity_equality_nestedCollection -"""); - } - - [ConditionalFact] - public virtual void Check_all_tests_overridden() - => TestHelpers.AssertAllMethodsOverridden(GetType()); -} + : ComplexJsonStructuralEqualityRelationalTestBase(fixture, testOutputHelper);