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);