diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 68e847041e2..cd1840a4d9d 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -578,7 +578,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s } // Otherwise, attempt to translate as Any since that passes through Where predicate translation. This will e.g. take care of - // entity , which e.g. does entity equality/containment for entities with composite keys. + // entity, which e.g. does entity equality/containment for entities with composite keys. var anyLambdaParameter = Expression.Parameter(item.Type, "p"); var anyLambda = Expression.Lambda( Infrastructure.ExpressionExtensions.CreateEqualsExpression(anyLambdaParameter, item), diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs index aa521f8e93a..75d0c415c1d 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs @@ -333,66 +333,77 @@ bool TryRewriteComplexTypeEquality(bool collection, [NotNullWhen(true)] out SqlE // into complex properties to generate a flattened list of comparisons. // The moment we reach a a complex property that's mapped to JSON, we stop and generate a single comparison // for the whole complex type. - bool TryGenerateComparisons(IComplexType type, Expression left, Expression right, [NotNullWhen(true)] ref SqlExpression? comparisons, out bool exitImmediately) + bool TryGenerateComparisons( + IComplexType type, + Expression left, + Expression right, + [NotNullWhen(true)] ref SqlExpression? comparisons, + out bool exitImmediately) { exitImmediately = false; if (type.IsMappedToJson()) { - var leftScalar = Process(left); - var rightScalar = Process(right); - - var comparison = _sqlExpressionFactory.MakeBinary(nodeType, leftScalar, rightScalar, boolTypeMapping)!; + // The fact that a type is mapped to JSON doesn't necessary mean that we simply compare its JSON column/value: + // a JSON *collection* may have been converted to a relational representation via e.g. OPENJSON, at which point + // it behaves just like a table-splitting complex type, where we have to compare column-by-column. + // TryProcessJson() attempts to extract a single JSON column/value from both sides: if we succeed, we just compare + // both sides. Otherwise, we flow down to the column-by-column flow. + if (TryProcessJson(left, out var leftScalar) && TryProcessJson(right, out var rightScalar)) + { + var comparison = _sqlExpressionFactory.MakeBinary(nodeType, leftScalar, rightScalar, boolTypeMapping)!; - // A single JSON-mapped complex type requires only a single comparison for the JSON value/column on each side; - // but the JSON-mapped complex type may be nested inside a non-JSON (table-split) complex type, in which - // case this is just one comparison in several. - comparisons = comparisons is null - ? comparison - : nodeType == ExpressionType.Equal - ? _sqlExpressionFactory.AndAlso(comparisons, comparison) - : _sqlExpressionFactory.OrElse(comparisons, comparison); + // A single JSON-mapped complex type requires only a single comparison for the JSON value/column on each side; + // but the JSON-mapped complex type may be nested inside a non-JSON (table-split) complex type, in which + // case this is just one comparison in several. + comparisons = comparisons is null + ? comparison + : nodeType == ExpressionType.Equal + ? _sqlExpressionFactory.AndAlso(comparisons, comparison) + : _sqlExpressionFactory.OrElse(comparisons, comparison); - return true; + return true; + } - SqlExpression Process(Expression expression) - => expression switch + bool TryProcessJson(Expression expression, [NotNullWhen(true)] out SqlExpression? result) + { + switch (expression) { // When a non-collection JSON column - or a nested complex property within a JSON column - is compared, // we get a StructuralTypeReferenceExpression over a JsonQueryExpression. Convert this to a // JsonScalarExpression, which is our current representation for a complex JSON in the SQL tree // (as opposed to in the shaper) - see #36392. - StructuralTypeReferenceExpression - { - Parameter: { ValueBufferExpression: JsonQueryExpression jsonQuery } - } - => new JsonScalarExpression( + case StructuralTypeReferenceExpression { Parameter.ValueBufferExpression: JsonQueryExpression jsonQuery }: + result = new JsonScalarExpression( jsonQuery.JsonColumn, jsonQuery.Path, jsonQuery.Type.UnwrapNullableType(), jsonQuery.JsonColumn.TypeMapping, - jsonQuery.IsNullable), + jsonQuery.IsNullable); + return true; // As above, but for a complex JSON collection - CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery } - => new JsonScalarExpression( + case CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery }: + result = new JsonScalarExpression( jsonQuery.JsonColumn, jsonQuery.Path, jsonQuery.Type.UnwrapNullableType(), jsonQuery.JsonColumn.TypeMapping, - jsonQuery.IsNullable), + jsonQuery.IsNullable); + return true; // When an object is instantiated inline (e.g. Where(c => c.ShippingAddress == new Address { ... })), we get a SqlConstantExpression // with the .NET instance. Serialize it to JSON and replace the constant (note that the type mapping will be inferred from the // JSON column on other side above - important for e.g. nvarchar vs. json columns) - SqlConstantExpression constant - => new SqlConstantExpression( + case SqlConstantExpression constant: + result = new SqlConstantExpression( SerializeComplexTypeToJson(complexType, constant.Value, collection), typeof(string), - typeMapping: null), + typeMapping: null); + return true; - SqlParameterExpression parameter - => (SqlParameterExpression)Visit( + case SqlParameterExpression parameter: + result = (SqlParameterExpression)Visit( _queryCompilationContext.RegisterRuntimeParameter( $"{RuntimeParameterPrefix}{parameter.Name}", Expression.Lambda( @@ -405,13 +416,52 @@ SqlParameterExpression parameter indexer: typeof(Dictionary).GetProperty("Item", [typeof(string)]), [Expression.Constant(parameter.Name, typeof(string))]), Expression.Constant(collection)), - QueryCompilationContext.QueryContextParameter))), + QueryCompilationContext.QueryContextParameter))); + return true; + + case ParameterBasedComplexPropertyChainExpression chainExpression: + { + var lastComplexProperty = chainExpression.ComplexPropertyChain.Last(); + var extractComplexPropertyClrType = Expression.Call( + ParameterValueExtractorMethod.MakeGenericMethod(lastComplexProperty.ClrType.MakeNullable()), + QueryCompilationContext.QueryContextParameter, + Expression.Constant(chainExpression.ParameterExpression.Name, typeof(string)), + Expression.Constant(chainExpression.ComplexPropertyChain, typeof(List)), + Expression.Constant(null, typeof(IProperty))); + + var lambda = + Expression.Lambda( + Expression.Call( + SerializeComplexTypeToJsonMethod, + Expression.Constant(lastComplexProperty.ComplexType), + extractComplexPropertyClrType, + Expression.Constant(lastComplexProperty.IsCollection)), + QueryCompilationContext.QueryContextParameter); + + var parameterNameBuilder = new StringBuilder(RuntimeParameterPrefix) + .Append(chainExpression.ParameterExpression.Name); + + foreach (var complexProperty in chainExpression.ComplexPropertyChain) + { + parameterNameBuilder.Append('_').Append(complexProperty.Name); + } - _ => throw new UnreachableException() + result = (SqlParameterExpression)Visit( + _queryCompilationContext.RegisterRuntimeParameter(parameterNameBuilder.ToString(), lambda)); + + return true; + } + + default: + result = null; + return false; }; + } } - // We handled complex JSON above, from here we handle table splitting + // We handled JSON column comparison above. + // From here we handle table splitting and JSON types that have been converted to a relational table representation via + // e.g. OPENJSON. foreach (var property in type.GetProperties()) { if (TryTranslatePropertyAccess(left, property, out var leftTranslation) @@ -586,7 +636,7 @@ when memberInitExpression.Bindings.SingleOrDefault(mb => mb.Member.Name == compl QueryContext context, string baseParameterName, List? complexPropertyChain, - IProperty property) + IProperty? property) { var baseValue = context.Parameters[baseParameterName]; @@ -603,7 +653,11 @@ when memberInitExpression.Bindings.SingleOrDefault(mb => mb.Member.Name == compl } } - return baseValue == null ? (T?)(object?)null : (T?)property.GetGetter().GetClrValue(baseValue); + return baseValue == null + ? (T?)(object?)null + : property is null + ? (T?)baseValue + : (T?)property.GetGetter().GetClrValue(baseValue); } /// diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs index 489dd8381fd..0a13f17a792 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs @@ -97,6 +97,39 @@ public override async Task Nested_collection_with_parameter() AssertSql(); } + #region Contains + + public override async Task Contains_with_inline() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(() => base.Contains_with_inline()); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await AssertTranslationFailed(base.Contains_with_parameter); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await AssertTranslationFailed(base.Contains_with_operators_composed_on_the_collection); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await AssertTranslationFailed(base.Contains_with_nested_and_composed_operators); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); 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 f0e6f34490b..7a3e5b1d8a9 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs @@ -28,6 +28,44 @@ public override Task Nested_collection_with_inline() public override Task Two_nested_collections() => AssertTranslationFailed(() => base.Two_nested_collections()); + #region Contains + + public override async Task Contains_with_inline() + { + // Collections are not supported with table splitting, only JSON + await AssertTranslationFailed(base.Contains_with_inline); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + // Collections are not supported with table splitting, only JSON + await AssertTranslationFailed(base.Contains_with_parameter); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + // Collections are not supported with table splitting, only JSON + // Note that the exception is correct, since the collections in the test data are null for table splitting + await Assert.ThrowsAsync(base.Contains_with_operators_composed_on_the_collection); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + // Collections are not supported with table splitting, only JSON + // Note that the exception is correct, since the collections in the test data are null for table splitting + await Assert.ThrowsAsync(base.Contains_with_nested_and_composed_operators); + + AssertSql(); + } + + #endregion Contains + protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualityRelationalTestBase.cs index a15d16d4471..82e0e56b0ab 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualityRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualityRelationalTestBase.cs @@ -20,6 +20,42 @@ public OwnedJsonStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutp public override Task Related_with_parameter_null() => Assert.ThrowsAsync(() => base.Related_with_parameter_null()); + #region Contains + + public override async Task Contains_with_inline() + { + // The given key 'Property: RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.__synthesizedOrdinal (no field, int) Shadow Required PK AfterSave:Throw ValueGenerated.OnAdd' was not present in the dictionary. + await Assert.ThrowsAsync(base.Contains_with_inline); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_parameter); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_operators_composed_on_the_collection); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_nested_and_composed_operators); + + AssertSql(); + } + + #endregion Contains + protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityRelationalTestBase.cs index 5aa7058c657..10429917cf8 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityRelationalTestBase.cs @@ -13,6 +13,42 @@ public OwnedNavigationsStructuralEqualityRelationalTestBase(TFixture fixture, IT Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + #region Contains + + public override async Task Contains_with_inline() + { + // The given key 'Property: RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.__synthesizedOrdinal (no field, int) Shadow Required PK AfterSave:Throw ValueGenerated.OnAdd' was not present in the dictionary. + await Assert.ThrowsAsync(base.Contains_with_inline); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_parameter); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_operators_composed_on_the_collection); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + // No backing field could be found for property 'RootEntity.RelatedCollection#RelatedType.RootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_nested_and_composed_operators); + + AssertSql(); + } + + #endregion Contains + protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualityRelationalTestBase.cs index 1fe18212ad4..afc8a68e1f8 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualityRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualityRelationalTestBase.cs @@ -16,6 +16,42 @@ public OwnedTableSplittingStructuralEqualityRelationalTestBase(TFixture fixture, Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + #region Contains + + public override async Task Contains_with_inline() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_inline); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_parameter); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_operators_composed_on_the_collection); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + // No backing field could be found for property 'RootEntity.RequiredRelated#RelatedType.NestedCollection#NestedType.RelatedTypeRootEntityId' and the property does not have a getter. + await Assert.ThrowsAsync(base.Contains_with_nested_and_composed_operators); + + AssertSql(); + } + + #endregion Contains + protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs index 2fd5c904e6b..aaaed23438a 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs @@ -103,8 +103,8 @@ void SetRelatedValues(RelatedType related) } }), - // Entity where values are referentially identical to each other across required/optional, to test various equality sceanarios. - // Note that this gets overridden for owned navigations . + // Entity where values are referentially identical to each other across a given entity, to test various equality sceanarios. + // Note that this gets overridden for owned navigations. CreateRootEntity( id++, description: "With_referential_identity", e => { @@ -113,7 +113,7 @@ void SetRelatedValues(RelatedType related) e.OptionalRelated.OptionalNested = e.RequiredRelated.RequiredNested; e.RelatedCollection.Clear(); - e.RequiredRelated.NestedCollection.Clear(); + e.RequiredRelated.NestedCollection = [e.RequiredRelated.RequiredNested]; e.OptionalRelated.NestedCollection.Clear(); }), diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs index 84e1ea8a2f0..8dcfd11a6f9 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs @@ -165,5 +165,55 @@ await AssertQuery( nestedCollection))); // TODO: Rewrite equality to Equals for the entire test suite for complex } + #region Contains + + [ConditionalFact] + public virtual Task Contains_with_inline() + => AssertQuery(ss => ss.Set().Where(e => + e.RequiredRelated.NestedCollection.Contains( + new NestedType + { + Id = 1002, + Name = "Root1_RequiredRelated_NestedCollection_1", + Int = 8, + String = "foo" + }))); + + [ConditionalFact] + public virtual async Task Contains_with_parameter() + { + var nested = new NestedType + { + Id = 1002, + Name = "Root1_RequiredRelated_NestedCollection_1", + Int = 8, + String = "foo" + }; + + await AssertQuery(ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection.Contains(nested))); + } + + [ConditionalFact] + public virtual async Task Contains_with_operators_composed_on_the_collection() + { + var collection = Fixture.Data.RootEntities.Single(e => e.Name == "Root3_With_different_values").RequiredRelated.NestedCollection; + + await AssertQuery( + ss => ss.Set().Where( + e => e.RequiredRelated.NestedCollection.Where(n => n.Int > collection[0].Int).Contains(collection[1]))); + } + + [ConditionalFact] + public virtual async Task Contains_with_nested_and_composed_operators() + { + var collection = Fixture.Data.RootEntities.Single(e => e.Name == "Root3_With_different_values").RelatedCollection; + + await AssertQuery( + ss => ss.Set() + .Where(e => e.RelatedCollection.Where(r => r.Id > collection[0].Id).Contains(collection[1]))); + } + + #endregion Contains + // TODO: Equality on subquery } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs index f940c74799a..39ac8e71497 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs @@ -258,6 +258,119 @@ WHERE JSON_QUERY([r].[RequiredRelated], '$.NestedCollection') = @entity_equality } } + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + // TODO: The following translation is sub-optimal: we should be using OPENSJON to extract elements of the collection as JSON elements (OPENJSON WITH JSON), + // and comparison those elements to a single entire JSON fragment on the other side (just like non-collection JSON comparison), rather than breaking the + // elements down to their columns and doing column-by-column comparison. See #32576. + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE EXISTS ( + SELECT 1 + FROM OPENJSON([r].[RequiredRelated], '$.NestedCollection') WITH ( + [Id] int '$.Id', + [Int] int '$.Int', + [Name] nvarchar(max) '$.Name', + [String] nvarchar(max) '$.String' + ) AS [n] + WHERE [n].[Id] = 1002 AND [n].[Int] = 8 AND [n].[Name] = N'Root1_RequiredRelated_NestedCollection_1' AND [n].[String] = N'foo') +"""); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + // TODO: The following translation is sub-optimal: we should be using OPENSJON to extract elements of the collection as JSON elements (OPENJSON WITH JSON), + // and comparison those elements to a single entire JSON fragment on the other side (just like non-collection JSON comparison), rather than breaking the + // elements down to their columns and doing column-by-column comparison. See #32576. + AssertSql( + """ +@entity_equality_nested_Id='?' (DbType = Int32) +@entity_equality_nested_Int='?' (DbType = Int32) +@entity_equality_nested_Name='?' (Size = 4000) +@entity_equality_nested_String='?' (Size = 4000) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE EXISTS ( + SELECT 1 + FROM OPENJSON([r].[RequiredRelated], '$.NestedCollection') WITH ( + [Id] int '$.Id', + [Int] int '$.Int', + [Name] nvarchar(max) '$.Name', + [String] nvarchar(max) '$.String' + ) AS [n] + WHERE [n].[Id] = @entity_equality_nested_Id AND [n].[Int] = @entity_equality_nested_Int AND [n].[Name] = @entity_equality_nested_Name AND [n].[String] = @entity_equality_nested_String) +"""); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql( + """ +@get_Item_Int='?' (DbType = Int32) +@entity_equality_get_Item_Id='?' (DbType = Int32) +@entity_equality_get_Item_Int='?' (DbType = Int32) +@entity_equality_get_Item_Name='?' (Size = 4000) +@entity_equality_get_Item_String='?' (Size = 4000) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE EXISTS ( + SELECT 1 + FROM OPENJSON([r].[RequiredRelated], '$.NestedCollection') WITH ( + [Id] int '$.Id', + [Int] int '$.Int', + [Name] nvarchar(max) '$.Name', + [String] nvarchar(max) '$.String' + ) AS [n] + WHERE [n].[Int] > @get_Item_Int AND [n].[Id] = @entity_equality_get_Item_Id AND [n].[Int] = @entity_equality_get_Item_Int AND [n].[Name] = @entity_equality_get_Item_Name AND [n].[String] = @entity_equality_get_Item_String) +"""); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql( + """ +@get_Item_Id='?' (DbType = Int32) +@entity_equality_get_Item_Id='?' (DbType = Int32) +@entity_equality_get_Item_Int='?' (DbType = Int32) +@entity_equality_get_Item_Name='?' (Size = 4000) +@entity_equality_get_Item_String='?' (Size = 4000) +@entity_equality_get_Item_NestedCollection='?' (Size = 195) +@entity_equality_get_Item_OptionalNested='?' (Size = 89) +@entity_equality_get_Item_RequiredNested='?' (Size = 89) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE EXISTS ( + SELECT 1 + FROM OPENJSON([r].[RelatedCollection], '$') WITH ( + [Id] int '$.Id', + [Int] int '$.Int', + [Name] nvarchar(max) '$.Name', + [String] nvarchar(max) '$.String', + [NestedCollection] nvarchar(max) '$.NestedCollection' AS JSON, + [OptionalNested] nvarchar(max) '$.OptionalNested' AS JSON, + [RequiredNested] nvarchar(max) '$.RequiredNested' AS JSON + ) AS [r0] + WHERE [r0].[Id] > @get_Item_Id AND [r0].[Id] = @entity_equality_get_Item_Id AND [r0].[Int] = @entity_equality_get_Item_Int AND [r0].[Name] = @entity_equality_get_Item_Name AND [r0].[String] = @entity_equality_get_Item_String AND [r0].[NestedCollection] = @entity_equality_get_Item_NestedCollection AND [r0].[OptionalNested] = @entity_equality_get_Item_OptionalNested AND [r0].[RequiredNested] = @entity_equality_get_Item_RequiredNested) +"""); + } + + #endregion Contains + #region Value types public override async Task Nullable_value_type_with_null() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs index 347c712ae4d..8d780096da1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs @@ -130,6 +130,38 @@ public override async Task Nested_collection_with_parameter() AssertSql(); } + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + #region Value types public override async Task Nullable_value_type_with_null() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs index 073edd333a4..ea910fa6418 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs @@ -276,6 +276,142 @@ public override async Task Nested_collection_with_parameter() AssertSql(); } + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId], [r1].[Id], [r1].[CollectionRootId], [r1].[Int], [r1].[Name], [r1].[OptionalNestedId], [r1].[RequiredNestedId], [r1].[String], [r0].[Id], [n0].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n4].[Id], [n4].[CollectionRelatedId], [n4].[Int], [n4].[Name], [n4].[String], [n0].[CollectionRelatedId], [n0].[Int], [n0].[Name], [n0].[String], [n1].[CollectionRelatedId], [n1].[Int], [n1].[Name], [n1].[String], [s].[Id], [s].[CollectionRootId], [s].[Int], [s].[Name], [s].[OptionalNestedId], [s].[RequiredNestedId], [s].[String], [s].[Id0], [s].[Id1], [s].[Id2], [s].[CollectionRelatedId], [s].[Int0], [s].[Name0], [s].[String0], [s].[CollectionRelatedId0], [s].[Int1], [s].[Name1], [s].[String1], [s].[CollectionRelatedId1], [s].[Int2], [s].[Name2], [s].[String2], [r0].[CollectionRootId], [r0].[Int], [r0].[Name], [r0].[OptionalNestedId], [r0].[RequiredNestedId], [r0].[String], [n8].[Id], [n8].[CollectionRelatedId], [n8].[Int], [n8].[Name], [n8].[String], [n2].[CollectionRelatedId], [n2].[Int], [n2].[Name], [n2].[String], [n3].[CollectionRelatedId], [n3].[Int], [n3].[Name], [n3].[String] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +LEFT JOIN [NestedType] AS [n0] ON [r1].[OptionalNestedId] = [n0].[Id] +LEFT JOIN [NestedType] AS [n1] ON [r1].[RequiredNestedId] = [n1].[Id] +LEFT JOIN [NestedType] AS [n2] ON [r0].[OptionalNestedId] = [n2].[Id] +INNER JOIN [NestedType] AS [n3] ON [r0].[RequiredNestedId] = [n3].[Id] +LEFT JOIN [NestedType] AS [n4] ON [r1].[Id] = [n4].[CollectionRelatedId] +LEFT JOIN ( + SELECT [r2].[Id], [r2].[CollectionRootId], [r2].[Int], [r2].[Name], [r2].[OptionalNestedId], [r2].[RequiredNestedId], [r2].[String], [n5].[Id] AS [Id0], [n6].[Id] AS [Id1], [n7].[Id] AS [Id2], [n7].[CollectionRelatedId], [n7].[Int] AS [Int0], [n7].[Name] AS [Name0], [n7].[String] AS [String0], [n5].[CollectionRelatedId] AS [CollectionRelatedId0], [n5].[Int] AS [Int1], [n5].[Name] AS [Name1], [n5].[String] AS [String1], [n6].[CollectionRelatedId] AS [CollectionRelatedId1], [n6].[Int] AS [Int2], [n6].[Name] AS [Name2], [n6].[String] AS [String2] + FROM [RelatedType] AS [r2] + LEFT JOIN [NestedType] AS [n5] ON [r2].[OptionalNestedId] = [n5].[Id] + INNER JOIN [NestedType] AS [n6] ON [r2].[RequiredNestedId] = [n6].[Id] + LEFT JOIN [NestedType] AS [n7] ON [r2].[Id] = [n7].[CollectionRelatedId] +) AS [s] ON [r].[Id] = [s].[CollectionRootId] +LEFT JOIN [NestedType] AS [n8] ON [r0].[Id] = [n8].[CollectionRelatedId] +WHERE EXISTS ( + SELECT 1 + FROM [NestedType] AS [n] + WHERE [r0].[Id] = [n].[CollectionRelatedId] AND [n].[Id] = 1002) +ORDER BY [r].[Id], [r0].[Id], [r1].[Id], [n0].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n4].[Id], [s].[Id], [s].[Id0], [s].[Id1], [s].[Id2] +"""); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql( + """ +@entity_equality_nested_Id='1002' (Nullable = true) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId], [r1].[Id], [r1].[CollectionRootId], [r1].[Int], [r1].[Name], [r1].[OptionalNestedId], [r1].[RequiredNestedId], [r1].[String], [r0].[Id], [n0].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n4].[Id], [n4].[CollectionRelatedId], [n4].[Int], [n4].[Name], [n4].[String], [n0].[CollectionRelatedId], [n0].[Int], [n0].[Name], [n0].[String], [n1].[CollectionRelatedId], [n1].[Int], [n1].[Name], [n1].[String], [s].[Id], [s].[CollectionRootId], [s].[Int], [s].[Name], [s].[OptionalNestedId], [s].[RequiredNestedId], [s].[String], [s].[Id0], [s].[Id1], [s].[Id2], [s].[CollectionRelatedId], [s].[Int0], [s].[Name0], [s].[String0], [s].[CollectionRelatedId0], [s].[Int1], [s].[Name1], [s].[String1], [s].[CollectionRelatedId1], [s].[Int2], [s].[Name2], [s].[String2], [r0].[CollectionRootId], [r0].[Int], [r0].[Name], [r0].[OptionalNestedId], [r0].[RequiredNestedId], [r0].[String], [n8].[Id], [n8].[CollectionRelatedId], [n8].[Int], [n8].[Name], [n8].[String], [n2].[CollectionRelatedId], [n2].[Int], [n2].[Name], [n2].[String], [n3].[CollectionRelatedId], [n3].[Int], [n3].[Name], [n3].[String] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +LEFT JOIN [NestedType] AS [n0] ON [r1].[OptionalNestedId] = [n0].[Id] +LEFT JOIN [NestedType] AS [n1] ON [r1].[RequiredNestedId] = [n1].[Id] +LEFT JOIN [NestedType] AS [n2] ON [r0].[OptionalNestedId] = [n2].[Id] +INNER JOIN [NestedType] AS [n3] ON [r0].[RequiredNestedId] = [n3].[Id] +LEFT JOIN [NestedType] AS [n4] ON [r1].[Id] = [n4].[CollectionRelatedId] +LEFT JOIN ( + SELECT [r2].[Id], [r2].[CollectionRootId], [r2].[Int], [r2].[Name], [r2].[OptionalNestedId], [r2].[RequiredNestedId], [r2].[String], [n5].[Id] AS [Id0], [n6].[Id] AS [Id1], [n7].[Id] AS [Id2], [n7].[CollectionRelatedId], [n7].[Int] AS [Int0], [n7].[Name] AS [Name0], [n7].[String] AS [String0], [n5].[CollectionRelatedId] AS [CollectionRelatedId0], [n5].[Int] AS [Int1], [n5].[Name] AS [Name1], [n5].[String] AS [String1], [n6].[CollectionRelatedId] AS [CollectionRelatedId1], [n6].[Int] AS [Int2], [n6].[Name] AS [Name2], [n6].[String] AS [String2] + FROM [RelatedType] AS [r2] + LEFT JOIN [NestedType] AS [n5] ON [r2].[OptionalNestedId] = [n5].[Id] + INNER JOIN [NestedType] AS [n6] ON [r2].[RequiredNestedId] = [n6].[Id] + LEFT JOIN [NestedType] AS [n7] ON [r2].[Id] = [n7].[CollectionRelatedId] +) AS [s] ON [r].[Id] = [s].[CollectionRootId] +LEFT JOIN [NestedType] AS [n8] ON [r0].[Id] = [n8].[CollectionRelatedId] +WHERE EXISTS ( + SELECT 1 + FROM [NestedType] AS [n] + WHERE [r0].[Id] = [n].[CollectionRelatedId] AND [n].[Id] = @entity_equality_nested_Id) +ORDER BY [r].[Id], [r0].[Id], [r1].[Id], [n0].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n4].[Id], [s].[Id], [s].[Id0], [s].[Id1], [s].[Id2] +"""); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql( + """ +@get_Item_Int='103' +@entity_equality_get_Item_Id='3003' (Nullable = true) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId], [r1].[Id], [r1].[CollectionRootId], [r1].[Int], [r1].[Name], [r1].[OptionalNestedId], [r1].[RequiredNestedId], [r1].[String], [r0].[Id], [n0].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n4].[Id], [n4].[CollectionRelatedId], [n4].[Int], [n4].[Name], [n4].[String], [n0].[CollectionRelatedId], [n0].[Int], [n0].[Name], [n0].[String], [n1].[CollectionRelatedId], [n1].[Int], [n1].[Name], [n1].[String], [s].[Id], [s].[CollectionRootId], [s].[Int], [s].[Name], [s].[OptionalNestedId], [s].[RequiredNestedId], [s].[String], [s].[Id0], [s].[Id1], [s].[Id2], [s].[CollectionRelatedId], [s].[Int0], [s].[Name0], [s].[String0], [s].[CollectionRelatedId0], [s].[Int1], [s].[Name1], [s].[String1], [s].[CollectionRelatedId1], [s].[Int2], [s].[Name2], [s].[String2], [r0].[CollectionRootId], [r0].[Int], [r0].[Name], [r0].[OptionalNestedId], [r0].[RequiredNestedId], [r0].[String], [n8].[Id], [n8].[CollectionRelatedId], [n8].[Int], [n8].[Name], [n8].[String], [n2].[CollectionRelatedId], [n2].[Int], [n2].[Name], [n2].[String], [n3].[CollectionRelatedId], [n3].[Int], [n3].[Name], [n3].[String] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +LEFT JOIN [NestedType] AS [n0] ON [r1].[OptionalNestedId] = [n0].[Id] +LEFT JOIN [NestedType] AS [n1] ON [r1].[RequiredNestedId] = [n1].[Id] +LEFT JOIN [NestedType] AS [n2] ON [r0].[OptionalNestedId] = [n2].[Id] +INNER JOIN [NestedType] AS [n3] ON [r0].[RequiredNestedId] = [n3].[Id] +LEFT JOIN [NestedType] AS [n4] ON [r1].[Id] = [n4].[CollectionRelatedId] +LEFT JOIN ( + SELECT [r2].[Id], [r2].[CollectionRootId], [r2].[Int], [r2].[Name], [r2].[OptionalNestedId], [r2].[RequiredNestedId], [r2].[String], [n5].[Id] AS [Id0], [n6].[Id] AS [Id1], [n7].[Id] AS [Id2], [n7].[CollectionRelatedId], [n7].[Int] AS [Int0], [n7].[Name] AS [Name0], [n7].[String] AS [String0], [n5].[CollectionRelatedId] AS [CollectionRelatedId0], [n5].[Int] AS [Int1], [n5].[Name] AS [Name1], [n5].[String] AS [String1], [n6].[CollectionRelatedId] AS [CollectionRelatedId1], [n6].[Int] AS [Int2], [n6].[Name] AS [Name2], [n6].[String] AS [String2] + FROM [RelatedType] AS [r2] + LEFT JOIN [NestedType] AS [n5] ON [r2].[OptionalNestedId] = [n5].[Id] + INNER JOIN [NestedType] AS [n6] ON [r2].[RequiredNestedId] = [n6].[Id] + LEFT JOIN [NestedType] AS [n7] ON [r2].[Id] = [n7].[CollectionRelatedId] +) AS [s] ON [r].[Id] = [s].[CollectionRootId] +LEFT JOIN [NestedType] AS [n8] ON [r0].[Id] = [n8].[CollectionRelatedId] +WHERE EXISTS ( + SELECT 1 + FROM [NestedType] AS [n] + WHERE [r0].[Id] = [n].[CollectionRelatedId] AND [n].[Int] > @get_Item_Int AND [n].[Id] = @entity_equality_get_Item_Id) +ORDER BY [r].[Id], [r0].[Id], [r1].[Id], [n0].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n4].[Id], [s].[Id], [s].[Id0], [s].[Id1], [s].[Id2] +"""); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql( + """ +@get_Item_Id='302' +@entity_equality_get_Item_Id='303' (Nullable = true) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId], [r1].[Id], [r1].[CollectionRootId], [r1].[Int], [r1].[Name], [r1].[OptionalNestedId], [r1].[RequiredNestedId], [r1].[String], [n].[Id], [n0].[Id], [r2].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [n3].[CollectionRelatedId], [n3].[Int], [n3].[Name], [n3].[String], [n].[CollectionRelatedId], [n].[Int], [n].[Name], [n].[String], [n0].[CollectionRelatedId], [n0].[Int], [n0].[Name], [n0].[String], [s].[Id], [s].[CollectionRootId], [s].[Int], [s].[Name], [s].[OptionalNestedId], [s].[RequiredNestedId], [s].[String], [s].[Id0], [s].[Id1], [s].[Id2], [s].[CollectionRelatedId], [s].[Int0], [s].[Name0], [s].[String0], [s].[CollectionRelatedId0], [s].[Int1], [s].[Name1], [s].[String1], [s].[CollectionRelatedId1], [s].[Int2], [s].[Name2], [s].[String2], [r2].[CollectionRootId], [r2].[Int], [r2].[Name], [r2].[OptionalNestedId], [r2].[RequiredNestedId], [r2].[String], [n7].[Id], [n7].[CollectionRelatedId], [n7].[Int], [n7].[Name], [n7].[String], [n1].[CollectionRelatedId], [n1].[Int], [n1].[Name], [n1].[String], [n2].[CollectionRelatedId], [n2].[Int], [n2].[Name], [n2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +LEFT JOIN [NestedType] AS [n] ON [r1].[OptionalNestedId] = [n].[Id] +LEFT JOIN [NestedType] AS [n0] ON [r1].[RequiredNestedId] = [n0].[Id] +INNER JOIN [RelatedType] AS [r2] ON [r].[RequiredRelatedId] = [r2].[Id] +LEFT JOIN [NestedType] AS [n1] ON [r2].[OptionalNestedId] = [n1].[Id] +INNER JOIN [NestedType] AS [n2] ON [r2].[RequiredNestedId] = [n2].[Id] +LEFT JOIN [NestedType] AS [n3] ON [r1].[Id] = [n3].[CollectionRelatedId] +LEFT JOIN ( + SELECT [r3].[Id], [r3].[CollectionRootId], [r3].[Int], [r3].[Name], [r3].[OptionalNestedId], [r3].[RequiredNestedId], [r3].[String], [n4].[Id] AS [Id0], [n5].[Id] AS [Id1], [n6].[Id] AS [Id2], [n6].[CollectionRelatedId], [n6].[Int] AS [Int0], [n6].[Name] AS [Name0], [n6].[String] AS [String0], [n4].[CollectionRelatedId] AS [CollectionRelatedId0], [n4].[Int] AS [Int1], [n4].[Name] AS [Name1], [n4].[String] AS [String1], [n5].[CollectionRelatedId] AS [CollectionRelatedId1], [n5].[Int] AS [Int2], [n5].[Name] AS [Name2], [n5].[String] AS [String2] + FROM [RelatedType] AS [r3] + LEFT JOIN [NestedType] AS [n4] ON [r3].[OptionalNestedId] = [n4].[Id] + INNER JOIN [NestedType] AS [n5] ON [r3].[RequiredNestedId] = [n5].[Id] + LEFT JOIN [NestedType] AS [n6] ON [r3].[Id] = [n6].[CollectionRelatedId] +) AS [s] ON [r].[Id] = [s].[CollectionRootId] +LEFT JOIN [NestedType] AS [n7] ON [r2].[Id] = [n7].[CollectionRelatedId] +WHERE EXISTS ( + SELECT 1 + FROM [RelatedType] AS [r0] + WHERE [r].[Id] = [r0].[CollectionRootId] AND [r0].[Id] > @get_Item_Id AND [r0].[Id] = @entity_equality_get_Item_Id) +ORDER BY [r].[Id], [r1].[Id], [n].[Id], [n0].[Id], [r2].[Id], [n1].[Id], [n2].[Id], [n3].[Id], [s].[Id], [s].[Id0], [s].[Id1], [s].[Id2] +"""); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs index 11814ea1fe1..d97e0148faf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs @@ -124,6 +124,38 @@ public override async Task Nested_collection_with_parameter() ); } + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs index c0e1aa5df3d..d44f21f7810 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs @@ -234,6 +234,38 @@ public override async Task Nested_collection_with_parameter() AssertSql(); } + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs index 1d27c715d27..04b93fdc6b8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs @@ -194,6 +194,38 @@ public override async Task Nested_collection_with_parameter() AssertSql(); } + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); + } + + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs index 2efe0ecfee7..98d55680b9e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs @@ -112,18 +112,48 @@ public override async Task Nested_collection_with_inline() { await base.Nested_collection_with_inline(); - AssertSql( - ); + AssertSql(); } public override async Task Nested_collection_with_parameter() { await base.Nested_collection_with_parameter(); - AssertSql( - ); + AssertSql(); + } + + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); } + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqliteTest.cs index 648bbe24bce..1b2077e691a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqliteTest.cs @@ -224,18 +224,48 @@ public override async Task Nested_collection_with_inline() { await base.Nested_collection_with_inline(); - AssertSql( - ); + AssertSql(); } public override async Task Nested_collection_with_parameter() { await base.Nested_collection_with_parameter(); - AssertSql( - ); + AssertSql(); + } + + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); } + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqliteTest.cs index 480044520aa..ce6f53dd0c7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqliteTest.cs @@ -184,18 +184,48 @@ public override async Task Nested_collection_with_inline() { await base.Nested_collection_with_inline(); - AssertSql( - ); + AssertSql(); } public override async Task Nested_collection_with_parameter() { await base.Nested_collection_with_parameter(); - AssertSql( - ); + AssertSql(); + } + + #region Contains + + public override async Task Contains_with_inline() + { + await base.Contains_with_inline(); + + AssertSql(); + } + + public override async Task Contains_with_parameter() + { + await base.Contains_with_parameter(); + + AssertSql(); + } + + public override async Task Contains_with_operators_composed_on_the_collection() + { + await base.Contains_with_operators_composed_on_the_collection(); + + AssertSql(); } + public override async Task Contains_with_nested_and_composed_operators() + { + await base.Contains_with_nested_and_composed_operators(); + + AssertSql(); + } + + #endregion Contains + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType());