diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index a82b0b7286e..6ad7fec2d3c 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -53,20 +53,6 @@ public static string BadSequenceType public static string CannotChangeWhenOpen => GetString("CannotChangeWhenOpen"); - /// - /// Comparing complex types to null is not supported. - /// - public static string CannotCompareComplexTypeToNull - => GetString("CannotCompareComplexTypeToNull"); - - /// - /// You are attempting to project out complex type '{complexType}' via an optional navigation; that is currently not supported. Either project out the complex type in a non-optional context, or project the containing entity type along with the complex type. - /// - public static string CannotProjectNullableComplexType(object? complexType) - => string.Format( - GetString("CannotProjectNullableComplexType", nameof(complexType)), - complexType); - /// /// Join expressions have no aliases; set the alias on the enclosed table expression. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 75e42be0127..5be7983829e 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -130,12 +130,6 @@ The instance of DbConnection is currently in use. The connection can only be changed when the existing connection is not being used. - - Comparing complex types to null is not supported. - - - You are attempting to project out complex type '{complexType}' via an optional navigation; that is currently not supported. Either project out the complex type in a non-optional context, or project the containing entity type along with the complex type. - Join expressions have no aliases; set the alias on the enclosed table expression. diff --git a/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs b/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs index 43400fe11c1..b61477d3aad 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalStructuralTypeMaterializerSource.cs @@ -1,9 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. + namespace Microsoft.EntityFrameworkCore.Query.Internal; -#pragma warning disable EF1001 // EntityMaterializerSource is pubternal +#pragma warning disable EF1001 // StructuralTypeMaterializerSource is pubternal /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -15,39 +16,9 @@ public class RelationalStructuralTypeMaterializerSource(StructuralTypeMaterializ : StructuralTypeMaterializerSource(dependencies) { /// - /// 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. - /// - protected override void AddInitializeExpression( - IPropertyBase property, - ParameterBindingInfo bindingInfo, - Expression instanceVariable, - MethodCallExpression valueBufferExpression, - List blockExpressions) - { - // JSON complex properties are not handled in the initial materialization expression, since they're not - // simply e.g. DbDataReader.GetFieldValue<>() calls. So they're handled afterwards in the shaper, and need - // to be skipped here. - if (property is IComplexProperty { ComplexType: var complexType } && complexType.IsMappedToJson()) - { - return; - } - - base.AddInitializeExpression(property, bindingInfo, instanceVariable, valueBufferExpression, blockExpressions); - } - - /// - /// 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. + /// JSON complex properties are not handled in the initial materialization expression, + /// since they're not simply e.g. DbDataReader.GetFieldValue calls. + /// So they're handled afterwards in the shaper, and need to be skipped. /// - [EntityFrameworkInternal] - public static readonly MethodInfo MaterializeJsonComplexTypeMethod - = typeof(RelationalStructuralTypeMaterializerSource).GetTypeInfo().GetDeclaredMethod(nameof(MaterializeJsonComplexType))!; - - private static T MaterializeJsonComplexType(in ValueBuffer valueBuffer, IComplexProperty complexProperty) - => throw new UnreachableException(); + protected override bool ReadComplexTypeDirectly(IComplexType complexType) => !complexType.IsMappedToJson(); } diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs index f199ebc0d41..93491a9102e 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs @@ -311,15 +311,6 @@ bool TryRewriteComplexTypeEquality(bool collection, [NotNullWhen(true)] out SqlE Check.DebugAssert(complexType != null, "We checked that at least one side is a complex type before calling this function"); - // Comparison to null needs to be handled in a special way for table splitting, but for JSON mapping is handled via - // the regular JSON flow below. - if ((IsNullSqlConstantExpression(left) || IsNullSqlConstantExpression(right)) && !complexType.IsMappedToJson()) - { - // TODO: when we support optional complex types with table splitting - or projecting required complex types via optional - // navigations - we'll be able to translate this, #31376 - throw new InvalidOperationException(RelationalStrings.CannotCompareComplexTypeToNull); - } - // If a complex type is the result of a subquery, then comparing its columns would mean duplicating the subquery, which would // be potentially very inefficient. // TODO: Enable this by extracting the subquery out to a common table expressions (WITH), #31237 diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 4f96e4f6eb2..eef7e91c6a5 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -492,12 +492,6 @@ public void ApplyProjection() void AddStructuralTypeProjection(StructuralTypeProjectionExpression projection) { - if (_projection.Count == 0 - && projection is { StructuralType: IComplexType complexType, IsNullable: true }) - { - throw new InvalidOperationException(RelationalStrings.CannotProjectNullableComplexType(complexType.DisplayName())); - } - ProcessTypeProjection(projection); void ProcessTypeProjection(StructuralTypeProjectionExpression projection) @@ -1367,11 +1361,6 @@ Expression CopyProjectionToOuter(SelectExpression innerSelectExpression, Express ConstantExpression AddStructuralTypeProjection(StructuralTypeProjectionExpression projection) { - if (projection is { StructuralType: IComplexType complexType, IsNullable: true }) - { - throw new InvalidOperationException(RelationalStrings.CannotProjectNullableComplexType(complexType.DisplayName())); - } - // JSON entity that had some query operations applied on it - it has been converted to a query root via OPENJSON/json_each // so it requires different materialization path than regular entity // e.g. we need to also add all the child navigations, JSON entity builds all the includes as part of it's own materializer diff --git a/src/EFCore/Query/EntityMaterializerSourceParameters.cs b/src/EFCore/Query/EntityMaterializerSourceParameters.cs index 88677025bbf..659e5606bbc 100644 --- a/src/EFCore/Query/EntityMaterializerSourceParameters.cs +++ b/src/EFCore/Query/EntityMaterializerSourceParameters.cs @@ -8,12 +8,14 @@ namespace Microsoft.EntityFrameworkCore.Query; /// /// The entity or complex type being materialized. /// The name of the instance being materialized. +/// Whether nullable result is allowed. /// /// The query tracking behavior, or if this materialization is not from a query. /// public readonly record struct StructuralTypeMaterializerSourceParameters( ITypeBase StructuralType, string InstanceName, + bool? AllowNullable, QueryTrackingBehavior? QueryTrackingBehavior); /// diff --git a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs index 2745b19b62d..5c98ed229d9 100644 --- a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs +++ b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs @@ -95,28 +95,46 @@ public Expression CreateMaterializeExpression( properties.Remove(consumedProperty); } - var constructorExpression = constructorBinding.CreateConstructorExpression(bindingInfo); - - if (_materializationInterceptor == null - // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types - // don't get intercepted. #35883 - || structuralType is not IEntityType) + var materializationExpression = HandleMaterializationInterception(); + + return + structuralType is IComplexType complexType && ReadComplexTypeDirectly(complexType) + && (IsNullable(complexType) || parameters.AllowNullable == true) + ? HandleNullableComplexTypeMaterialization( + complexType, + complexType.ClrType, + materializationExpression, + bindingInfo) + : materializationExpression; + + Expression HandleMaterializationInterception() { - return properties.Count == 0 && blockExpressions.Count == 0 - ? constructorExpression - : CreateMaterializeExpression(blockExpressions, instanceVariable, constructorExpression, properties, bindingInfo); + var constructorExpression = constructorBinding.CreateConstructorExpression(bindingInfo); + + return _materializationInterceptor == null + // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types + // don't get intercepted. #35883 + || structuralType is not IEntityType + ? properties.Count == 0 && blockExpressions.Count == 0 + ? constructorExpression + : CreateMaterializeExpression(blockExpressions, instanceVariable, constructorExpression, properties, bindingInfo) + : CreateInterceptionMaterializeExpression( + structuralType, + properties, + _materializationInterceptor, + bindingInfo, + constructorExpression, + instanceVariable, + blockExpressions); } - - return CreateInterceptionMaterializeExpression( - structuralType, - properties, - _materializationInterceptor, - bindingInfo, - constructorExpression, - instanceVariable, - blockExpressions); } + /// + /// Should complex type be read directly using e.g. DbDataReader.GetFieldValue + /// or is it going to be handled separately (i.e. relational JSON). + /// + protected virtual bool ReadComplexTypeDirectly(IComplexType complexType) => true; + /// /// 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 @@ -130,11 +148,17 @@ protected virtual void AddInitializeExpression( MethodCallExpression valueBufferExpression, List blockExpressions) { + if (property is IComplexProperty cp && !ReadComplexTypeDirectly(cp.ComplexType)) + { + return; + } + var memberInfo = property.GetMemberInfo(forMaterialization: true, forSet: true); var valueExpression = property switch { - IProperty p => valueBufferExpression.CreateValueBufferReadValueExpression(memberInfo.GetMemberType(), p.GetIndex(), p), + IProperty p + => valueBufferExpression.CreateValueBufferReadValueExpression(memberInfo.GetMemberType(), p.GetIndex(), p), IServiceProperty serviceProperty => serviceProperty.ParameterBinding.BindToParameter(bindingInfo), @@ -143,9 +167,7 @@ IServiceProperty serviceProperty => Default(complexProperty.ClrType), // Initialize collections to null, they'll be populated separately IComplexProperty complexProperty - => CreateMaterializeExpression( - new StructuralTypeMaterializerSourceParameters(complexProperty.ComplexType, "complexType", QueryTrackingBehavior: null), - bindingInfo.MaterializationContextExpression), + => CreateComplexTypeMaterializeExpression(complexProperty, bindingInfo), _ => throw new UnreachableException() }; @@ -193,6 +215,21 @@ static Expression CreateMemberAssignment(Expression parameter, MemberInfo member value) : MakeMemberAccess(parameter, memberInfo).Assign(value); } + + Expression CreateComplexTypeMaterializeExpression(IComplexProperty complexProperty, ParameterBindingInfo bindingInfo) + { + var materializeExpression = CreateMaterializeExpression( + new StructuralTypeMaterializerSourceParameters(complexProperty.ComplexType, "complexType", null, QueryTrackingBehavior: null), + bindingInfo.MaterializationContextExpression); + + return IsNullable(complexProperty) + ? HandleNullableComplexTypeMaterialization( + complexProperty.ComplexType, + complexProperty.ClrType, + materializeExpression, + bindingInfo) + : materializeExpression; + } } private void AddInitializeExpressions( @@ -493,15 +530,14 @@ BlockExpression CreateInitializeExpression() /// 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 Func GetMaterializer( - IEntityType entityType) + public virtual Func GetMaterializer(IEntityType entityType) { var materializationContextParameter = Parameter(typeof(MaterializationContext), "materializationContext"); return Lambda>( ((IStructuralTypeMaterializerSource)this).CreateMaterializeExpression( - new StructuralTypeMaterializerSourceParameters(entityType, "instance", null), materializationContextParameter), + new StructuralTypeMaterializerSourceParameters(entityType, "instance", null, null), materializationContextParameter), materializationContextParameter) .Compile(); } @@ -518,7 +554,7 @@ public virtual Func GetMaterializer(IComplexType return Lambda>( ((IStructuralTypeMaterializerSource)this).CreateMaterializeExpression( - new StructuralTypeMaterializerSourceParameters(complexType, "instance", null), materializationContextParameter), + new StructuralTypeMaterializerSourceParameters(complexType, "instance", null, null), materializationContextParameter), materializationContextParameter) .Compile(); } @@ -572,7 +608,7 @@ public virtual Func GetEmptyMaterializer( var materializationContextExpression = Parameter(typeof(MaterializationContext), "mc"); var bindingInfo = new ParameterBindingInfo( - new StructuralTypeMaterializerSourceParameters(entityType, "instance", null), materializationContextExpression); + new StructuralTypeMaterializerSourceParameters(entityType, "instance", null, null), materializationContextExpression); var blockExpressions = new List(); var instanceVariable = Variable(binding.RuntimeType, "instance"); @@ -644,4 +680,50 @@ private static void CreateServiceInstances( } } } + + private Expression HandleNullableComplexTypeMaterialization(IComplexType complexType, Type clrType, Expression materializeExpression, ParameterBindingInfo bindingInfo) + { + var valueBufferExpression = Call( + bindingInfo.MaterializationContextExpression, + MaterializationContext.GetValueBufferMethod); + + // Get all scalar properties of the complex type (including nested ones). + var allScalarProperties = complexType.GetFlattenedProperties().ToList(); + + if (allScalarProperties is []) + { + // If no scalar properties, just create the instance. + return CreateMaterializeExpression( + new StructuralTypeMaterializerSourceParameters(complexType, "complexType", null, QueryTrackingBehavior: null), + bindingInfo.MaterializationContextExpression); + } + + var requiredProperty = allScalarProperties.Where(p => !p.IsNullable).FirstOrDefault(); + var nullCheck = requiredProperty is not null + // If there's a required property, it's enough to check just that one for null. + ? Equal( + valueBufferExpression.CreateValueBufferReadValueExpression(typeof(object), requiredProperty.GetIndex(), requiredProperty), + Constant(null, typeof(object))) + // Create null checks for all scalar properties. + : allScalarProperties + .Select(p => + Equal( + valueBufferExpression.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p), + Constant(null, typeof(object)))) + .Aggregate(AndAlso); + + // If property/properties are null, return default (to handle structs); otherwise materialize the complex type. + return Condition( + nullCheck, + Default(clrType), + materializeExpression); + } + + private static bool IsNullable(IComplexType complexType) + => IsNullable(complexType.ComplexProperty); + + private static bool IsNullable(IComplexProperty complexProperty) + => complexProperty.IsNullable + || (complexProperty.DeclaringType is IComplexType complexType + && IsNullable(complexType.ComplexProperty)); } diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs index bf13c3e75c8..3dba903bf1f 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs @@ -659,7 +659,7 @@ private Expression MaterializeEntity( { var concreteStructuralType = concreteStructuralTypes[i]; switchCases[i] = SwitchCase( - CreateFullMaterializeExpression(concreteStructuralTypes[i], expressionContext), + CreateFullMaterializeExpression(concreteStructuralTypes[i], shaper.IsNullable, expressionContext), supportsPrecompiledQuery ? liftableConstantFactory.CreateLiftableConstant( concreteStructuralTypes[i], @@ -712,6 +712,7 @@ private Expression MaterializeEntity( private BlockExpression CreateFullMaterializeExpression( ITypeBase concreteStructuralType, + bool shaperIsNullable, (Type ReturnType, ParameterExpression MaterializationContextVariable, ParameterExpression ConcreteEntityTypeVariable, @@ -727,7 +728,7 @@ private BlockExpression CreateFullMaterializeExpression( var materializer = materializerSource .CreateMaterializeExpression( new StructuralTypeMaterializerSourceParameters( - concreteStructuralType, "instance", queryTrackingBehavior), materializationContextVariable); + concreteStructuralType, "instance", shaperIsNullable, queryTrackingBehavior), materializationContextVariable); // TODO: Properly support shadow properties for complex types #35613 if (_queryStateManager diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs index aef5c28ad88..c83866193b0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs @@ -190,6 +190,10 @@ FROM root c } } + [ConditionalTheory(Skip = "This type of projection does not make sense for Cosmos.")] + public override Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + => base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + #endregion Non-collection #region Collection diff --git a/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs index 9482eae1632..e9cd422bb42 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ComplexTypeQueryRelationalTestBase.cs @@ -39,15 +39,6 @@ public override async Task Union_two_different_complex_type(bool async) AssertSql(); } - public override async Task Complex_type_equals_null(bool async) - { - var exception = await Assert.ThrowsAsync(() => base.Complex_type_equals_null(async)); - - Assert.Equal(RelationalStrings.CannotCompareComplexTypeToNull, exception.Message); - - AssertSql(); - } - public override async Task Subquery_over_struct_complex_type(bool async) { var exception = await Assert.ThrowsAsync(() => base.Subquery_over_struct_complex_type(async)); diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousRelationalTestBase.cs index 54ec7285792..b781ff85492 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousRelationalTestBase.cs @@ -15,10 +15,6 @@ public ComplexTableSplittingMiscellaneousRelationalTestBase(TFixture fixture, IT fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - // TODO: Optional complex properties not yet supported (#31376) - public override Task Where_optional_related_property() - => AssertTranslationFailed(() => base.Where_optional_related_property()); - protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } 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 3ff62477ccd..7e7bd3ec641 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs @@ -16,31 +16,27 @@ public ComplexTableSplittingProjectionRelationalTestBase(TFixture fixture, ITest Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - // Complex JSON collections, update pipeline not yet supported so no seeding, #31237 + // Collections are not supported with table splitting, only JSON public override Task Select_related_collection(QueryTrackingBehavior queryTrackingBehavior) => Assert.ThrowsAsync(() => base.Select_related_collection(queryTrackingBehavior)); - // Complex JSON collections, update pipeline not yet supported so no seeding, #31237 + // Collections are not supported with table splitting, only JSON public override Task Select_nested_collection_on_required_related(QueryTrackingBehavior queryTrackingBehavior) => Assert.ThrowsAsync(() => base.Select_nested_collection_on_required_related(queryTrackingBehavior)); - // Optional complex types, #31376 - public override Task Select_optional_nested_on_required_related(QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.Select_optional_nested_on_required_related(queryTrackingBehavior)); + // Collections are not supported with table splitting, only JSON + public override Task Select_nested_collection_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) + => Assert.ThrowsAsync(() => base.Select_nested_collection_on_optional_related(queryTrackingBehavior)); - // Optional complex types, #31376 - public override Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior)); - - // Complex JSON collections, update pipeline not yet supported so no seeding, #31237 + // Collections are not supported with table splitting, only JSON public override Task SelectMany_related_collection(QueryTrackingBehavior queryTrackingBehavior) => Assert.ThrowsAsync(() => base.SelectMany_related_collection(queryTrackingBehavior)); - // Optional complex types, #31376 + // Collections are not supported with table splitting, only JSON public override Task SelectMany_nested_collection_on_required_related(QueryTrackingBehavior queryTrackingBehavior) => Assert.ThrowsAsync(() => base.SelectMany_nested_collection_on_required_related(queryTrackingBehavior)); - // Optional complex types, #31376 + // Collections are not supported with table splitting, only JSON public override Task SelectMany_nested_collection_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) => Assert.ThrowsAsync(() => base.SelectMany_nested_collection_on_optional_related(queryTrackingBehavior)); diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingRelationalFixtureBase.cs index b032125a503..ce92afb4449 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingRelationalFixtureBase.cs @@ -17,20 +17,6 @@ public abstract class ComplexTableSplittingRelationalFixtureBase : ComplexProper { protected override string StoreName => "ComplexTableSplittingQueryTest"; - protected override RelationshipsData CreateData() - { - var data = new RelationshipsData(); - - // TODO: Optional complex properties not yet supported (#31376), remove them from the seeding data - foreach (var rootEntity in data.RootEntities) - { - rootEntity.OptionalRelated = null; - rootEntity.RequiredRelated.OptionalNested = null; - } - - return data; - } - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); @@ -40,16 +26,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.ComplexProperty(e => e.RequiredRelated, rrb => { rrb.ComplexProperty(r => r.RequiredNested); - - // TODO: Optional complex properties not yet supported: #31376 - rrb.Ignore(r => r.OptionalNested); + rrb.ComplexProperty(r => r.OptionalNested); // Collections are not supported with table splitting, only JSON rrb.Ignore(r => r.NestedCollection); }); - // TODO: Optional complex properties not yet supported: #31376 - b.Ignore(r => r.OptionalRelated); + b.ComplexProperty(e => e.OptionalRelated, orb => + { + orb.ComplexProperty(o => o.RequiredNested); + orb.ComplexProperty(o => o.OptionalNested); + + // Collections are not supported with table splitting, only JSON + orb.Ignore(o => o.NestedCollection); + }); // Collections are not supported with table splitting, only JSON b.Ignore(r => r.RelatedCollection); diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs index 852dd8202d4..27b8b2ef261 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs @@ -15,36 +15,17 @@ public ComplexTableSplittingStructuralEqualityRelationalTestBase(TFixture fixtur Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - // TODO: All the tests below rely on access OptionalRelated, but optional complex properties not yet supported (#31376) - - public override Task Two_related() - => Assert.ThrowsAsync(() => base.Two_related()); - - public override Task Two_nested() - => Assert.ThrowsAsync(() => base.Two_nested()); - - public override Task Not_equals() - => Assert.ThrowsAsync(() => base.Not_equals()); - - public override Task Related_with_inline_null() - => Assert.ThrowsAsync(() => base.Related_with_inline_null()); - - public override Task Related_with_parameter_null() - => Assert.ThrowsAsync(() => base.Related_with_parameter_null()); - - public override Task Nested_with_inline_null() - => Assert.ThrowsAsync(() => base.Nested_with_inline_null()); - - public override Task Two_nested_collections() - => Assert.ThrowsAsync(() => base.Two_nested_collections()); + // Collections are not supported with table splitting, only JSON + public override Task Nested_collection_with_parameter() + => Assert.ThrowsAsync(base.Nested_collection_with_parameter); - // Collection equality with owned collections is not supported + // Collections are not supported with table splitting, only JSON public override Task Nested_collection_with_inline() - => Assert.ThrowsAsync(() => base.Nested_collection_with_inline()); + => Assert.ThrowsAsync(base.Nested_collection_with_inline); - // Collection equality with owned collections is not supported - public override Task Nested_collection_with_parameter() - => Assert.ThrowsAsync(() => base.Nested_collection_with_parameter()); + // Collections are not supported with table splitting, only JSON + public override Task Two_nested_collections() + => Assert.ThrowsAsync(base.Two_nested_collections); protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonProjectionRelationalTestBase.cs index 46c5807dd33..1a5b3d2ebd4 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonProjectionRelationalTestBase.cs @@ -15,6 +15,11 @@ public OwnedJsonProjectionRelationalTestBase(TFixture fixture, ITestOutputHelper fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + public override Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery( + queryTrackingBehavior, + () => base.Select_required_related_via_optional_navigation(queryTrackingBehavior)); + protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionRelationalTestBase.cs index b4c5a469d0d..97374646526 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionRelationalTestBase.cs @@ -14,6 +14,11 @@ public OwnedNavigationsProjectionRelationalTestBase(TFixture fixture, ITestOutpu Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + public override Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery( + queryTrackingBehavior, + () => base.Select_required_related_via_optional_navigation(queryTrackingBehavior)); + // Traditional relational collections navigations projected from null instances are returned as empty collections rather than null. // This is in contrast to client evaluation behavior - and also the JSON collection behavior - where we get null instance (coalescing). public override Task Select_nested_collection_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionRelationalTestBase.cs index f99d261232f..5dad021a7fe 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionRelationalTestBase.cs @@ -16,6 +16,11 @@ public OwnedTableSplittingProjectionRelationalTestBase(TFixture fixture, ITestOu Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + public override Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery( + queryTrackingBehavior, + () => base.Select_required_related_via_optional_navigation(queryTrackingBehavior)); + // Traditional relational collections navigations can't be compared reliably. // The failure below is because collections on from null instances are returned as empty collections rather than null; but // even disregarding that, elements in the collection don't preserve ordering and so can't be compared reliably. diff --git a/test/EFCore.Specification.Tests/Query/ComplexTypeQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/ComplexTypeQueryFixtureBase.cs index 7207c5fa501..857e0dd3ac2 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexTypeQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexTypeQueryFixtureBase.cs @@ -76,8 +76,8 @@ public ISetSource GetExpectedData() { typeof(ValuedCustomerGroup), (Func)(e => e.Id) }, // Complex types - still need comparers for cases where they are projected directly - { typeof(Address), (Func)(e => e.ZipCode) }, - { typeof(Country), (Func)(e => e.Code) }, + { typeof(Address), (Func)(e => e?.ZipCode ?? 0) }, + { typeof(Country), (Func)(e => e?.Code ?? string.Empty) }, { typeof(AddressStruct), (Func)(e => e.ZipCode) }, { typeof(CountryStruct), (Func)(e => e.Code) } }.ToDictionary(e => e.Key, e => e.Value); diff --git a/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs index 7248a7f96ac..489de9ba4fe 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexTypeQueryTestBase.cs @@ -67,7 +67,8 @@ public virtual Task Filter_on_required_property_inside_required_complex_type_on_ public virtual Task Project_complex_type_via_optional_navigation(bool async) => AssertQuery( async, - ss => ss.Set().Select(cg => cg.OptionalCustomer!.ShippingAddress)); + ss => ss.Set().Select(cg => cg.OptionalCustomer!.ShippingAddress), + ss => ss.Set().Select(cg => cg.OptionalCustomer != null ? cg.OptionalCustomer.ShippingAddress : default)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -172,13 +173,6 @@ public virtual Task Complex_type_equals_parameter(bool async) && c.ShippingAddress.Tags.SequenceEqual(new List { "foo", "bar" }))); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual Task Complex_type_equals_null(bool async) - => AssertQuery( - async, - ss => ss.Set().Where(c => c.ShippingAddress == null)); - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Subquery_over_complex_type(bool async) @@ -338,7 +332,8 @@ public virtual Task Filter_on_required_property_inside_required_struct_complex_t public virtual Task Project_struct_complex_type_via_optional_navigation(bool async) => AssertQuery( async, - ss => ss.Set().Select(cg => cg.OptionalCustomer!.ShippingAddress)); + ss => ss.Set().Select(cg => cg.OptionalCustomer!.ShippingAddress), + ss => ss.Set().Select(cg => cg.OptionalCustomer != null ? cg.OptionalCustomer.ShippingAddress : default)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs index 0c7a8777495..50f90b7c9ca 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs @@ -10,7 +10,7 @@ public RelationshipsData() RootEntities = CreateRootEntities(); RelatedTypes = []; NestedTypes = []; - PreRootEntities = []; + RootReferencingEntities = CreateRootReferencingEntities(RootEntities); } public List RootEntities { get; } @@ -19,7 +19,7 @@ public RelationshipsData() public List RelatedTypes { get; } public List NestedTypes { get; } - public List PreRootEntities { get; } + public List RootReferencingEntities { get; } public static List CreateRootEntities() { @@ -334,6 +334,23 @@ RootEntity CreateRootEntity(int id, string? description, Action? cus } } + public static List CreateRootReferencingEntities(IEnumerable rootEntities) + { + var rootReferencingEntities = new List(); + + var id = 1; + + rootReferencingEntities.Add(new() { Id = id++, Root = null }); + foreach (var rootEntity in rootEntities) + { + var rootReferencingEntity = new RootReferencingEntity { Id = id++, Root = rootEntity }; + rootEntity.RootReferencingEntity = rootReferencingEntity; + rootReferencingEntities.Add(rootReferencingEntity); + } + + return rootReferencingEntities; + } + public IQueryable Set() where TEntity : class => typeof(TEntity) switch @@ -341,7 +358,7 @@ public IQueryable Set() var t when t == typeof(RootEntity) => (IQueryable)RootEntities.AsQueryable(), var t when t == typeof(RelatedType) => (IQueryable)RelatedTypes.AsQueryable(), var t when t == typeof(NestedType) => (IQueryable)NestedTypes.AsQueryable(), - var t when t == typeof(RootReferencingEntity) => (IQueryable)PreRootEntities.AsQueryable(), + var t when t == typeof(RootReferencingEntity) => (IQueryable)RootReferencingEntities.AsQueryable(), _ => throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)) }; diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs index 3f7c53f65d8..efa3310802f 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs @@ -92,6 +92,13 @@ public virtual Task Select_optional_nested_on_optional_related(QueryTrackingBeha ss => ss.Set().Select(x => x.OptionalRelated!.OptionalNested), queryTrackingBehavior: queryTrackingBehavior); + [ConditionalTheory] + [MemberData(nameof(TrackingData))] + public virtual Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + => AssertQuery( + ss => ss.Set().Select(e => e.Root!.RequiredRelated), + queryTrackingBehavior: queryTrackingBehavior); + #endregion Non-collection #region Collection diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs index ad850c79138..680db19a12e 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsQueryFixtureBase.cs @@ -34,6 +34,10 @@ protected virtual RelationshipsData CreateData() protected override Task SeedAsync(PoolableDbContext context) { context.Set().AddRange(_data.RootEntities); + if (context.Model.FindEntityType(typeof(RootReferencingEntity)) is not null) + { + context.Set().AddRange(_data.RootReferencingEntities); + } return context.SaveChangesAsync(); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs index 776c0446f1e..7b31606e0e9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs @@ -107,14 +107,16 @@ WHERE [c0].[ShippingAddress_ZipCode] <> 7728 """); } - // This test fails because when OptionalCustomer is null, we get all-null results because of the LEFT JOIN, and we materialize this - // as an empty ShippingAddress instead of null (see SQL). The proper solution here would be to project the Customer ID just for the - // purpose of knowing that it's there. public override async Task Project_complex_type_via_optional_navigation(bool async) { - var exception = await Assert.ThrowsAsync(() => base.Project_complex_type_via_optional_navigation(async)); + await base.Project_complex_type_via_optional_navigation(async); - Assert.Equal(RelationalStrings.CannotProjectNullableComplexType("Customer.ShippingAddress#Address"), exception.Message); + AssertSql( + """ +SELECT [c0].[ShippingAddress_AddressLine1], [c0].[ShippingAddress_AddressLine2], [c0].[ShippingAddress_Tags], [c0].[ShippingAddress_ZipCode], [c0].[ShippingAddress_Country_Code], [c0].[ShippingAddress_Country_FullName] +FROM [CustomerGroup] AS [c] +LEFT JOIN [Customer] AS [c0] ON [c].[OptionalCustomerId] = [c0].[Id] +"""); } public override async Task Project_complex_type_via_required_navigation(bool async) @@ -245,13 +247,6 @@ FROM [Customer] AS [c] """); } - public override async Task Complex_type_equals_null(bool async) - { - await base.Complex_type_equals_null(async); - - AssertSql(); - } - public override async Task Subquery_over_complex_type(bool async) { await base.Subquery_over_complex_type(async); @@ -476,15 +471,16 @@ WHERE [v0].[ShippingAddress_ZipCode] <> 7728 """); } - // This test fails because when OptionalCustomer is null, we get all-null results because of the LEFT JOIN, and we materialize this - // as an empty ShippingAddress instead of null (see SQL). The proper solution here would be to project the Customer ID just for the - // purpose of knowing that it's there. public override async Task Project_struct_complex_type_via_optional_navigation(bool async) { - var exception = - await Assert.ThrowsAsync(() => base.Project_struct_complex_type_via_optional_navigation(async)); + await base.Project_struct_complex_type_via_optional_navigation(async); - Assert.Equal(RelationalStrings.CannotProjectNullableComplexType("ValuedCustomer.ShippingAddress#AddressStruct"), exception.Message); + AssertSql( + """ +SELECT [v0].[ShippingAddress_AddressLine1], [v0].[ShippingAddress_AddressLine2], [v0].[ShippingAddress_ZipCode], [v0].[ShippingAddress_Country_Code], [v0].[ShippingAddress_Country_FullName] +FROM [ValuedCustomerGroup] AS [v] +LEFT JOIN [ValuedCustomer] AS [v0] ON [v].[OptionalCustomerId] = [v0].[Id] +"""); } public override async Task Project_struct_complex_type_via_required_navigation(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs index fcb5ec2720a..791075c9dce 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs @@ -177,6 +177,18 @@ FROM [RootEntity] AS [r] """); } + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + + AssertSql( + """ +SELECT [r0].[RequiredRelated] +FROM [RootReferencingEntity] AS [r] +LEFT JOIN [RootEntity] AS [r0] ON [r].[RootEntityId] = [r0].[Id] +"""); + } + #endregion Non-collection #region Collection diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs index b0391f6d839..67f0df5075f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs @@ -8,15 +8,13 @@ public class ComplexTableSplittingMiscellaneousSqlServerTest( ITestOutputHelper testOutputHelper) : ComplexTableSplittingMiscellaneousRelationalTestBase(fixture, testOutputHelper) { - #region Simple filters - public override async Task Where_related_property() { await base.Where_related_property(); AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] WHERE [r].[RequiredRelated_Int] = 8 """); @@ -26,7 +24,12 @@ public override async Task Where_optional_related_property() { await base.Where_optional_related_property(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[OptionalRelated_Int] = 8 +"""); } public override async Task Where_nested_related_property() @@ -35,14 +38,12 @@ public override async Task Where_nested_related_property() AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] WHERE [r].[RequiredRelated_RequiredNested_Int] = 8 """); } - #endregion Simple filters - [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 cfa9adac8b6..038a00678a0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs @@ -8,19 +8,41 @@ public class ComplexTableSplittingProjectionSqlServerTest( ITestOutputHelper testOutputHelper) : ComplexTableSplittingProjectionRelationalTestBase(fixture, testOutputHelper) { + public override async Task Select_related_collection(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_related_collection(queryTrackingBehavior); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +ORDER BY [r].[Id] +"""); + } + + public override async Task Select_nested_collection_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nested_collection_on_required_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +ORDER BY [r].[Id] +"""); + } + public override async Task Select_root(QueryTrackingBehavior queryTrackingBehavior) { await base.Select_root(queryTrackingBehavior); AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] """); } - #region Simple properties - public override async Task Select_property_on_required_related(QueryTrackingBehavior queryTrackingBehavior) { await base.Select_property_on_required_related(queryTrackingBehavior); @@ -38,7 +60,7 @@ public override async Task Select_property_on_optional_related(QueryTrackingBeha AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[OptionalRelated_String] FROM [RootEntity] AS [r] """); } @@ -49,7 +71,7 @@ public override async Task Select_value_type_property_on_null_related_throws(Que AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[OptionalRelated_Int] FROM [RootEntity] AS [r] """); } @@ -60,22 +82,18 @@ public override async Task Select_nullable_value_type_property_on_null_related(Q AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[OptionalRelated_Int] FROM [RootEntity] AS [r] """); } - #endregion Simple properties - - #region Non-collection - public override async Task Select_related(QueryTrackingBehavior queryTrackingBehavior) { await base.Select_related(queryTrackingBehavior); AssertSql( """ -SELECT [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] """); } @@ -86,7 +104,7 @@ public override async Task Select_optional_related(QueryTrackingBehavior queryTr AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String] FROM [RootEntity] AS [r] """); } @@ -108,7 +126,7 @@ public override async Task Select_optional_nested_on_required_related(QueryTrack AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String] FROM [RootEntity] AS [r] """); } @@ -119,7 +137,7 @@ public override async Task Select_required_nested_on_optional_related(QueryTrack AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String] FROM [RootEntity] AS [r] """); } @@ -130,36 +148,20 @@ public override async Task Select_optional_nested_on_optional_related(QueryTrack AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] -FROM [RootEntity] AS [r] -"""); - } - - #endregion Non-collection - - #region Collection - - public override async Task Select_related_collection(QueryTrackingBehavior queryTrackingBehavior) - { - await base.Select_related_collection(queryTrackingBehavior); - - AssertSql( - """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String] FROM [RootEntity] AS [r] -ORDER BY [r].[Id] """); } - public override async Task Select_nested_collection_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_nested_collection_on_required_related(queryTrackingBehavior); + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] -FROM [RootEntity] AS [r] -ORDER BY [r].[Id] +SELECT [r0].[RequiredRelated_Id], [r0].[RequiredRelated_Int], [r0].[RequiredRelated_Name], [r0].[RequiredRelated_String], [r0].[RequiredRelated_OptionalNested_Id], [r0].[RequiredRelated_OptionalNested_Int], [r0].[RequiredRelated_OptionalNested_Name], [r0].[RequiredRelated_OptionalNested_String], [r0].[RequiredRelated_RequiredNested_Id], [r0].[RequiredRelated_RequiredNested_Int], [r0].[RequiredRelated_RequiredNested_Name], [r0].[RequiredRelated_RequiredNested_String] +FROM [RootReferencingEntity] AS [r] +LEFT JOIN [RootEntity] AS [r0] ON [r].[RootEntityId] = [r0].[Id] """); } @@ -169,7 +171,7 @@ public override async Task Select_nested_collection_on_optional_related(QueryTra AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] ORDER BY [r].[Id] """); @@ -196,30 +198,31 @@ public override async Task SelectMany_nested_collection_on_optional_related(Quer AssertSql(); } - #endregion Collection - - #region Multiple - public override async Task Select_root_duplicated(QueryTrackingBehavior queryTrackingBehavior) { await base.Select_root_duplicated(queryTrackingBehavior); AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] """); } - #endregion Multiple - - #region Subquery - public override async Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior) { await base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior); - AssertSql(); + AssertSql( + """ +SELECT [r1].[RequiredRelated_RequiredNested_Id], [r1].[RequiredRelated_RequiredNested_Int], [r1].[RequiredRelated_RequiredNested_Name], [r1].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +OUTER APPLY ( + SELECT TOP(1) [r0].[RequiredRelated_RequiredNested_Id], [r0].[RequiredRelated_RequiredNested_Int], [r0].[RequiredRelated_RequiredNested_Name], [r0].[RequiredRelated_RequiredNested_String] + FROM [RootEntity] AS [r0] + ORDER BY [r0].[Id] +) AS [r1] +"""); } public override async Task Select_subquery_optional_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior) @@ -228,18 +231,16 @@ public override async Task Select_subquery_optional_related_FirstOrDefault(Query AssertSql( """ -SELECT [r1].[Id], [r1].[Name], [r1].[RequiredRelated_Id], [r1].[RequiredRelated_Int], [r1].[RequiredRelated_Name], [r1].[RequiredRelated_String], [r1].[RequiredRelated_RequiredNested_Id], [r1].[RequiredRelated_RequiredNested_Int], [r1].[RequiredRelated_RequiredNested_Name], [r1].[RequiredRelated_RequiredNested_String], [r1].[c] +SELECT [r1].[OptionalRelated_RequiredNested_Id], [r1].[OptionalRelated_RequiredNested_Int], [r1].[OptionalRelated_RequiredNested_Name], [r1].[OptionalRelated_RequiredNested_String] FROM [RootEntity] AS [r] OUTER APPLY ( - SELECT TOP(1) [r0].[Id], [r0].[Name], [r0].[RequiredRelated_Id], [r0].[RequiredRelated_Int], [r0].[RequiredRelated_Name], [r0].[RequiredRelated_String], [r0].[RequiredRelated_RequiredNested_Id], [r0].[RequiredRelated_RequiredNested_Int], [r0].[RequiredRelated_RequiredNested_Name], [r0].[RequiredRelated_RequiredNested_String], 1 AS [c] + SELECT TOP(1) [r0].[OptionalRelated_RequiredNested_Id], [r0].[OptionalRelated_RequiredNested_Int], [r0].[OptionalRelated_RequiredNested_Name], [r0].[OptionalRelated_RequiredNested_String] FROM [RootEntity] AS [r0] ORDER BY [r0].[Id] ) AS [r1] """); } - #endregion Subquery - [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 9c85421e925..bc12ee0ef53 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs @@ -12,42 +12,72 @@ public override async Task Two_related() { await base.Two_related(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[RequiredRelated_Id] = [r].[OptionalRelated_Id] AND [r].[RequiredRelated_Int] = [r].[OptionalRelated_Int] AND [r].[RequiredRelated_Name] = [r].[OptionalRelated_Name] AND [r].[RequiredRelated_String] = [r].[OptionalRelated_String] AND ([r].[RequiredRelated_OptionalNested_Id] = [r].[RequiredRelated_OptionalNested_Id] OR [r].[RequiredRelated_OptionalNested_Id] IS NULL) AND ([r].[RequiredRelated_OptionalNested_Int] = [r].[RequiredRelated_OptionalNested_Int] OR [r].[RequiredRelated_OptionalNested_Int] IS NULL) AND ([r].[RequiredRelated_OptionalNested_Name] = [r].[RequiredRelated_OptionalNested_Name] OR [r].[RequiredRelated_OptionalNested_Name] IS NULL) AND ([r].[RequiredRelated_OptionalNested_String] = [r].[RequiredRelated_OptionalNested_String] OR [r].[RequiredRelated_OptionalNested_String] IS NULL) AND [r].[RequiredRelated_RequiredNested_Id] = [r].[RequiredRelated_RequiredNested_Id] AND [r].[RequiredRelated_RequiredNested_Int] = [r].[RequiredRelated_RequiredNested_Int] AND [r].[RequiredRelated_RequiredNested_Name] = [r].[RequiredRelated_RequiredNested_Name] AND [r].[RequiredRelated_RequiredNested_String] = [r].[RequiredRelated_RequiredNested_String] +"""); } public override async Task Two_nested() { await base.Two_nested(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[RequiredRelated_RequiredNested_Id] = [r].[OptionalRelated_RequiredNested_Id] AND [r].[RequiredRelated_RequiredNested_Int] = [r].[OptionalRelated_RequiredNested_Int] AND [r].[RequiredRelated_RequiredNested_Name] = [r].[OptionalRelated_RequiredNested_Name] AND [r].[RequiredRelated_RequiredNested_String] = [r].[OptionalRelated_RequiredNested_String] +"""); } public override async Task Not_equals() { await base.Not_equals(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[RequiredRelated_Id] <> [r].[OptionalRelated_Id] OR [r].[OptionalRelated_Id] IS NULL OR [r].[RequiredRelated_Int] <> [r].[OptionalRelated_Int] OR [r].[OptionalRelated_Int] IS NULL OR [r].[RequiredRelated_Name] <> [r].[OptionalRelated_Name] OR [r].[OptionalRelated_Name] IS NULL OR [r].[RequiredRelated_String] <> [r].[OptionalRelated_String] OR [r].[OptionalRelated_String] IS NULL OR (([r].[RequiredRelated_OptionalNested_Id] <> [r].[RequiredRelated_OptionalNested_Id] OR [r].[RequiredRelated_OptionalNested_Id] IS NULL) AND [r].[RequiredRelated_OptionalNested_Id] IS NOT NULL) OR (([r].[RequiredRelated_OptionalNested_Int] <> [r].[RequiredRelated_OptionalNested_Int] OR [r].[RequiredRelated_OptionalNested_Int] IS NULL) AND [r].[RequiredRelated_OptionalNested_Int] IS NOT NULL) OR (([r].[RequiredRelated_OptionalNested_Name] <> [r].[RequiredRelated_OptionalNested_Name] OR [r].[RequiredRelated_OptionalNested_Name] IS NULL) AND [r].[RequiredRelated_OptionalNested_Name] IS NOT NULL) OR (([r].[RequiredRelated_OptionalNested_String] <> [r].[RequiredRelated_OptionalNested_String] OR [r].[RequiredRelated_OptionalNested_String] IS NULL) AND [r].[RequiredRelated_OptionalNested_String] IS NOT NULL) OR [r].[RequiredRelated_RequiredNested_Id] <> [r].[RequiredRelated_RequiredNested_Id] OR [r].[RequiredRelated_RequiredNested_Id] IS NULL OR [r].[RequiredRelated_RequiredNested_Int] <> [r].[RequiredRelated_RequiredNested_Int] OR [r].[RequiredRelated_RequiredNested_Int] IS NULL OR [r].[RequiredRelated_RequiredNested_Name] <> [r].[RequiredRelated_RequiredNested_Name] OR [r].[RequiredRelated_RequiredNested_Name] IS NULL OR [r].[RequiredRelated_RequiredNested_String] <> [r].[RequiredRelated_RequiredNested_String] OR [r].[RequiredRelated_RequiredNested_String] IS NULL +"""); } public override async Task Related_with_inline_null() { await base.Related_with_inline_null(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[OptionalRelated_Id] IS NULL AND [r].[OptionalRelated_Int] IS NULL AND [r].[OptionalRelated_Name] IS NULL AND [r].[OptionalRelated_String] IS NULL AND [r].[OptionalRelated_OptionalNested_Id] IS NULL AND [r].[OptionalRelated_OptionalNested_Int] IS NULL AND [r].[OptionalRelated_OptionalNested_Name] IS NULL AND [r].[OptionalRelated_OptionalNested_String] IS NULL AND [r].[OptionalRelated_RequiredNested_Id] IS NULL AND [r].[OptionalRelated_RequiredNested_Int] IS NULL AND [r].[OptionalRelated_RequiredNested_Name] IS NULL AND [r].[OptionalRelated_RequiredNested_String] IS NULL +"""); } public override async Task Related_with_parameter_null() { await base.Related_with_parameter_null(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[OptionalRelated_Id] IS NULL AND [r].[OptionalRelated_Int] IS NULL AND [r].[OptionalRelated_Name] IS NULL AND [r].[OptionalRelated_String] IS NULL AND [r].[OptionalRelated_OptionalNested_Id] IS NULL AND [r].[OptionalRelated_OptionalNested_Int] IS NULL AND [r].[OptionalRelated_OptionalNested_Name] IS NULL AND [r].[OptionalRelated_OptionalNested_String] IS NULL AND [r].[OptionalRelated_RequiredNested_Id] IS NULL AND [r].[OptionalRelated_RequiredNested_Int] IS NULL AND [r].[OptionalRelated_RequiredNested_Name] IS NULL AND [r].[OptionalRelated_RequiredNested_String] IS NULL +"""); } public override async Task Nested_with_inline_null() { await base.Nested_with_inline_null(); - AssertSql(); + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +FROM [RootEntity] AS [r] +WHERE [r].[RequiredRelated_OptionalNested_Id] IS NULL AND [r].[RequiredRelated_OptionalNested_Int] IS NULL AND [r].[RequiredRelated_OptionalNested_Name] IS NULL AND [r].[RequiredRelated_OptionalNested_String] IS NULL +"""); } public override async Task Nested_with_inline() @@ -56,7 +86,7 @@ public override async Task Nested_with_inline() AssertSql( """ -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] WHERE [r].[RequiredRelated_RequiredNested_Id] = 1000 AND [r].[RequiredRelated_RequiredNested_Int] = 8 AND [r].[RequiredRelated_RequiredNested_Name] = N'Root1_RequiredRelated_RequiredNested' AND [r].[RequiredRelated_RequiredNested_String] = N'foo' """); @@ -73,7 +103,7 @@ public override async Task Nested_with_parameter() @entity_equality_nested_Name='?' (Size = 4000) @entity_equality_nested_String='?' (Size = 4000) -SELECT [r].[Id], [r].[Name], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [r].[OptionalRelated_OptionalNested_Id], [r].[OptionalRelated_OptionalNested_Int], [r].[OptionalRelated_OptionalNested_Name], [r].[OptionalRelated_OptionalNested_String], [r].[OptionalRelated_RequiredNested_Id], [r].[OptionalRelated_RequiredNested_Int], [r].[OptionalRelated_RequiredNested_Name], [r].[OptionalRelated_RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r].[RequiredRelated_OptionalNested_Id], [r].[RequiredRelated_OptionalNested_Int], [r].[RequiredRelated_OptionalNested_Name], [r].[RequiredRelated_OptionalNested_String], [r].[RequiredRelated_RequiredNested_Id], [r].[RequiredRelated_RequiredNested_Int], [r].[RequiredRelated_RequiredNested_Name], [r].[RequiredRelated_RequiredNested_String] FROM [RootEntity] AS [r] WHERE [r].[RequiredRelated_RequiredNested_Id] = @entity_equality_nested_Id AND [r].[RequiredRelated_RequiredNested_Int] = @entity_equality_nested_Int AND [r].[RequiredRelated_RequiredNested_Name] = @entity_equality_nested_Name AND [r].[RequiredRelated_RequiredNested_String] = @entity_equality_nested_String """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs index 6212b7a5b07..c04628b63d3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs @@ -147,6 +147,19 @@ FROM [RootEntity] AS [r] """); } + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + + AssertSql( + """ +SELECT [r1].[Id], [r1].[CollectionRootId], [r1].[Int], [r1].[Name], [r1].[OptionalNestedId], [r1].[RequiredNestedId], [r1].[String] +FROM [RootReferencingEntity] AS [r] +LEFT JOIN [RootEntity] AS [r0] ON [r].[RootEntityId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r0].[RequiredRelatedId] = [r1].[Id] +"""); + } + #endregion Non-collection #region Collection diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs index a1a8ccccd75..26bbb973581 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs @@ -151,6 +151,21 @@ FROM [RootEntity] AS [r] } } + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + + if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) + { + AssertSql( + """ +SELECT [r0].[RequiredRelated], [r0].[Id] +FROM [RootReferencingEntity] AS [r] +LEFT JOIN [RootEntity] AS [r0] ON [r].[RootEntityId] = [r0].[Id] +"""); + } + } + #endregion Non-collection #region Collection diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs index 13875b1d887..0ceb4db4021 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs @@ -189,6 +189,26 @@ FROM [RootEntity] AS [r] } } + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + + if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) + { + AssertSql( + """ +SELECT [r1].[RootEntityId], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r].[Id], [r0].[Id], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [r4].[RelatedTypeRootEntityId], [r4].[Id], [r4].[Int], [r4].[Name], [r4].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String] +FROM [RootReferencingEntity] AS [r] +LEFT JOIN [RootEntity] AS [r0] ON [r].[RootEntityId] = [r0].[Id] +LEFT JOIN [RequiredRelated] AS [r1] ON [r0].[Id] = [r1].[RootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r2] ON [r1].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r3] ON [r1].[RootEntityId] = [r3].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r4] ON [r1].[RootEntityId] = [r4].[RelatedTypeRootEntityId] +ORDER BY [r].[Id], [r0].[Id], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [r4].[RelatedTypeRootEntityId] +"""); + } + } + #endregion Non-collection #region Collection diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs index fe8d7fcd789..964ecc08892 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs @@ -167,6 +167,23 @@ FROM [RootEntity] AS [r] } } + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + + if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) + { + AssertSql( + """ +SELECT [r0].[Id], [r0].[RequiredRelated_Id], [r0].[RequiredRelated_Int], [r0].[RequiredRelated_Name], [r0].[RequiredRelated_String], [r].[Id], [r1].[RelatedTypeRootEntityId], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r0].[RequiredRelated_OptionalNested_Id], [r0].[RequiredRelated_OptionalNested_Int], [r0].[RequiredRelated_OptionalNested_Name], [r0].[RequiredRelated_OptionalNested_String], [r0].[RequiredRelated_RequiredNested_Id], [r0].[RequiredRelated_RequiredNested_Int], [r0].[RequiredRelated_RequiredNested_Name], [r0].[RequiredRelated_RequiredNested_String] +FROM [RootReferencingEntity] AS [r] +LEFT JOIN [RootEntity] AS [r0] ON [r].[RootEntityId] = [r0].[Id] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r1] ON [r0].[Id] = [r1].[RelatedTypeRootEntityId] +ORDER BY [r].[Id], [r0].[Id], [r1].[RelatedTypeRootEntityId] +"""); + } + } + #endregion Non-collection #region Collection diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs index 49a75281aa6..7a9a221c9a7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexTypeQuerySqliteTest.cs @@ -107,14 +107,16 @@ public override async Task Filter_on_required_property_inside_required_complex_t """); } - // This test fails because when OptionalCustomer is null, we get all-null results because of the LEFT JOIN, and we materialize this - // as an empty ShippingAddress instead of null (see SQL). The proper solution here would be to project the Customer ID just for the - // purpose of knowing that it's there. public override async Task Project_complex_type_via_optional_navigation(bool async) { - var exception = await Assert.ThrowsAsync(() => base.Project_complex_type_via_optional_navigation(async)); + await base.Project_complex_type_via_optional_navigation(async); - Assert.Equal(RelationalStrings.CannotProjectNullableComplexType("Customer.ShippingAddress#Address"), exception.Message); + AssertSql( + """ +SELECT "c0"."ShippingAddress_AddressLine1", "c0"."ShippingAddress_AddressLine2", "c0"."ShippingAddress_Tags", "c0"."ShippingAddress_ZipCode", "c0"."ShippingAddress_Country_Code", "c0"."ShippingAddress_Country_FullName" +FROM "CustomerGroup" AS "c" +LEFT JOIN "Customer" AS "c0" ON "c"."OptionalCustomerId" = "c0"."Id" +"""); } public override async Task Project_complex_type_via_required_navigation(bool async) @@ -245,13 +247,6 @@ public override async Task Complex_type_equals_parameter(bool async) """); } - public override async Task Complex_type_equals_null(bool async) - { - await base.Complex_type_equals_null(async); - - AssertSql(); - } - public override async Task Subquery_over_complex_type(bool async) { await base.Subquery_over_complex_type(async); @@ -476,15 +471,16 @@ public override async Task Filter_on_required_property_inside_required_struct_co """); } - // This test fails because when OptionalCustomer is null, we get all-null results because of the LEFT JOIN, and we materialize this - // as an empty ShippingAddress instead of null (see SQL). The proper solution here would be to project the Customer ID just for the - // purpose of knowing that it's there. public override async Task Project_struct_complex_type_via_optional_navigation(bool async) { - var exception = - await Assert.ThrowsAsync(() => base.Project_struct_complex_type_via_optional_navigation(async)); + await base.Project_struct_complex_type_via_optional_navigation(async); - Assert.Equal(RelationalStrings.CannotProjectNullableComplexType("ValuedCustomer.ShippingAddress#AddressStruct"), exception.Message); + AssertSql( + """ +SELECT "v0"."ShippingAddress_AddressLine1", "v0"."ShippingAddress_AddressLine2", "v0"."ShippingAddress_ZipCode", "v0"."ShippingAddress_Country_Code", "v0"."ShippingAddress_Country_FullName" +FROM "ValuedCustomerGroup" AS "v" +LEFT JOIN "ValuedCustomer" AS "v0" ON "v"."OptionalCustomerId" = "v0"."Id" +"""); } public override async Task Project_struct_complex_type_via_required_navigation(bool async) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqliteTest.cs index 59abab0b7f6..92dcd0cebf5 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqliteTest.cs @@ -6,4 +6,45 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexTableSplittin public class ComplexTableSplittingMiscellaneousSqliteTest( ComplexTableSplittingSqliteFixture fixture, ITestOutputHelper testOutputHelper) - : ComplexTableSplittingMiscellaneousRelationalTestBase(fixture, testOutputHelper); + : ComplexTableSplittingMiscellaneousRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Where_related_property() + { + await base.Where_related_property(); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated_Int" = 8 +"""); + } + + public override async Task Where_optional_related_property() + { + await base.Where_optional_related_property(); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."OptionalRelated_Int" = 8 +"""); + } + + public override async Task Where_nested_related_property() + { + await base.Where_nested_related_property(); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated_RequiredNested_Int" = 8 +"""); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqliteTest.cs index 177104f5a77..c9ad7ff74b5 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqliteTest.cs @@ -8,9 +8,216 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexTableSplittin public class ComplexTableSplittingProjectionSqliteTest(ComplexTableSplittingSqliteFixture fixture, ITestOutputHelper testOutputHelper) : ComplexTableSplittingProjectionRelationalTestBase(fixture, testOutputHelper) { + public override async Task Select_related_collection(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_related_collection(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_nested_collection_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nested_collection_on_required_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task Select_root(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_root(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_property_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_property_on_required_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."RequiredRelated_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_property_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_property_on_optional_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."OptionalRelated_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_value_type_property_on_null_related_throws(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_value_type_property_on_null_related_throws(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."OptionalRelated_Int" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."OptionalRelated_Int" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_optional_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_optional_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_required_nested_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_nested_on_required_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_optional_nested_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_optional_nested_on_required_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_required_nested_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_nested_on_optional_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_optional_nested_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_optional_nested_on_optional_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override async Task Select_required_related_via_optional_navigation(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_required_related_via_optional_navigation(queryTrackingBehavior); + + AssertSql(""" +SELECT "r0"."RequiredRelated_Id", "r0"."RequiredRelated_Int", "r0"."RequiredRelated_Name", "r0"."RequiredRelated_String", "r0"."RequiredRelated_OptionalNested_Id", "r0"."RequiredRelated_OptionalNested_Int", "r0"."RequiredRelated_OptionalNested_Name", "r0"."RequiredRelated_OptionalNested_String", "r0"."RequiredRelated_RequiredNested_Id", "r0"."RequiredRelated_RequiredNested_Int", "r0"."RequiredRelated_RequiredNested_Name", "r0"."RequiredRelated_RequiredNested_String" +FROM "RootReferencingEntity" AS "r" +LEFT JOIN "RootEntity" AS "r0" ON "r"."RootEntityId" = "r0"."Id" +"""); + } + + public override async Task Select_nested_collection_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nested_collection_on_optional_related(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +ORDER BY "r"."Id" +"""); + } + + public override async Task SelectMany_related_collection(QueryTrackingBehavior queryTrackingBehavior) + { + await base.SelectMany_related_collection(queryTrackingBehavior); + + AssertSql(); + } + + public override async Task SelectMany_nested_collection_on_required_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.SelectMany_nested_collection_on_required_related(queryTrackingBehavior); + + AssertSql(); + } + + public override async Task SelectMany_nested_collection_on_optional_related(QueryTrackingBehavior queryTrackingBehavior) + { + await base.SelectMany_nested_collection_on_optional_related(queryTrackingBehavior); + + AssertSql(); + } + + public override async Task Select_root_duplicated(QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_root_duplicated(queryTrackingBehavior); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +"""); + } + + public override Task Select_subquery_required_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior) + => AssertApplyNotSupported(() => base.Select_subquery_required_related_FirstOrDefault(queryTrackingBehavior)); + public override Task Select_subquery_optional_related_FirstOrDefault(QueryTrackingBehavior queryTrackingBehavior) => AssertApplyNotSupported(() => base.Select_subquery_optional_related_FirstOrDefault(queryTrackingBehavior)); + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + private static async Task AssertApplyNotSupported(Func query) => Assert.Equal( SqliteStrings.ApplyNotSupported, diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs index ac1b0875b43..be0e7050262 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs @@ -12,42 +12,72 @@ public override async Task Two_related() { await base.Two_related(); - AssertSql(); + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated_Id" = "r"."OptionalRelated_Id" AND "r"."RequiredRelated_Int" = "r"."OptionalRelated_Int" AND "r"."RequiredRelated_Name" = "r"."OptionalRelated_Name" AND "r"."RequiredRelated_String" = "r"."OptionalRelated_String" AND ("r"."RequiredRelated_OptionalNested_Id" = "r"."RequiredRelated_OptionalNested_Id" OR "r"."RequiredRelated_OptionalNested_Id" IS NULL) AND ("r"."RequiredRelated_OptionalNested_Int" = "r"."RequiredRelated_OptionalNested_Int" OR "r"."RequiredRelated_OptionalNested_Int" IS NULL) AND ("r"."RequiredRelated_OptionalNested_Name" = "r"."RequiredRelated_OptionalNested_Name" OR "r"."RequiredRelated_OptionalNested_Name" IS NULL) AND ("r"."RequiredRelated_OptionalNested_String" = "r"."RequiredRelated_OptionalNested_String" OR "r"."RequiredRelated_OptionalNested_String" IS NULL) AND "r"."RequiredRelated_RequiredNested_Id" = "r"."RequiredRelated_RequiredNested_Id" AND "r"."RequiredRelated_RequiredNested_Int" = "r"."RequiredRelated_RequiredNested_Int" AND "r"."RequiredRelated_RequiredNested_Name" = "r"."RequiredRelated_RequiredNested_Name" AND "r"."RequiredRelated_RequiredNested_String" = "r"."RequiredRelated_RequiredNested_String" +"""); } public override async Task Two_nested() { await base.Two_nested(); - AssertSql(); + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated_RequiredNested_Id" = "r"."OptionalRelated_RequiredNested_Id" AND "r"."RequiredRelated_RequiredNested_Int" = "r"."OptionalRelated_RequiredNested_Int" AND "r"."RequiredRelated_RequiredNested_Name" = "r"."OptionalRelated_RequiredNested_Name" AND "r"."RequiredRelated_RequiredNested_String" = "r"."OptionalRelated_RequiredNested_String" +"""); } public override async Task Not_equals() { await base.Not_equals(); - AssertSql(); + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated_Id" <> "r"."OptionalRelated_Id" OR "r"."OptionalRelated_Id" IS NULL OR "r"."RequiredRelated_Int" <> "r"."OptionalRelated_Int" OR "r"."OptionalRelated_Int" IS NULL OR "r"."RequiredRelated_Name" <> "r"."OptionalRelated_Name" OR "r"."OptionalRelated_Name" IS NULL OR "r"."RequiredRelated_String" <> "r"."OptionalRelated_String" OR "r"."OptionalRelated_String" IS NULL OR (("r"."RequiredRelated_OptionalNested_Id" <> "r"."RequiredRelated_OptionalNested_Id" OR "r"."RequiredRelated_OptionalNested_Id" IS NULL) AND "r"."RequiredRelated_OptionalNested_Id" IS NOT NULL) OR (("r"."RequiredRelated_OptionalNested_Int" <> "r"."RequiredRelated_OptionalNested_Int" OR "r"."RequiredRelated_OptionalNested_Int" IS NULL) AND "r"."RequiredRelated_OptionalNested_Int" IS NOT NULL) OR (("r"."RequiredRelated_OptionalNested_Name" <> "r"."RequiredRelated_OptionalNested_Name" OR "r"."RequiredRelated_OptionalNested_Name" IS NULL) AND "r"."RequiredRelated_OptionalNested_Name" IS NOT NULL) OR (("r"."RequiredRelated_OptionalNested_String" <> "r"."RequiredRelated_OptionalNested_String" OR "r"."RequiredRelated_OptionalNested_String" IS NULL) AND "r"."RequiredRelated_OptionalNested_String" IS NOT NULL) OR "r"."RequiredRelated_RequiredNested_Id" <> "r"."RequiredRelated_RequiredNested_Id" OR "r"."RequiredRelated_RequiredNested_Id" IS NULL OR "r"."RequiredRelated_RequiredNested_Int" <> "r"."RequiredRelated_RequiredNested_Int" OR "r"."RequiredRelated_RequiredNested_Int" IS NULL OR "r"."RequiredRelated_RequiredNested_Name" <> "r"."RequiredRelated_RequiredNested_Name" OR "r"."RequiredRelated_RequiredNested_Name" IS NULL OR "r"."RequiredRelated_RequiredNested_String" <> "r"."RequiredRelated_RequiredNested_String" OR "r"."RequiredRelated_RequiredNested_String" IS NULL +"""); } public override async Task Related_with_inline_null() { await base.Related_with_inline_null(); - AssertSql(); + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."OptionalRelated_Id" IS NULL AND "r"."OptionalRelated_Int" IS NULL AND "r"."OptionalRelated_Name" IS NULL AND "r"."OptionalRelated_String" IS NULL AND "r"."OptionalRelated_OptionalNested_Id" IS NULL AND "r"."OptionalRelated_OptionalNested_Int" IS NULL AND "r"."OptionalRelated_OptionalNested_Name" IS NULL AND "r"."OptionalRelated_OptionalNested_String" IS NULL AND "r"."OptionalRelated_RequiredNested_Id" IS NULL AND "r"."OptionalRelated_RequiredNested_Int" IS NULL AND "r"."OptionalRelated_RequiredNested_Name" IS NULL AND "r"."OptionalRelated_RequiredNested_String" IS NULL +"""); } public override async Task Related_with_parameter_null() { await base.Related_with_parameter_null(); - AssertSql(); + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."OptionalRelated_Id" IS NULL AND "r"."OptionalRelated_Int" IS NULL AND "r"."OptionalRelated_Name" IS NULL AND "r"."OptionalRelated_String" IS NULL AND "r"."OptionalRelated_OptionalNested_Id" IS NULL AND "r"."OptionalRelated_OptionalNested_Int" IS NULL AND "r"."OptionalRelated_OptionalNested_Name" IS NULL AND "r"."OptionalRelated_OptionalNested_String" IS NULL AND "r"."OptionalRelated_RequiredNested_Id" IS NULL AND "r"."OptionalRelated_RequiredNested_Int" IS NULL AND "r"."OptionalRelated_RequiredNested_Name" IS NULL AND "r"."OptionalRelated_RequiredNested_String" IS NULL +"""); } public override async Task Nested_with_inline_null() { await base.Nested_with_inline_null(); - AssertSql(); + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated_OptionalNested_Id" IS NULL AND "r"."RequiredRelated_OptionalNested_Int" IS NULL AND "r"."RequiredRelated_OptionalNested_Name" IS NULL AND "r"."RequiredRelated_OptionalNested_String" IS NULL +"""); } public override async Task Nested_with_inline() @@ -56,7 +86,7 @@ public override async Task Nested_with_inline() AssertSql( """ -SELECT "r"."Id", "r"."Name", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" FROM "RootEntity" AS "r" WHERE "r"."RequiredRelated_RequiredNested_Id" = 1000 AND "r"."RequiredRelated_RequiredNested_Int" = 8 AND "r"."RequiredRelated_RequiredNested_Name" = 'Root1_RequiredRelated_RequiredNested' AND "r"."RequiredRelated_RequiredNested_String" = 'foo' """); @@ -73,7 +103,7 @@ public override async Task Nested_with_parameter() @entity_equality_nested_Name='?' (Size = 36) @entity_equality_nested_String='?' (Size = 3) -SELECT "r"."Id", "r"."Name", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "r"."OptionalRelated_OptionalNested_Id", "r"."OptionalRelated_OptionalNested_Int", "r"."OptionalRelated_OptionalNested_Name", "r"."OptionalRelated_OptionalNested_String", "r"."OptionalRelated_RequiredNested_Id", "r"."OptionalRelated_RequiredNested_Int", "r"."OptionalRelated_RequiredNested_Name", "r"."OptionalRelated_RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r"."RequiredRelated_OptionalNested_Id", "r"."RequiredRelated_OptionalNested_Int", "r"."RequiredRelated_OptionalNested_Name", "r"."RequiredRelated_OptionalNested_String", "r"."RequiredRelated_RequiredNested_Id", "r"."RequiredRelated_RequiredNested_Int", "r"."RequiredRelated_RequiredNested_Name", "r"."RequiredRelated_RequiredNested_String" FROM "RootEntity" AS "r" WHERE "r"."RequiredRelated_RequiredNested_Id" = @entity_equality_nested_Id AND "r"."RequiredRelated_RequiredNested_Int" = @entity_equality_nested_Int AND "r"."RequiredRelated_RequiredNested_Name" = @entity_equality_nested_Name AND "r"."RequiredRelated_RequiredNested_String" = @entity_equality_nested_String """); diff --git a/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs b/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs index 4551fd36bd1..3b27f7be65b 100644 --- a/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs +++ b/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs @@ -26,7 +26,7 @@ public void Throws_for_abstract_types() CoreStrings.CannotMaterializeAbstractType(nameof(SomeAbstractEntity)), Assert.Throws( () => source.CreateMaterializeExpression( - new StructuralTypeMaterializerSourceParameters((IEntityType)entityType, "", null), null!)) + new StructuralTypeMaterializerSourceParameters((IEntityType)entityType, "", null, null), null!)) .Message); } @@ -550,7 +550,7 @@ public virtual Func GetMaterializer( IReadOnlyEntityType entityType) => Expression.Lambda>( source.CreateMaterializeExpression( - new StructuralTypeMaterializerSourceParameters((IEntityType)entityType, "instance", null), + new StructuralTypeMaterializerSourceParameters((IEntityType)entityType, "instance", null, null), _contextParameter), _contextParameter) .Compile();