diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs new file mode 100644 index 00000000000..d08cbbb4c4f --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.StructuralEquality.cs @@ -0,0 +1,720 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Text.Json; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Query; + +// Contains the parts of RelationalSqlTranslatingExpressionVisitor which handle structural equality, +// i.e. when an entity or complex type is compared to another structural type (as opposed to scalar equality): +// context.Customers.Where(c => c.ShippingAddress == c.BillingAddress) +public partial class RelationalSqlTranslatingExpressionVisitor +{ + private static readonly MethodInfo ParameterValueExtractorMethod = + typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterValueExtractor))!; + + private static readonly MethodInfo ParameterListValueExtractorMethod = + typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterListValueExtractor))!; + + private static readonly MethodInfo SerializeComplexTypeToJsonMethod = + typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(SerializeComplexTypeToJson))!; + + private bool TryRewriteContainsEntity(Expression source, Expression item, [NotNullWhen(true)] out SqlExpression? result) + { + result = null; + + if (item is not StructuralTypeReferenceExpression { StructuralType: IEntityType entityType }) + { + return false; + } + + var primaryKeyProperties = entityType.FindPrimaryKey()?.Properties; + if (primaryKeyProperties == null) + { + throw new InvalidOperationException( + CoreStrings.EntityEqualityOnKeylessEntityNotSupported( + nameof(Queryable.Contains), entityType.DisplayName())); + } + + if (primaryKeyProperties.Count > 1) + { + throw new InvalidOperationException( + CoreStrings.EntityEqualityOnCompositeKeyEntitySubqueryNotSupported( + nameof(Queryable.Contains), entityType.DisplayName())); + } + + var property = primaryKeyProperties[0]; + Expression rewrittenSource; + switch (source) + { + case SqlConstantExpression sqlConstantExpression: + var values = (IEnumerable)sqlConstantExpression.Value!; + var propertyValueList = + (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(property.ClrType.MakeNullable()))!; + var propertyGetter = property.GetGetter(); + foreach (var value in values) + { + propertyValueList.Add(propertyGetter.GetClrValue(value)); + } + + rewrittenSource = Expression.Constant(propertyValueList); + break; + + case SqlParameterExpression sqlParameterExpression: + var lambda = Expression.Lambda( + Expression.Call( + ParameterListValueExtractorMethod.MakeGenericMethod(entityType.ClrType, property.ClrType.MakeNullable()), + QueryCompilationContext.QueryContextParameter, + Expression.Constant(sqlParameterExpression.Name, typeof(string)), + Expression.Constant(property, typeof(IProperty))), + QueryCompilationContext.QueryContextParameter); + + var newParameterName = + $"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}"; + + rewrittenSource = _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); + break; + + default: + return false; + } + + result = (SqlExpression)Visit( + Expression.Call( + EnumerableMethods.Contains.MakeGenericMethod(property.ClrType.MakeNullable()), + rewrittenSource, + CreatePropertyAccessExpression(item, property))); + + return true; + } + + private bool TryRewriteStructuralTypeEquality( + ExpressionType nodeType, + Expression left, + Expression right, + bool equalsMethod, + [NotNullWhen(true)] out SqlExpression? result) + { + switch ((left, right)) + { + case (StructuralTypeReferenceExpression { StructuralType: IEntityType }, _): + case (_, StructuralTypeReferenceExpression { StructuralType: IEntityType }): + return TryRewriteEntityEquality(out result); + + case (StructuralTypeReferenceExpression { StructuralType: IComplexType }, _): + case (_, StructuralTypeReferenceExpression { StructuralType: IComplexType }): + return TryRewriteComplexTypeEquality(collection: false, out result); + + case (CollectionResultExpression { Relationship: IComplexProperty }, _): + case (_, CollectionResultExpression { Relationship: IComplexProperty }): + return TryRewriteComplexTypeEquality(collection: true, out result); + + default: + result = null; + return false; + } + + bool TryRewriteEntityEquality([NotNullWhen(true)] out SqlExpression? result) + { + var leftReference = left as StructuralTypeReferenceExpression; + var rightReference = right as StructuralTypeReferenceExpression; + + if (IsNullSqlConstantExpression(left) + || IsNullSqlConstantExpression(right)) + { + var nonNullEntityReference = (IsNullSqlConstantExpression(left) ? rightReference : leftReference)!; + var nullComparedEntityType = (IEntityType)nonNullEntityReference.StructuralType; + + if (nonNullEntityReference is { Parameter.ValueBufferExpression: JsonQueryExpression jsonQueryExpression }) + { + var jsonScalarExpression = new JsonScalarExpression( + jsonQueryExpression.JsonColumn, + jsonQueryExpression.Path, + jsonQueryExpression.JsonColumn.Type, + jsonQueryExpression.JsonColumn.TypeMapping!, + jsonQueryExpression.IsNullable); + + result = nodeType == ExpressionType.Equal + ? _sqlExpressionFactory.IsNull(jsonScalarExpression) + : _sqlExpressionFactory.IsNotNull(jsonScalarExpression); + + return true; + } + + var nullComparedEntityTypePrimaryKeyProperties = nullComparedEntityType.FindPrimaryKey()?.Properties; + if (nullComparedEntityTypePrimaryKeyProperties == null) + { + throw new InvalidOperationException( + CoreStrings.EntityEqualityOnKeylessEntityNotSupported( + nodeType == ExpressionType.Equal + ? equalsMethod ? nameof(Equals) : "==" + : equalsMethod + ? "!" + nameof(Equals) + : "!=", + nullComparedEntityType.DisplayName())); + } + + if (nullComparedEntityType.GetRootType() == nullComparedEntityType + && nullComparedEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) + { + var table = nullComparedEntityType.GetViewOrTableMappings().SingleOrDefault()?.Table + ?? nullComparedEntityType.GetDefaultMappings().Single().Table; + if (table.IsOptional(nullComparedEntityType)) + { + Expression? condition = null; + // Optional dependent sharing table + var requiredNonPkProperties = nullComparedEntityType.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()) + .ToList(); + if (requiredNonPkProperties.Count > 0) + { + condition = requiredNonPkProperties.Select( + p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( + CreatePropertyAccessExpression(nonNullEntityReference, p), + Expression.Constant(null, p.ClrType.MakeNullable()), + nodeType != ExpressionType.Equal)) + .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r)); + } + + var allNonPrincipalSharedNonPkProperties = nullComparedEntityType.GetNonPrincipalSharedNonPkProperties(table); + // We don't need condition for nullable property if there exist at least one required property which is non shared. + if (allNonPrincipalSharedNonPkProperties.Count != 0 + && allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable)) + { + // if we don't have any required properties to properly check the nullability, + // we rely on optional ones (somewhat unreliably) + // - if entity is to be null, all the properties must be null + // - if the entity is to be not null, at least one property must be not null + var optionalPropertiesCondition = allNonPrincipalSharedNonPkProperties + .Select( + p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( + CreatePropertyAccessExpression(nonNullEntityReference, p), + Expression.Constant(null, p.ClrType.MakeNullable()), + nodeType != ExpressionType.Equal)) + .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r)); + + condition = condition == null + ? optionalPropertiesCondition + : nodeType == ExpressionType.Equal + ? Expression.OrElse(condition, optionalPropertiesCondition) + : Expression.AndAlso(condition, optionalPropertiesCondition); + } + + if (condition != null) + { + result = (SqlExpression)Visit(condition); + return true; + } + + result = null; + return false; + } + } + + result = (SqlExpression)Visit( + nullComparedEntityTypePrimaryKeyProperties.Select( + p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( + CreatePropertyAccessExpression(nonNullEntityReference, p), + Expression.Constant(null, p.ClrType.MakeNullable()), + nodeType != ExpressionType.Equal)) + .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r))); + + return true; + } + + var leftEntityType = leftReference?.StructuralType as IEntityType; + var rightEntityType = rightReference?.StructuralType as IEntityType; + var entityType = leftEntityType ?? rightEntityType; + + Check.DebugAssert(entityType != null, "We checked that at least one side is an entity type before calling this function"); + + if (leftEntityType != null + && rightEntityType != null + && leftEntityType.GetRootType() != rightEntityType.GetRootType()) + { + result = _sqlExpressionFactory.Constant(false); + return true; + } + + var primaryKeyProperties = entityType.FindPrimaryKey()?.Properties; + if (primaryKeyProperties == null) + { + throw new InvalidOperationException( + CoreStrings.EntityEqualityOnKeylessEntityNotSupported( + nodeType == ExpressionType.Equal + ? equalsMethod ? nameof(object.Equals) : "==" + : equalsMethod + ? "!" + nameof(object.Equals) + : "!=", + entityType.DisplayName())); + } + + if (primaryKeyProperties.Count > 1 + && (leftReference?.Subquery != null + || rightReference?.Subquery != null)) + { + throw new InvalidOperationException( + CoreStrings.EntityEqualityOnCompositeKeyEntitySubqueryNotSupported( + nodeType == ExpressionType.Equal + ? equalsMethod ? nameof(object.Equals) : "==" + : equalsMethod + ? "!" + nameof(object.Equals) + : "!=", + entityType.DisplayName())); + } + + result = (SqlExpression)Visit( + primaryKeyProperties.Select( + p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( + CreatePropertyAccessExpression(left, p), + CreatePropertyAccessExpression(right, p), + nodeType != ExpressionType.Equal)) + .Aggregate( + (l, r) => nodeType == ExpressionType.Equal + ? Expression.AndAlso(l, r) + : Expression.OrElse(l, r))); + + return true; + } + + bool TryRewriteComplexTypeEquality(bool collection, [NotNullWhen(true)] out SqlExpression? result) + { + var leftComplexType = left switch + { + StructuralTypeReferenceExpression { StructuralType: IComplexType t } => t, + CollectionResultExpression { Relationship: IComplexProperty { ComplexType: var t } } => t, + _ => null + }; + + var rightComplexType = right switch + { + StructuralTypeReferenceExpression { StructuralType: IComplexType t } => t, + CollectionResultExpression { Relationship: IComplexProperty { ComplexType: var t } } => t, + _ => null + }; + + if (leftComplexType is not null + && rightComplexType is not null + && leftComplexType.ClrType != rightComplexType.ClrType) + { + // Currently only support comparing complex types of the same CLR type. + // We could allow any case where the complex types have the same properties (some may be shadow). + result = null; + return false; + } + + var complexType = leftComplexType ?? rightComplexType; + + 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 + if (left is StructuralTypeReferenceExpression { Subquery: not null } + || right is StructuralTypeReferenceExpression { Subquery: not null }) + { + throw new InvalidOperationException(RelationalStrings.SubqueryOverComplexTypesNotSupported(complexType.DisplayName())); + } + + // Generate an expression that compares each property on the left to the same property on the right; this needs to recursively + // include all properties in nested complex types. + var boolTypeMapping = Dependencies.TypeMappingSource.FindMapping(typeof(bool))!; + SqlExpression? comparisons = null; + + if (!TryGenerateComparisons(complexType, left, right, ref comparisons)) + { + result = null; + return false; + } + + result = comparisons; + return true; + + // For table splitting, we simply go over all properties and generate an equality for each one; we recurse + // 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) + { + if (type.IsMappedToJson()) + { + var leftScalar = Process(left); + var rightScalar = Process(right); + + var comparison = _sqlExpressionFactory.MakeBinary(nodeType, leftScalar, rightScalar, boolTypeMapping)!; + + comparisons = comparisons is null + ? comparison + : nodeType == ExpressionType.Equal + ? _sqlExpressionFactory.AndAlso(comparisons, comparison) + : _sqlExpressionFactory.OrElse(comparisons, comparison); + + return true; + + SqlExpression Process(Expression expression) + => expression switch + { + // 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: StructuralTypeShaperExpression { ValueBufferExpression: JsonQueryExpression jsonQuery } } + => new JsonScalarExpression( + jsonQuery.JsonColumn, + jsonQuery.Path, + jsonQuery.Type, + jsonQuery.JsonColumn.TypeMapping, + jsonQuery.IsNullable), + + // As above, but for a complex JSON collectio + CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery } + => new JsonScalarExpression( + jsonQuery.JsonColumn, + jsonQuery.Path, + jsonQuery.Type, + jsonQuery.JsonColumn.TypeMapping, + jsonQuery.IsNullable), + + // 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( + SerializeComplexTypeToJson(complexType, constant.Value, collection), + typeof(string), + typeMapping: null), + + SqlParameterExpression parameter + => (SqlParameterExpression)Visit(_queryCompilationContext.RegisterRuntimeParameter( + $"{RuntimeParameterPrefix}{parameter.Name}", + Expression.Lambda( + Expression.Call( + SerializeComplexTypeToJsonMethod, + Expression.Constant(complexType), + Expression.MakeIndex( + Expression.Property(QueryCompilationContext.QueryContextParameter, nameof(QueryContext.Parameters)), + indexer: typeof(Dictionary).GetProperty("Item", [typeof(string)]), + [Expression.Constant(parameter.Name, typeof(string))]), + Expression.Constant(collection)), + QueryCompilationContext.QueryContextParameter))), + + _ => throw new UnreachableException() + }; + } + + // We handled complex JSON above, from here we handle table splitting + foreach (var property in type.GetProperties()) + { + if (TryTranslatePropertyAccess(left, property, out var leftTranslation) + && TryTranslatePropertyAccess(right, property, out var rightTranslation)) + { + var comparison = _sqlExpressionFactory.MakeBinary(nodeType, leftTranslation, rightTranslation, boolTypeMapping)!; + + comparisons = comparisons is null + ? comparison + : nodeType == ExpressionType.Equal + ? _sqlExpressionFactory.AndAlso(comparisons, comparison) + : _sqlExpressionFactory.OrElse(comparisons, comparison); + } + else + { + return false; + } + } + + foreach (var complexProperty in type.GetComplexProperties()) + { + Check.DebugAssert( + left is not StructuralTypeReferenceExpression { Subquery: not null } + && right is not StructuralTypeReferenceExpression { Subquery: not null }, + "Subquery complex type references are not supported"); + + // TODO: Implement/test non-entity binding (i.e. with a constant instance) + var nestedLeft = left is StructuralTypeReferenceExpression leftReference + ? BindComplexProperty(leftReference, complexProperty) + : CreateComplexPropertyAccessExpression(left, complexProperty); + var nestedRight = right is StructuralTypeReferenceExpression rightReference + ? BindComplexProperty(rightReference, complexProperty) + : CreateComplexPropertyAccessExpression(right, complexProperty); + + if (nestedLeft is null + || nestedRight is null + || !TryGenerateComparisons(complexProperty.ComplexType, nestedLeft, nestedRight, ref comparisons)) + { + return false; + } + } + + return comparisons is not null; + } + } + } + + private bool TryTranslatePropertyAccess(Expression target, IPropertyBase property, [NotNullWhen(true)] out SqlExpression? translation) + { + var expression = CreatePropertyAccessExpression(target, property); + translation = Translate(expression); + return translation is not null; + } + + Expression CreatePropertyAccessExpression(Expression target, IPropertyBase property) + { + switch (target) + { + // TODO: Cleanup, why do we need both SqlConstantExpression and ConstantExpression + case SqlConstantExpression sqlConstantExpression: + return Expression.Constant( + sqlConstantExpression.Value is null + ? null + : property.GetGetter().GetClrValue(sqlConstantExpression.Value), + property.ClrType.MakeNullable()); + + case ConstantExpression sqlConstantExpression: + return Expression.Constant( + sqlConstantExpression.Value is null + ? null + : property.GetGetter().GetClrValue(sqlConstantExpression.Value), + property.ClrType.MakeNullable()); + + case SqlParameterExpression sqlParameterExpression: + { + var lambda = Expression.Lambda( + Expression.Call( + ParameterValueExtractorMethod.MakeGenericMethod(property.ClrType.MakeNullable()), + QueryCompilationContext.QueryContextParameter, + Expression.Constant(sqlParameterExpression.Name, typeof(string)), + Expression.Constant(null, typeof(List)), + Expression.Constant(property, typeof(IProperty))), + QueryCompilationContext.QueryContextParameter); + + var newParameterName = + $"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}"; + + return _queryCompilationContext.RegisterRuntimeParameter($"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}", lambda); + } + + case ParameterBasedComplexPropertyChainExpression chainExpression: + { + var lambda = Expression.Lambda( + Expression.Call( + ParameterValueExtractorMethod.MakeGenericMethod(property.ClrType.MakeNullable()), + QueryCompilationContext.QueryContextParameter, + Expression.Constant(chainExpression.ParameterExpression.Name, typeof(string)), + Expression.Constant(chainExpression.ComplexPropertyChain, typeof(List)), + Expression.Constant(property, typeof(IProperty))), + QueryCompilationContext.QueryContextParameter); + + var parameterNameBuilder = new StringBuilder(RuntimeParameterPrefix) + .Append(chainExpression.ParameterExpression.Name) + .Append('_'); + + foreach (var complexProperty in chainExpression.ComplexPropertyChain) + { + parameterNameBuilder.Append(complexProperty.Name).Append('_'); + } + + parameterNameBuilder.Append(property.Name); + + return _queryCompilationContext.RegisterRuntimeParameter(parameterNameBuilder.ToString(), lambda); + } + + case MemberInitExpression memberInitExpression + when memberInitExpression.Bindings.SingleOrDefault( + mb => mb.Member.Name == property.Name) is MemberAssignment memberAssignment: + return memberAssignment.Expression; + + default: + return target.CreateEFPropertyExpression(property); + } + } + + private Expression CreateComplexPropertyAccessExpression(Expression target, IComplexProperty complexProperty) + => target switch + { + SqlConstantExpression constant => Expression.Constant( + constant.Value is null ? null : complexProperty.GetGetter().GetClrValue(constant.Value), + complexProperty.ClrType.MakeNullable()), + + SqlParameterExpression sqlParameterExpression + => new ParameterBasedComplexPropertyChainExpression(sqlParameterExpression, complexProperty), + + MemberInitExpression memberInitExpression + when memberInitExpression.Bindings.SingleOrDefault(mb => mb.Member.Name == complexProperty.Name) is MemberAssignment + memberAssignment + => memberAssignment.Expression, + + // For non-constant/parameter complex property accesses, BindComplexProperty is called instead of this method + // TODO: possibly refactor, folding this method into BindComplexProperty to have it handle the constant/parameter cases as well + // (but consider the non-complex property case as well) + _ => throw new UnreachableException() + }; + + /// + /// 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. + /// + [EntityFrameworkInternal] + public static T? ParameterValueExtractor( + QueryContext context, + string baseParameterName, + List? complexPropertyChain, + IProperty property) + { + var baseValue = context.Parameters[baseParameterName]; + + if (complexPropertyChain is not null) + { + foreach (var complexProperty in complexPropertyChain) + { + if (baseValue is null) + { + break; + } + + baseValue = complexProperty.GetGetter().GetClrValue(baseValue); + } + } + + return baseValue == null ? (T?)(object?)null : (T?)property.GetGetter().GetClrValue(baseValue); + } + + /// + /// 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. + /// + [EntityFrameworkInternal] + public static List? ParameterListValueExtractor( + QueryContext context, + string baseParameterName, + IProperty property) + { + if (context.Parameters[baseParameterName] is not IEnumerable baseListParameter) + { + return null; + } + + var getter = property.GetGetter(); + return baseListParameter.Select(e => e != null ? (TProperty?)getter.GetClrValue(e) : (TProperty?)(object?)null).ToList(); + } + + private sealed class ParameterBasedComplexPropertyChainExpression( + SqlParameterExpression parameterExpression, + IComplexProperty firstComplexProperty) + : Expression + { + public SqlParameterExpression ParameterExpression { get; } = parameterExpression; + public List ComplexPropertyChain { get; } = [firstComplexProperty]; + } + + /// + /// 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. + /// + [EntityFrameworkInternal] + public static string? SerializeComplexTypeToJson(IComplexType complexType, object? value, bool collection) + { + // Note that we treat toplevel null differently: we return a relational NULL for that case. For nested nulls, + // we return JSON null string (so you get { "foo": null }) + if (value is null) + { + return null; + } + + var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }); + + WriteJson(writer, complexType, value, collection); + + writer.Flush(); + + return Encoding.UTF8.GetString(stream.ToArray()); + + void WriteJson(Utf8JsonWriter writer, IComplexType complexType, object? value, bool collection) + { + if (collection) + { + if (value is null) + { + writer.WriteNullValue(); + + return; + } + + writer.WriteStartArray(); + + foreach (var element in (IEnumerable)value) + { + WriteJsonObject(writer, complexType, element); + } + + writer.WriteEndArray(); + return; + } + + WriteJsonObject(writer, complexType, value); + } + + void WriteJsonObject(Utf8JsonWriter writer, IComplexType complexType, object? objectValue) + { + if (objectValue is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + + foreach (var property in complexType.GetProperties()) + { + var jsonPropertyName = property.GetJsonPropertyName(); + Check.DebugAssert(jsonPropertyName is not null); + writer.WritePropertyName(jsonPropertyName); + + var propertyValue = property.GetGetter().GetClrValue(objectValue); + if (propertyValue is null) + { + writer.WriteNullValue(); + } + else + { + var jsonValueReaderWriter = property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter; + Check.DebugAssert(jsonValueReaderWriter is not null, "Missing JsonValueReaderWriter on JSON property"); + jsonValueReaderWriter.ToJson(writer, propertyValue); + } + } + + foreach (var complexProperty in complexType.GetComplexProperties()) + { + var jsonPropertyName = complexProperty.GetJsonPropertyName(); + Check.DebugAssert(jsonPropertyName is not null); + writer.WritePropertyName(jsonPropertyName); + + var propertyValue = complexProperty.GetGetter().GetClrValue(objectValue); + + WriteJson(writer, complexProperty.ComplexType, propertyValue, complexProperty.IsCollection); + } + + writer.WriteEndObject(); + } + } +} diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index bbcb020acee..57e1d26508f 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Query; /// not used in application code. /// /// -public class RelationalSqlTranslatingExpressionVisitor : ExpressionVisitor +public partial class RelationalSqlTranslatingExpressionVisitor : ExpressionVisitor { private const string RuntimeParameterPrefix = "entity_equality_"; @@ -40,12 +40,6 @@ public class RelationalSqlTranslatingExpressionVisitor : ExpressionVisitor QueryableMethods.ElementAtOrDefault ]; - private static readonly MethodInfo ParameterValueExtractorMethod = - typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterValueExtractor))!; - - private static readonly MethodInfo ParameterListValueExtractorMethod = - typeof(RelationalSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ParameterListValueExtractor))!; - private static readonly MethodInfo StringEqualsWithStringComparison = typeof(string).GetRuntimeMethod(nameof(string.Equals), [typeof(string), typeof(StringComparison)])!; @@ -1682,499 +1676,6 @@ private static bool TryEvaluateToConstant(Expression expression, [NotNullWhen(tr return false; } - private bool TryRewriteContainsEntity(Expression source, Expression item, [NotNullWhen(true)] out Expression? result) - { - result = null; - - if (item is not StructuralTypeReferenceExpression { StructuralType: IEntityType entityType }) - { - return false; - } - - var primaryKeyProperties = entityType.FindPrimaryKey()?.Properties; - if (primaryKeyProperties == null) - { - throw new InvalidOperationException( - CoreStrings.EntityEqualityOnKeylessEntityNotSupported( - nameof(Queryable.Contains), entityType.DisplayName())); - } - - if (primaryKeyProperties.Count > 1) - { - throw new InvalidOperationException( - CoreStrings.EntityEqualityOnCompositeKeyEntitySubqueryNotSupported( - nameof(Queryable.Contains), entityType.DisplayName())); - } - - var property = primaryKeyProperties[0]; - Expression rewrittenSource; - switch (source) - { - case SqlConstantExpression sqlConstantExpression: - var values = (IEnumerable)sqlConstantExpression.Value!; - var propertyValueList = - (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(property.ClrType.MakeNullable()))!; - var propertyGetter = property.GetGetter(); - foreach (var value in values) - { - propertyValueList.Add(propertyGetter.GetClrValue(value)); - } - - rewrittenSource = Expression.Constant(propertyValueList); - break; - - case SqlParameterExpression sqlParameterExpression: - var lambda = Expression.Lambda( - Expression.Call( - ParameterListValueExtractorMethod.MakeGenericMethod(entityType.ClrType, property.ClrType.MakeNullable()), - QueryCompilationContext.QueryContextParameter, - Expression.Constant(sqlParameterExpression.Name, typeof(string)), - Expression.Constant(property, typeof(IProperty))), - QueryCompilationContext.QueryContextParameter); - - var newParameterName = - $"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}"; - - rewrittenSource = _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); - break; - - default: - return false; - } - - result = Visit( - Expression.Call( - EnumerableMethods.Contains.MakeGenericMethod(property.ClrType.MakeNullable()), - rewrittenSource, - CreatePropertyAccessExpression(item, property))); - - return true; - } - - private bool TryRewriteStructuralTypeEquality( - ExpressionType nodeType, - Expression left, - Expression right, - bool equalsMethod, - [NotNullWhen(true)] out Expression? result) - { - var leftReference = left as StructuralTypeReferenceExpression; - var rightReference = right as StructuralTypeReferenceExpression; - - switch ((leftEntityReference: leftReference, rightEntityReference: rightReference)) - { - case ({ StructuralType: IEntityType }, { StructuralType: IEntityType } or null): - case ({ StructuralType: IEntityType } or null, { StructuralType: IEntityType }): - return TryRewriteEntityEquality(out result); - - case ({ StructuralType: IComplexType }, { StructuralType: IComplexType } or null): - case ({ StructuralType: IComplexType } or null, { StructuralType: IComplexType }): - return TryRewriteComplexTypeEquality(out result); - - default: - result = null; - return false; - } - - bool TryRewriteEntityEquality([NotNullWhen(true)] out Expression? result) - { - if (IsNullSqlConstantExpression(left) - || IsNullSqlConstantExpression(right)) - { - var nonNullEntityReference = (IsNullSqlConstantExpression(left) ? rightReference : leftReference)!; - var nullComparedEntityType = (IEntityType)nonNullEntityReference.StructuralType; - - if (nonNullEntityReference is { Parameter.ValueBufferExpression: JsonQueryExpression jsonQueryExpression }) - { - var jsonScalarExpression = new JsonScalarExpression( - jsonQueryExpression.JsonColumn, - jsonQueryExpression.Path, - jsonQueryExpression.JsonColumn.Type, - jsonQueryExpression.JsonColumn.TypeMapping!, - jsonQueryExpression.IsNullable); - - result = nodeType == ExpressionType.Equal - ? _sqlExpressionFactory.IsNull(jsonScalarExpression) - : _sqlExpressionFactory.IsNotNull(jsonScalarExpression); - - return true; - } - - var nullComparedEntityTypePrimaryKeyProperties = nullComparedEntityType.FindPrimaryKey()?.Properties; - if (nullComparedEntityTypePrimaryKeyProperties == null) - { - throw new InvalidOperationException( - CoreStrings.EntityEqualityOnKeylessEntityNotSupported( - nodeType == ExpressionType.Equal - ? equalsMethod ? nameof(object.Equals) : "==" - : equalsMethod - ? "!" + nameof(object.Equals) - : "!=", - nullComparedEntityType.DisplayName())); - } - - if (nullComparedEntityType.GetRootType() == nullComparedEntityType - && nullComparedEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) - { - var table = nullComparedEntityType.GetViewOrTableMappings().SingleOrDefault()?.Table - ?? nullComparedEntityType.GetDefaultMappings().Single().Table; - if (table.IsOptional(nullComparedEntityType)) - { - Expression? condition = null; - // Optional dependent sharing table - var requiredNonPkProperties = nullComparedEntityType.GetProperties().Where(p => !p.IsNullable && !p.IsPrimaryKey()) - .ToList(); - if (requiredNonPkProperties.Count > 0) - { - condition = requiredNonPkProperties.Select( - p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( - CreatePropertyAccessExpression(nonNullEntityReference, p), - Expression.Constant(null, p.ClrType.MakeNullable()), - nodeType != ExpressionType.Equal)) - .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r)); - } - - var allNonPrincipalSharedNonPkProperties = nullComparedEntityType.GetNonPrincipalSharedNonPkProperties(table); - // We don't need condition for nullable property if there exist at least one required property which is non shared. - if (allNonPrincipalSharedNonPkProperties.Count != 0 - && allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable)) - { - // if we don't have any required properties to properly check the nullability, - // we rely on optional ones (somewhat unreliably) - // - if entity is to be null, all the properties must be null - // - if the entity is to be not null, at least one property must be not null - var optionalPropertiesCondition = allNonPrincipalSharedNonPkProperties - .Select( - p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( - CreatePropertyAccessExpression(nonNullEntityReference, p), - Expression.Constant(null, p.ClrType.MakeNullable()), - nodeType != ExpressionType.Equal)) - .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r)); - - condition = condition == null - ? optionalPropertiesCondition - : nodeType == ExpressionType.Equal - ? Expression.OrElse(condition, optionalPropertiesCondition) - : Expression.AndAlso(condition, optionalPropertiesCondition); - } - - if (condition != null) - { - result = Visit(condition); - return true; - } - - result = null; - return false; - } - } - - result = Visit( - nullComparedEntityTypePrimaryKeyProperties.Select( - p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( - CreatePropertyAccessExpression(nonNullEntityReference, p), - Expression.Constant(null, p.ClrType.MakeNullable()), - nodeType != ExpressionType.Equal)) - .Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r))); - - return true; - } - - var leftEntityType = leftReference?.StructuralType as IEntityType; - var rightEntityType = rightReference?.StructuralType as IEntityType; - var entityType = leftEntityType ?? rightEntityType; - - Check.DebugAssert(entityType != null, "We checked that at least one side is an entity type before calling this function"); - - if (leftEntityType != null - && rightEntityType != null - && leftEntityType.GetRootType() != rightEntityType.GetRootType()) - { - result = _sqlExpressionFactory.Constant(false); - return true; - } - - var primaryKeyProperties = entityType.FindPrimaryKey()?.Properties; - if (primaryKeyProperties == null) - { - throw new InvalidOperationException( - CoreStrings.EntityEqualityOnKeylessEntityNotSupported( - nodeType == ExpressionType.Equal - ? equalsMethod ? nameof(object.Equals) : "==" - : equalsMethod - ? "!" + nameof(object.Equals) - : "!=", - entityType.DisplayName())); - } - - if (primaryKeyProperties.Count > 1 - && (leftReference?.Subquery != null - || rightReference?.Subquery != null)) - { - throw new InvalidOperationException( - CoreStrings.EntityEqualityOnCompositeKeyEntitySubqueryNotSupported( - nodeType == ExpressionType.Equal - ? equalsMethod ? nameof(object.Equals) : "==" - : equalsMethod - ? "!" + nameof(object.Equals) - : "!=", - entityType.DisplayName())); - } - - result = Visit( - primaryKeyProperties.Select( - p => Infrastructure.ExpressionExtensions.CreateEqualsExpression( - CreatePropertyAccessExpression(left, p), - CreatePropertyAccessExpression(right, p), - nodeType != ExpressionType.Equal)) - .Aggregate( - (l, r) => nodeType == ExpressionType.Equal - ? Expression.AndAlso(l, r) - : Expression.OrElse(l, r))); - - return true; - } - - bool TryRewriteComplexTypeEquality([NotNullWhen(true)] out Expression? result) - { - if (IsNullSqlConstantExpression(left) - || IsNullSqlConstantExpression(right)) - { - // TODO: when we support optional complex types - or projecting required complex types via optional navigations - we'll - // be able to translate this, #31376 - throw new InvalidOperationException(RelationalStrings.CannotCompareComplexTypeToNull); - } - - var leftComplexType = leftReference?.StructuralType as IComplexType; - var rightComplexType = rightReference?.StructuralType as IComplexType; - var complexType = leftComplexType ?? rightComplexType; - - Check.DebugAssert(complexType != null, "We checked that at least one side is a complex type before calling this function"); - - // 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 - if (leftReference is { Subquery: not null } || rightReference is { Subquery: not null }) - { - throw new InvalidOperationException(RelationalStrings.SubqueryOverComplexTypesNotSupported(complexType.DisplayName())); - } - - // Generate an expression that compares each property on the left to the same property on the right; this needs to recursively - // include all properties in nested complex types. - Expression? comparisons = null; - GenerateComparisons(complexType, left, right); - - // Indicates failure to bind to a complex property while generating the comparisons - if (comparisons is null) - { - result = null; - return false; - } - - result = Visit(comparisons); - return true; - - void GenerateComparisons(IComplexType type, Expression left, Expression right) - { - if (type.IsMappedToJson()) - { - throw new NotImplementedException("Issue #36296"); - } - - foreach (var property in type.GetProperties()) - { - var comparison = Infrastructure.ExpressionExtensions.CreateEqualsExpression( - CreatePropertyAccessExpression(left, property), - CreatePropertyAccessExpression(right, property), - nodeType != ExpressionType.Equal); - - comparisons = comparisons is null - ? comparison - : nodeType == ExpressionType.Equal - ? Expression.AndAlso(comparisons, comparison) - : Expression.OrElse(comparisons, comparison); - } - - foreach (var complexProperty in type.GetComplexProperties()) - { - Check.DebugAssert( - left is not StructuralTypeReferenceExpression { Subquery: not null } - && right is not StructuralTypeReferenceExpression { Subquery: not null }, - "Subquery complex type references are not supported"); - - // TODO: Implement/test non-entity binding (i.e. with a constant instance) - var nestedLeft = left is StructuralTypeReferenceExpression leftReference - ? BindComplexProperty(leftReference, complexProperty) - : CreateComplexPropertyAccessExpression(left, complexProperty); - var nestedRight = right is StructuralTypeReferenceExpression rightReference - ? BindComplexProperty(rightReference, complexProperty) - : CreateComplexPropertyAccessExpression(right, complexProperty); - - if (nestedLeft is null || nestedRight is null) - { - comparisons = null; - return; - } - - GenerateComparisons(complexProperty.ComplexType, nestedLeft, nestedRight); - } - } - } - } - - private Expression CreatePropertyAccessExpression(Expression target, IProperty property) - { - switch (target) - { - // TODO: Cleanup, why do we need both SqlConstantExpression and ConstantExpression - case SqlConstantExpression sqlConstantExpression: - return Expression.Constant( - sqlConstantExpression.Value is null - ? null - : property.GetGetter().GetClrValue(sqlConstantExpression.Value), - property.ClrType.MakeNullable()); - - case ConstantExpression sqlConstantExpression: - return Expression.Constant( - sqlConstantExpression.Value is null - ? null - : property.GetGetter().GetClrValue(sqlConstantExpression.Value), - property.ClrType.MakeNullable()); - - case SqlParameterExpression sqlParameterExpression: - { - var lambda = Expression.Lambda( - Expression.Call( - ParameterValueExtractorMethod.MakeGenericMethod(property.ClrType.MakeNullable()), - QueryCompilationContext.QueryContextParameter, - Expression.Constant(sqlParameterExpression.Name, typeof(string)), - Expression.Constant(null, typeof(List)), - Expression.Constant(property, typeof(IProperty))), - QueryCompilationContext.QueryContextParameter); - - var newParameterName = - $"{RuntimeParameterPrefix}{sqlParameterExpression.Name}_{property.Name}"; - - return _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); - } - - case ParameterBasedComplexPropertyChainExpression chainExpression: - { - var lambda = Expression.Lambda( - Expression.Call( - ParameterValueExtractorMethod.MakeGenericMethod(property.ClrType.MakeNullable()), - QueryCompilationContext.QueryContextParameter, - Expression.Constant(chainExpression.ParameterExpression.Name, typeof(string)), - Expression.Constant(chainExpression.ComplexPropertyChain, typeof(List)), - Expression.Constant(property, typeof(IProperty))), - QueryCompilationContext.QueryContextParameter); - - var parameterNameBuilder = new StringBuilder(RuntimeParameterPrefix) - .Append(chainExpression.ParameterExpression.Name) - .Append('_'); - - foreach (var complexProperty in chainExpression.ComplexPropertyChain) - { - parameterNameBuilder.Append(complexProperty.Name).Append('_'); - } - - parameterNameBuilder.Append(property.Name); - - return _queryCompilationContext.RegisterRuntimeParameter(parameterNameBuilder.ToString(), lambda); - } - - case MemberInitExpression memberInitExpression - when memberInitExpression.Bindings.SingleOrDefault( - mb => mb.Member.Name == property.Name) is MemberAssignment memberAssignment: - return memberAssignment.Expression; - - default: - return target.CreateEFPropertyExpression(property); - } - } - - private Expression CreateComplexPropertyAccessExpression(Expression target, IComplexProperty complexProperty) - => target switch - { - SqlConstantExpression constant => Expression.Constant( - constant.Value is null ? null : complexProperty.GetGetter().GetClrValue(constant.Value), - complexProperty.ClrType.MakeNullable()), - - SqlParameterExpression sqlParameterExpression - => new ParameterBasedComplexPropertyChainExpression(sqlParameterExpression, complexProperty), - - MemberInitExpression memberInitExpression - when memberInitExpression.Bindings.SingleOrDefault(mb => mb.Member.Name == complexProperty.Name) is MemberAssignment - memberAssignment - => memberAssignment.Expression, - - // For non-constant/parameter complex property accesses, BindComplexProperty is called instead of this method - // TODO: possibly refactor, folding this method into BindComplexProperty to have it handle the constant/parameter cases as well - // (but consider the non-complex property case as well) - _ => throw new UnreachableException() - }; - - /// - /// 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. - /// - [EntityFrameworkInternal] - public static T? ParameterValueExtractor( - QueryContext context, - string baseParameterName, - List? complexPropertyChain, - IProperty property) - { - var baseValue = context.Parameters[baseParameterName]; - - if (complexPropertyChain is not null) - { - foreach (var complexProperty in complexPropertyChain) - { - if (baseValue is null) - { - break; - } - - baseValue = complexProperty.GetGetter().GetClrValue(baseValue); - } - } - - return baseValue == null ? (T?)(object?)null : (T?)property.GetGetter().GetClrValue(baseValue); - } - - /// - /// 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. - /// - [EntityFrameworkInternal] - public static List? ParameterListValueExtractor( - QueryContext context, - string baseParameterName, - IProperty property) - { - if (context.Parameters[baseParameterName] is not IEnumerable baseListParameter) - { - return null; - } - - var getter = property.GetGetter(); - return baseListParameter.Select(e => e != null ? (TProperty?)getter.GetClrValue(e) : (TProperty?)(object?)null).ToList(); - } - - private sealed class ParameterBasedComplexPropertyChainExpression( - SqlParameterExpression parameterExpression, - IComplexProperty firstComplexProperty) - : Expression - { - public SqlParameterExpression ParameterExpression { get; } = parameterExpression; - public List ComplexPropertyChain { get; } = [firstComplexProperty]; - } - private static bool CanEvaluate(Expression expression) => expression switch { diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs index 32a507b25bf..c678ab85e69 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs @@ -261,7 +261,7 @@ when _columnsToRewrite.TryGetValue((columnExpression.TableAlias, columnExpressio // with OPENJSON. return openJsonExpression.JsonExpression.TypeMapping is SqlServerStringTypeMapping { StoreType: "json" } - or SqlServerOwnedJsonTypeMapping { StoreType: "json" } + or SqlServerStructuralJsonTypeMapping { StoreType: "json" } ? openJsonExpression.Update( new SqlUnaryExpression( ExpressionType.Convert, diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index 73fd75e37c8..fe67d7b285d 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -522,7 +522,7 @@ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExp return jsonScalarExpression; } - if (jsonScalarExpression.TypeMapping is SqlServerOwnedJsonTypeMapping + if (jsonScalarExpression.TypeMapping is SqlServerStructuralJsonTypeMapping || jsonScalarExpression.TypeMapping?.ElementTypeMapping is not null) { Sql.Append("JSON_QUERY("); @@ -541,7 +541,7 @@ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExp GenerateJsonPath(jsonScalarExpression.Path); Sql.Append(")"); - if (jsonScalarExpression.TypeMapping is not SqlServerOwnedJsonTypeMapping and not StringTypeMapping) + if (jsonScalarExpression.TypeMapping is not SqlServerStructuralJsonTypeMapping and not StringTypeMapping) { Sql.Append(" AS "); Sql.Append(jsonScalarExpression.TypeMapping!.StoreType); diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs similarity index 92% rename from src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs rename to src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs index 658d6240e02..1d2042853fa 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs @@ -14,10 +14,10 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class SqlServerOwnedJsonTypeMapping : JsonTypeMapping +public class SqlServerStructuralJsonTypeMapping : JsonTypeMapping { private static readonly MethodInfo CreateUtf8StreamMethod - = typeof(SqlServerOwnedJsonTypeMapping).GetMethod(nameof(CreateUtf8Stream), [typeof(string)])!; + = typeof(SqlServerStructuralJsonTypeMapping).GetMethod(nameof(CreateUtf8Stream), [typeof(string)])!; private static readonly MethodInfo GetStringMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetString), [typeof(int)])!; @@ -28,7 +28,7 @@ private static readonly MethodInfo GetStringMethod /// 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 static SqlServerOwnedJsonTypeMapping Default { get; } = new("nvarchar(max)"); + public static SqlServerStructuralJsonTypeMapping Default { get; } = new("nvarchar(max)"); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -36,7 +36,7 @@ private static readonly MethodInfo GetStringMethod /// 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 static SqlServerOwnedJsonTypeMapping OwnedJsonTypeDefault { get; } = new("json"); + public static SqlServerStructuralJsonTypeMapping JsonTypeDefault { get; } = new("json"); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -44,7 +44,7 @@ private static readonly MethodInfo GetStringMethod /// 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 SqlServerOwnedJsonTypeMapping(string storeType) + public SqlServerStructuralJsonTypeMapping(string storeType) : base(storeType, typeof(JsonTypePlaceholder), System.Data.DbType.String) { } @@ -84,7 +84,7 @@ public override Expression CustomizeDataReaderExpression(Expression expression) /// 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 SqlServerOwnedJsonTypeMapping(RelationalTypeMappingParameters parameters) + protected SqlServerStructuralJsonTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } @@ -105,7 +105,7 @@ protected virtual string EscapeSqlLiteral(string literal) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateNonNullSqlLiteral(object value) - => $"'{EscapeSqlLiteral(JsonSerializer.Serialize(value))}'"; + => $"'{EscapeSqlLiteral((string)value)}'"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -114,7 +114,7 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - => new SqlServerOwnedJsonTypeMapping(parameters); + => new SqlServerStructuralJsonTypeMapping(parameters); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs index 1b0bbdb08a8..237a08d7cad 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs @@ -244,8 +244,8 @@ public SqlServerTypeMappingSource( if (clrType == typeof(JsonTypePlaceholder)) { return storeTypeName == "json" - ? SqlServerOwnedJsonTypeMapping.OwnedJsonTypeDefault - : SqlServerOwnedJsonTypeMapping.Default; + ? SqlServerStructuralJsonTypeMapping.JsonTypeDefault + : SqlServerStructuralJsonTypeMapping.Default; } if (storeTypeName != null) diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommand.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommand.cs index d4d8ae07587..d8bddfa9d95 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommand.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerModificationCommand.cs @@ -84,7 +84,7 @@ protected override void ProcessSinglePropertyJsonUpdate(ref ColumnModificationPa parameters = parameters with { Value = value, - TypeMapping = parameters.TypeMapping is SqlServerOwnedJsonTypeMapping + TypeMapping = parameters.TypeMapping is SqlServerStructuralJsonTypeMapping ? SqlServerStringTypeMapping.UnicodeDefault : parameters.TypeMapping }; diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs index 51925eb4d9e..32d5931aa1e 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs @@ -84,7 +84,7 @@ public override Expression CustomizeDataReaderExpression(Expression expression) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateNonNullSqlLiteral(object value) - => $"'{EscapeSqlLiteral(JsonSerializer.Serialize(value))}'"; + => $"'{EscapeSqlLiteral((string)value)}'"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs index 88288311868..d88d187d6fa 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs @@ -1013,7 +1013,7 @@ private void ReorderOriginalComplexCollectionEntries(IComplexProperty complexPro var originalEntries = GetComplexCollectionOriginalEntries(complexProperty); var elementToOriginalEntry = new Dictionary(ReferenceEqualityComparer.Instance); - + // Build mapping of existing non-null elements to their entries for (var i = 0; i < originalEntries.Count && i < oldOriginalCollection.Count; i++) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionCosmosTest.cs index 9b8ae7ae5d4..1c6e29b8206 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionCosmosTest.cs @@ -41,7 +41,7 @@ FROM root c WHERE (( SELECT VALUE COUNT(1) FROM r IN c["RelatedCollection"] - WHERE (r["Int"] != 50)) = 2) + WHERE (r["Int"] != 8)) = 2) """); }); @@ -59,7 +59,7 @@ FROM root c WHERE (ARRAY( SELECT VALUE r["Int"] FROM r IN c["RelatedCollection"] - ORDER BY r["Id"])[0] = 21) + ORDER BY r["Id"])[0] = 8) """); } } @@ -74,7 +74,7 @@ public override Task Index_constant(bool async) """ SELECT VALUE c FROM root c -WHERE (c["RelatedCollection"][0]["Int"] = 21) +WHERE (c["RelatedCollection"][0]["Int"] = 8) """); }); @@ -90,7 +90,7 @@ public override Task Index_parameter(bool async) SELECT VALUE c FROM root c -WHERE (c["RelatedCollection"][@i]["Int"] = 21) +WHERE (c["RelatedCollection"][@i]["Int"] = 8) """); }); @@ -105,7 +105,7 @@ public override async Task Index_column(bool async) """ SELECT VALUE c FROM root c -WHERE (c["RelatedCollection"][(c["Id"] - 1)]["Int"] = 21) +WHERE (c["RelatedCollection"][(c["Id"] - 1)]["Int"] = 8) """); } } @@ -120,7 +120,7 @@ public override async Task Index_out_of_bounds(bool async) """ SELECT VALUE c FROM root c -WHERE (c["RelatedCollection"][9999]["Int"] = 50) +WHERE (c["RelatedCollection"][9999]["Int"] = 8) """); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousCosmosTest.cs index 8e4e9393b92..6cf9e3f97f0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousCosmosTest.cs @@ -36,7 +36,7 @@ public override Task Where_optional_related_property(bool async) """ SELECT VALUE c FROM root c -WHERE (c["OptionalRelated"]["Int"] = 9) +WHERE (c["OptionalRelated"]["Int"] = 8) """); }); @@ -50,7 +50,7 @@ public override Task Where_nested_related_property(bool async) """ SELECT VALUE c FROM root c -WHERE (c["RequiredRelated"]["RequiredNested"]["Int"] = 50) +WHERE (c["RequiredRelated"]["RequiredNested"]["Int"] = 8) """); }); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs index 03391f8e8f4..ed633963db9 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionCosmosTest.cs @@ -29,11 +29,11 @@ FROM root c #region Simple properties - public override Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => Fixture.NoSyncTest( async, async a => { - await base.Select_related_property(a, queryTrackingBehavior); + await base.Select_property_on_required_related(a, queryTrackingBehavior); AssertSql( """ @@ -42,11 +42,17 @@ FROM root c """); }); - public override Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => Fixture.NoSyncTest( async, async a => { - await base.Select_optional_related_property(a, queryTrackingBehavior); + // When OptionalRelated is null, the property access on it evaluates to undefined in Cosmos, causing the + // result to be filtered out entirely. + await AssertQuery( + async, + ss => ss.Set().Select(x => x.OptionalRelated!.String), + ss => ss.Set().Where(x => x.OptionalRelated != null).Select(x => x.OptionalRelated!.String), + queryTrackingBehavior: queryTrackingBehavior); AssertSql( """ @@ -55,11 +61,36 @@ FROM root c """); }); - public override Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) => Fixture.NoSyncTest( async, async a => { - await base.Select_optional_related_property_value_type(a, queryTrackingBehavior); + // When OptionalRelated is null, the property access on it evaluates to undefined in Cosmos, causing the + // result to be filtered out entirely. + await AssertQuery( + async, + ss => ss.Set().Select(x => x.OptionalRelated!.Int), + ss => ss.Set().Where(x => x.OptionalRelated != null).Select(x => x.OptionalRelated!.Int), + queryTrackingBehavior: queryTrackingBehavior); + + AssertSql( + """ +SELECT VALUE c["OptionalRelated"]["Int"] +FROM root c +"""); + }); + + public override Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => Fixture.NoSyncTest( + async, async a => + { + // When OptionalRelated is null, the property access on it evaluates to undefined in Cosmos, causing the + // result to be filtered out entirely. + await AssertQuery( + async, + ss => ss.Set().Select(x => (int?)x.OptionalRelated!.Int), + ss => ss.Set().Where(x => x.OptionalRelated != null).Select(x => (int?)x.OptionalRelated!.Int), + queryTrackingBehavior: queryTrackingBehavior); AssertSql( """ @@ -106,11 +137,11 @@ FROM root c } } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { if (async) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -123,11 +154,11 @@ FROM root c } } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { if (async) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -140,11 +171,18 @@ FROM root c } } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - if (async) + if (async && queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + // When OptionalRelated is null, the property access on it evaluates to undefined in Cosmos, causing the + // result to be filtered out entirely. + await Assert.ThrowsAsync(() => // #36403 + AssertQuery( + async, + ss => ss.Set().Select(x => x.OptionalRelated!.OptionalNested), + ss => ss.Set().Where(x => x.OptionalRelated != null).Select(x => x.OptionalRelated!.OptionalNested), + queryTrackingBehavior: queryTrackingBehavior)); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -157,11 +195,18 @@ FROM root c } } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - if (async) + if (async && queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + // When OptionalRelated is null, the property access on it evaluates to undefined in Cosmos, causing the + // result to be filtered out entirely. + await Assert.ThrowsAsync(() => // #36403 + AssertQuery( + async, + ss => ss.Set().Select(x => x.OptionalRelated!.RequiredNested), + ss => ss.Set().Where(x => x.OptionalRelated != null).Select(x => x.OptionalRelated!.RequiredNested), + queryTrackingBehavior: queryTrackingBehavior)); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -196,30 +241,32 @@ ORDER BY c["Id"] } } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { if (async && queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { - await Assert.ThrowsAsync(() => base.Select_required_related_nested_collection(async, queryTrackingBehavior)); + await Assert.ThrowsAsync(() => base.Select_nested_collection_on_required_related(async, queryTrackingBehavior)); AssertSql( """ SELECT VALUE c FROM root c +ORDER BY c["Id"] """); } } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { if (async && queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { - await Assert.ThrowsAsync(() => base.Select_optional_related_nested_collection(async, queryTrackingBehavior)); + await Assert.ThrowsAsync(() => base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior)); AssertSql( """ SELECT VALUE c FROM root c +ORDER BY c["Id"] """); } } @@ -235,23 +282,23 @@ public override async Task SelectMany_related_collection(bool async, QueryTracki } } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { if (async && queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { // The given key 'n' was not present in the dictionary - await Assert.ThrowsAsync(() => base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior)); + await Assert.ThrowsAsync(() => base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior)); AssertSql(); } } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { if (async && queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { // The given key 'n' was not present in the dictionary - await Assert.ThrowsAsync(() => base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior)); + await Assert.ThrowsAsync(() => base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior)); AssertSql(); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs new file mode 100644 index 00000000000..f9be7edda2d --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityCosmosTest.cs @@ -0,0 +1,126 @@ +// 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.Relationships.OwnedNavigations; + +public class OwnedNavigationsStructuralEqualityCosmosTest : OwnedNavigationsStructuralEqualityTestBase +{ + public OwnedNavigationsStructuralEqualityCosmosTest(OwnedNavigationsCosmosFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override Task Two_related(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Two_related(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE false +"""); + }); + + public override Task Two_nested(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Two_nested(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE false +"""); + }); + + public override Task Not_equals(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Not_equals(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE false +"""); + }); + + public override Task Related_with_inline_null(bool async) + => Assert.ThrowsAsync(() => base.Related_with_inline_null(async)); + + public override Task Related_with_parameter_null(bool async) + => Assert.ThrowsAsync(() => base.Related_with_parameter_null(async)); + + public override Task Nested_with_inline_null(bool async) + => Assert.ThrowsAsync(() => base.Nested_with_inline_null(async)); + + public override async Task Nested_with_inline(bool async) + { + if (async) + { + await base.Nested_with_inline(async); + + AssertSql(); + } + } + + public override async Task Nested_with_parameter(bool async) + { + if (async) + { + await base.Nested_with_parameter(async); + + AssertSql(); + } + } + + public override Task Two_nested_collections(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Two_nested_collections(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE false +"""); + }); + + public override async Task Nested_collection_with_inline(bool async) + { + if (async) + { + await base.Nested_collection_with_inline(async); + + AssertSql(); + } + } + + public override async Task Nested_collection_with_parameter(bool async) + { + if (async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql(); + } + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index d117ab645d6..2350e82ea46 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -35,16 +35,20 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(RelationshipsProjectionTestBase<>), typeof(RelationshipsCollectionTestBase<>), typeof(RelationshipsMiscellaneousTestBase<>), + typeof(RelationshipsStructuralEqualityTestBase<>), typeof(NavigationsIncludeTestBase<>), typeof(NavigationsProjectionTestBase<>), typeof(NavigationsCollectionTestBase<>), typeof(NavigationsMiscellaneousTestBase<>), + typeof(NavigationsStructuralEqualityTestBase<>), typeof(OwnedNavigationsProjectionTestBase<>), typeof(OwnedNavigationsCollectionTestBase<>), typeof(OwnedNavigationsMiscellaneousTestBase<>), + typeof(OwnedNavigationsStructuralEqualityTestBase<>), typeof(ComplexPropertiesProjectionTestBase<>), typeof(ComplexPropertiesCollectionTestBase<>), - typeof(ComplexPropertiesMiscellaneousTestBase<>) + typeof(ComplexPropertiesMiscellaneousTestBase<>), + typeof(ComplexPropertiesStructuralEqualityTestBase<>) }; protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonRelationalFixtureBase.cs index d2bc66bc0c8..27ac8f2e7ad 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonRelationalFixtureBase.cs @@ -9,19 +9,37 @@ public abstract class ComplexJsonRelationalFixtureBase : ComplexPropertiesFixtur { protected override string StoreName => "ComplexJsonQueryTest"; - // TODO: Temporary, until we have update pipeline support for complex JSON - protected override Task SeedAsync(PoolableDbContext context) - => throw new NotImplementedException("Must be implemented in derived provider implementations using SQL, since the update pipeline does not yet support complex JSON."); - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); modelBuilder.Entity(b => { - b.ComplexProperty(e => e.RequiredRelated, rrb => rrb.ToJson()); - b.ComplexProperty(e => e.OptionalRelated, orb => orb.ToJson()); - b.ComplexCollection(e => e.RelatedCollection, rcb => rcb.ToJson()); + b.ComplexProperty(e => e.RequiredRelated, rrb => + { + rrb.ToJson(); + + rrb.ComplexProperty(r => r.OptionalNested).IsRequired(false); + }); + + b.ComplexProperty(e => e.OptionalRelated, orb => + { + orb.ToJson(); + orb.IsRequired(false); + + orb.ComplexProperty(r => r.OptionalNested).IsRequired(false); + + // TODO: Currently everything within an optional complex type must be configured as optional - seems wrong. + // #36402 + orb.ComplexProperty(r => r.RequiredNested).IsRequired(false); + }); + + b.ComplexCollection(e => e.RelatedCollection,rcb => + { + rcb.ToJson(); + + rcb.ComplexProperty(r => r.OptionalNested).IsRequired(false); + }); }); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualityRelationalTestBase.cs new file mode 100644 index 00000000000..3e4e738aead --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualityRelationalTestBase.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexJson; + +public abstract class ComplexJsonStructuralEqualityRelationalTestBase : ComplexPropertiesStructuralEqualityTestBase + where TFixture : ComplexJsonRelationalFixtureBase, new() +{ + public ComplexJsonStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + 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 455bf2246c6..ac87561b03a 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionRelationalTestBase.cs @@ -16,21 +16,17 @@ public ComplexTableSplittingProjectionRelationalTestBase(TFixture fixture, ITest Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - // Optional complex types, #31376 - public override Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.Select_optional_related_property_value_type(async, queryTrackingBehavior)); - // Complex JSON collections, update pipeline not yet supported so no seeding, #31237 public override Task Select_related_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.Select_related_collection(async, queryTrackingBehavior)); + => Assert.ThrowsAsync(() => base.Select_related_collection(async, queryTrackingBehavior)); // Complex JSON collections, update pipeline not yet supported so no seeding, #31237 - public override Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.Select_required_related_nested_collection(async, queryTrackingBehavior)); + public override Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => Assert.ThrowsAsync(() => base.Select_nested_collection_on_required_related(async, queryTrackingBehavior)); // Optional complex types, #31376 - public override Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.Select_required_related_optional_nested(async, queryTrackingBehavior)); + public override Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => Assert.ThrowsAsync(() => base.Select_optional_nested_on_required_related(async, queryTrackingBehavior)); // Optional complex types, #31376 public override Task Select_subquery_required_related_FirstOrDefault(bool async, QueryTrackingBehavior queryTrackingBehavior) @@ -41,12 +37,12 @@ public override Task SelectMany_related_collection(bool async, QueryTrackingBeha => Assert.ThrowsAsync(() => base.SelectMany_related_collection(async, queryTrackingBehavior)); // Optional complex types, #31376 - public override Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior)); + public override Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => Assert.ThrowsAsync(() => base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior)); // Optional complex types, #31376 - public override Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => Assert.ThrowsAsync(() => base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior)); + public override Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => Assert.ThrowsAsync(() => base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior)); protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); 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 71a8c69dc01..b032125a503 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingRelationalFixtureBase.cs @@ -19,8 +19,7 @@ public abstract class ComplexTableSplittingRelationalFixtureBase : ComplexProper protected override RelationshipsData CreateData() { - // Collections are not supported with table splitting, only with JSON - var data = new RelationshipsData(withCollections: false); + var data = new RelationshipsData(); // TODO: Optional complex properties not yet supported (#31376), remove them from the seeding data foreach (var rootEntity in data.RootEntities) diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs new file mode 100644 index 00000000000..4130e083424 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualityRelationalTestBase.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.Relationships.ComplexProperties; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexTableSplitting; + +public abstract class ComplexTableSplittingStructuralEqualityRelationalTestBase : ComplexPropertiesStructuralEqualityTestBase + where TFixture : ComplexTableSplittingRelationalFixtureBase, new() +{ + public ComplexTableSplittingStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + 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(bool async) + => Assert.ThrowsAsync(() => base.Two_related(async)); + + public override Task Two_nested(bool async) + => Assert.ThrowsAsync(() => base.Two_nested(async)); + + public override Task Not_equals(bool async) + => Assert.ThrowsAsync(() => base.Not_equals(async)); + + public override Task Related_with_inline_null(bool async) + => Assert.ThrowsAsync(() => base.Related_with_inline_null(async)); + + public override Task Related_with_parameter_null(bool async) + => Assert.ThrowsAsync(() => base.Related_with_parameter_null(async)); + + public override Task Nested_with_inline_null(bool async) + => Assert.ThrowsAsync(() => base.Nested_with_inline_null(async)); + + public override Task Two_nested_collections(bool async) + => Assert.ThrowsAsync(() => base.Two_nested_collections(async)); + + // Collection equality with owned collections is not supported + public override Task Nested_collection_with_inline(bool async) + => Assert.ThrowsAsync(() => base.Nested_collection_with_inline(async)); + + // Collection equality with owned collections is not supported + public override Task Nested_collection_with_parameter(bool async) + => Assert.ThrowsAsync(() => base.Nested_collection_with_parameter(async)); + + protected void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsProjectionRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsProjectionRelationalTestBase.cs index 292753da236..7934a42f7ed 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsProjectionRelationalTestBase.cs @@ -14,6 +14,17 @@ public NavigationsProjectionRelationalTestBase(TFixture fixture, ITestOutputHelp Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + // 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(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertQuery( + async, + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection), + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection ?? new List()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: r => r.Id), + queryTrackingBehavior: queryTrackingBehavior); + protected void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsStructuralEqualityRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsStructuralEqualityRelationalTestBase.cs new file mode 100644 index 00000000000..9054118a6c6 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/Navigations/NavigationsStructuralEqualityRelationalTestBase.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit.Sdk; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.Navigations; + +public abstract class NavigationsStructuralEqualityRelationalTestBase : NavigationsStructuralEqualityTestBase + where TFixture : NavigationsRelationalFixtureBase, new() +{ + public NavigationsStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + // 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. + public override Task Two_nested_collections(bool async) + => Assert.ThrowsAsync(() => base.Two_nested_collections(async)); + + public override Task Nested_collection_with_inline(bool async) + => Assert.ThrowsAsync(() => base.Nested_collection_with_inline(async)); + + public override Task Nested_collection_with_parameter(bool async) + => Assert.ThrowsAsync(() => base.Nested_collection_with_parameter(async)); + + 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 new file mode 100644 index 00000000000..92122b8eb9d --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualityRelationalTestBase.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.Relationships.OwnedNavigations; +using Xunit.Sdk; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.OwnedJson; + +public abstract class OwnedJsonStructuralEqualityRelationalTestBase : OwnedNavigationsStructuralEqualityTestBase + where TFixture : OwnedJsonRelationalFixtureBase, new() +{ + public OwnedJsonStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + // #36401 + public override Task Related_with_parameter_null(bool async) + => Assert.ThrowsAsync(() => base.Related_with_parameter_null(async)); + + 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 1fe8a165166..e7769118e2f 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,19 @@ public OwnedNavigationsProjectionRelationalTestBase(TFixture fixture, ITestOutpu Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + // 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(bool async, QueryTrackingBehavior queryTrackingBehavior) + => queryTrackingBehavior is QueryTrackingBehavior.TrackAll + ? base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior) + : AssertQuery( + async, + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection), + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection ?? new List()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: r => r.Id), + queryTrackingBehavior: queryTrackingBehavior); + public 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 new file mode 100644 index 00000000000..5aa7058c657 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityRelationalTestBase.cs @@ -0,0 +1,18 @@ +// 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.Relationships.OwnedNavigations; + +public abstract class OwnedNavigationsStructuralEqualityRelationalTestBase : OwnedNavigationsStructuralEqualityTestBase + where TFixture : OwnedNavigationsRelationalFixtureBase, new() +{ + public OwnedNavigationsStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} 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 bb15d0bb5f8..990ce547818 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionRelationalTestBase.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Query.Relationships.OwnedNavigations; -using Xunit.Sdk; namespace Microsoft.EntityFrameworkCore.Query.Relationships.OwnedTableSplitting; @@ -17,13 +16,19 @@ public OwnedTableSplittingProjectionRelationalTestBase(TFixture fixture, ITestOu Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - // // The following are tests projecting out collections, which aren't supported with table splitting. - // // Collection properties are ignored in the model, and since these tests project we client-eval and get an assertion failure. - // public override Task Select_related_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - // => Assert.ThrowsAsync(() => base.Select_related_collection(async, queryTrackingBehavior)); - - // public override Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - // => Assert.ThrowsAsync(() => base.Select_optional_related_nested_collection(async, 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. + public override Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery( + queryTrackingBehavior, + () => AssertQuery( + async, + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection), + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection ?? new List()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: r => r.Id), + queryTrackingBehavior: queryTrackingBehavior)); 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 new file mode 100644 index 00000000000..ce11febecca --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualityRelationalTestBase.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.Relationships.OwnedNavigations; +using Xunit.Sdk; + +namespace Microsoft.EntityFrameworkCore.Query.Relationships.OwnedTableSplitting; + +public abstract class OwnedTableSplittingStructuralEqualityRelationalTestBase : OwnedNavigationsStructuralEqualityTestBase + where TFixture : OwnedTableSplittingRelationalFixtureBase, new() +{ + public OwnedTableSplittingStructuralEqualityRelationalTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesFixtureBase.cs index 99caf2a92b6..45537b199a5 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesFixtureBase.cs @@ -7,14 +7,6 @@ public abstract class ComplexPropertiesFixtureBase : RelationshipsQueryFixtureBa { protected override string StoreName => "ComplexRelationshipsQueryTest"; - protected override Task SeedAsync(PoolableDbContext context) - { - var rootEntities = RelationshipsData.CreateRootEntities(); - context.Set().AddRange(rootEntities); - - return context.SaveChangesAsync(); - } - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); diff --git a/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs new file mode 100644 index 00000000000..4f8f13db0dc --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/ComplexProperties/ComplexPropertiesStructuralEqualityTestBase.cs @@ -0,0 +1,95 @@ +// 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.Relationships.ComplexProperties; + +public abstract class ComplexPropertiesStructuralEqualityTestBase(TFixture fixture) + : RelationshipsStructuralEqualityTestBase(fixture) + where TFixture : ComplexPropertiesFixtureBase, new() +{ + // The below overrides are in order to add the client-side by-value comparison (Equals instead of ==), to account for the + // by-value server-side behavior of complex properties. + // TODO: Ideally do this rewriting automatically via a visitor + + public override Task Two_related(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated == e.OptionalRelated), + ss => ss.Set().Where(e => e.RequiredRelated.Equals(e.OptionalRelated))); + + public override Task Two_nested(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested == e.OptionalRelated!.RequiredNested), + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested.Equals(e.OptionalRelated!.RequiredNested))); + + public override Task Not_equals(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated != e.OptionalRelated), + ss => ss.Set().Where(e => !e.RequiredRelated.Equals(e.OptionalRelated))); + + public override Task Nested_with_inline(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Where(e => e.RequiredRelated.RequiredNested == new NestedType { Id = 1000, Name = "Root1_RequiredRelated_RequiredNested", Int = 8, String = "foo" }), + ss => ss.Set() + .Where(e => e.RequiredRelated.RequiredNested.Equals(new NestedType { Id = 1000, Name = "Root1_RequiredRelated_RequiredNested", Int = 8, String = "foo" }))); + + public override async Task Nested_with_parameter(bool async) + { + var nested = new NestedType { Id = 1000, Name = "Root1_RequiredRelated_RequiredNested", Int = 8, String = "foo" }; + + await AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested == nested), + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested.Equals(nested))); + } + + public override Task Two_nested_collections(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == e.OptionalRelated!.NestedCollection), + ss => ss.Set().Where(e => e.OptionalRelated != null && e.RequiredRelated.NestedCollection.SequenceEqual(e.OptionalRelated!.NestedCollection))); + + public override Task Nested_collection_with_inline(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Where(e => e.RequiredRelated.NestedCollection == new List + { + new() { Id = 1002, Name = "Root1_RequiredRelated_NestedCollection_1", Int = 8, String = "foo" }, + new() { Id = 1003, Name = "Root1_RequiredRelated_NestedCollection_2", Int = 8, String = "foo" } + }), + ss => ss.Set() + .Where(e => e.RequiredRelated.NestedCollection.SequenceEqual(new List + { + new() { Id = 1002, Name = "Root1_RequiredRelated_NestedCollection_1", Int = 8, String = "foo" }, + new() { Id = 1003, Name = "Root1_RequiredRelated_NestedCollection_2", Int = 8, String = "foo" } + }))); + + public override async Task Nested_collection_with_parameter(bool async) + { + var nestedCollection = new List + { + new() { Id = 1002, Name = "Root1_RequiredRelated_NestedCollection_1", Int = 8, String = "foo" }, + new() { Id = 1003, Name = "Root1_RequiredRelated_NestedCollection_2", Int = 8, String = "foo" } + }; + + await AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == nestedCollection), + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection.SequenceEqual(nestedCollection))); + } + + // TODO: the following is temporary until change tracking is implemented for complex JSON types (#35962) + private readonly TrackingRewriter _trackingRewriter = new(QueryTrackingBehavior.NoTracking); + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var rewritten = _trackingRewriter.Visit(serverQueryExpression); + + return rewritten; + } +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/Navigations/NavigationsStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/Navigations/NavigationsStructuralEqualityTestBase.cs new file mode 100644 index 00000000000..20efc6ae716 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/Navigations/NavigationsStructuralEqualityTestBase.cs @@ -0,0 +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.Relationships.Navigations; + +public abstract class NavigationsStructuralEqualityTestBase(TFixture fixture) + : RelationshipsStructuralEqualityTestBase(fixture) + where TFixture : NavigationsFixtureBase, new() +{ +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsFixtureBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsFixtureBase.cs index 1adcb7e9c60..675b58d7be5 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsFixtureBase.cs @@ -7,14 +7,6 @@ public abstract class OwnedNavigationsFixtureBase : RelationshipsQueryFixtureBas { protected override string StoreName => "OwnedNavigationsQueryTest"; - protected override Task SeedAsync(PoolableDbContext context) - { - var rootEntities = RelationshipsData.CreateRootEntities(); - context.Set().AddRange(rootEntities); - - return context.SaveChangesAsync(); - } - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); @@ -66,6 +58,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con }); } + protected override RelationshipsData CreateData() + { + var data = base.CreateData(); + + // Owned mapping does not support the same instance being referenced multiple times; + // go over the referential identity entity and clone. + var rootEntity = data.RootEntities.Single(e => e.Name.EndsWith("With_referential_identity")); + rootEntity.RequiredRelated.OptionalNested = rootEntity.RequiredRelated.RequiredNested.DeepClone(); + rootEntity.OptionalRelated = rootEntity.RequiredRelated.DeepClone(); + rootEntity.RelatedCollection = rootEntity.RelatedCollection.Select(r => r.DeepClone()).ToList(); + + return data; + } + // Derived fixtures may need to ignore some owned navigations that are mapped in this fixture. public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) => builder.ConfigureWarnings(b => diff --git a/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionTestBase.cs index 18a63ab30e2..d7ccd13408e 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionTestBase.cs @@ -14,17 +14,17 @@ public override Task Select_related(bool async, QueryTrackingBehavior queryTrack public override Task Select_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_optional_related(async, queryTrackingBehavior)); - public override Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_required_related_required_nested(async, queryTrackingBehavior)); + public override Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_required_nested_on_required_related(async, queryTrackingBehavior)); - public override Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_required_related_optional_nested(async, queryTrackingBehavior)); + public override Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_optional_nested_on_required_related(async, queryTrackingBehavior)); - public override Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_optional_related_required_nested(async, queryTrackingBehavior)); + public override Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_required_nested_on_optional_related(async, queryTrackingBehavior)); - public override Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_optional_related_optional_nested(async, queryTrackingBehavior)); + public override Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior)); #endregion Non-collection @@ -33,20 +33,20 @@ public override Task Select_optional_related_optional_nested(bool async, QueryTr public override Task Select_related_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_related_collection(async, queryTrackingBehavior)); - public override Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_required_related_nested_collection(async, queryTrackingBehavior)); + public override Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_nested_collection_on_required_related(async, queryTrackingBehavior)); - public override Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_optional_related_nested_collection(async, queryTrackingBehavior)); + public override Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior)); public override Task SelectMany_related_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.SelectMany_related_collection(async, queryTrackingBehavior)); - public override Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior)); + public override Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior)); - public override Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior)); + public override Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertOwnedTrackingQuery(queryTrackingBehavior, () => base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior)); #endregion Collection diff --git a/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityTestBase.cs new file mode 100644 index 00000000000..f0d5f8019ad --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualityTestBase.cs @@ -0,0 +1,53 @@ +// 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.Relationships.OwnedNavigations; + +public abstract class OwnedNavigationsStructuralEqualityTestBase(TFixture fixture) + : RelationshipsStructuralEqualityTestBase(fixture) + where TFixture : OwnedNavigationsFixtureBase, new() +{ + public override Task Two_related(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated == e.OptionalRelated), + ss => ss.Set().Where(e => false), // Owned entities are never equal + assertEmpty: true); + + public override Task Two_nested(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested == e.OptionalRelated!.RequiredNested), + ss => ss.Set().Where(e => false), // Owned entities are never equal + assertEmpty: true); + + public override Task Not_equals(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated != e.OptionalRelated), + ss => ss.Set().Where(e => false), // TODO: unclear, this should be true + assertEmpty: true); + + // #36400 + public override Task Nested_with_inline(bool async) + => Assert.ThrowsAsync(() => base.Nested_with_inline(async)); + + // #36400 + public override Task Nested_with_parameter(bool async) + => Assert.ThrowsAsync(() => base.Nested_with_parameter(async)); + + public override Task Two_nested_collections(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == e.OptionalRelated!.NestedCollection), + ss => ss.Set().Where(e => false), // Owned entities are never equal + assertEmpty: true); + + // #36400 + public override Task Nested_collection_with_inline(bool async) + => Assert.ThrowsAsync(() => base.Nested_collection_with_inline(async)); + + // #36400 + public override Task Nested_collection_with_parameter(bool async) + => Assert.ThrowsAsync(() => base.Nested_collection_with_parameter(async)); +} diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsCollectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsCollectionTestBase.cs index e0fa150a24f..b14fdbd12e3 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsCollectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsCollectionTestBase.cs @@ -21,7 +21,7 @@ public virtual Task Count(bool async) public virtual Task Where(bool async) => AssertQuery( async, - ss => ss.Set().Where(e => e.RelatedCollection.Where(r => r.Int != 50).Count() == 2)); + ss => ss.Set().Where(e => e.RelatedCollection.Where(r => r.Int != 8).Count() == 2)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -29,9 +29,11 @@ public virtual Task OrderBy_ElementAt(bool async) => AssertQuery( async, ss => ss.Set().Where( - e => e.RelatedCollection.OrderBy(r => r.Id).ElementAt(0).Int == 21), + e => e.RelatedCollection.OrderBy(r => r.Id).ElementAt(0).Int == 8), ss => ss.Set().Where(e => e.RelatedCollection.Count > 0 - && e.RelatedCollection.OrderBy(r => r.Id).ElementAt(0).Int == 21)); + && e.RelatedCollection.OrderBy(r => r.Id).ElementAt(0).Int == 8)); + + #region Index [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -39,8 +41,8 @@ public virtual Task Index_constant(bool async) => AssertOrderedCollectionQuery( () => AssertQuery( async, - ss => ss.Set().Where(e => e.RelatedCollection[0].Int == 21), - ss => ss.Set().Where(e => e.RelatedCollection.Count > 0 && e.RelatedCollection[0].Int == 21))); + ss => ss.Set().Where(e => e.RelatedCollection[0].Int == 8), + ss => ss.Set().Where(e => e.RelatedCollection.Count > 0 && e.RelatedCollection[0].Int == 8))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -51,8 +53,8 @@ public virtual Task Index_parameter(bool async) return AssertQuery( async, - ss => ss.Set().Where(e => e.RelatedCollection[i].Int == 21), - ss => ss.Set().Where(e => e.RelatedCollection.Count > 0 && e.RelatedCollection[i].Int == 21)); + ss => ss.Set().Where(e => e.RelatedCollection[i].Int == 8), + ss => ss.Set().Where(e => e.RelatedCollection.Count > 0 && e.RelatedCollection[i].Int == 8)); }); [ConditionalTheory] @@ -61,8 +63,8 @@ public virtual Task Index_column(bool async) => AssertOrderedCollectionQuery( () => AssertQuery( async, - ss => ss.Set().Where(e => e.RelatedCollection[e.Id - 1].Int == 21), - ss => ss.Set().Where(e => e.RelatedCollection.Count > e.Id - 1 && e.RelatedCollection[e.Id - 1].Int == 21))); + ss => ss.Set().Where(e => e.RelatedCollection[e.Id - 1].Int == 8), + ss => ss.Set().Where(e => e.RelatedCollection.Count > e.Id - 1 && e.RelatedCollection[e.Id - 1].Int == 8))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -70,10 +72,12 @@ public virtual Task Index_out_of_bounds(bool async) => AssertOrderedCollectionQuery( () => AssertQuery( async, - ss => ss.Set().Where(e => e.RelatedCollection[9999].Int == 50), + ss => ss.Set().Where(e => e.RelatedCollection[9999].Int == 8), ss => ss.Set().Where(e => false), assertEmpty: true)); + #endregion Index + /// /// Utility for tests that depend on the collection being naturally ordered /// (e.g. JSON collection as opposed to a classical relational collection navigation, which is unordered). diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs index 6079ee7b5e4..8cd35a1b3e1 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsData.cs @@ -5,114 +5,197 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships; public class RelationshipsData : ISetSource { - public RelationshipsData(bool withCollections = true) + public RelationshipsData() { - RootEntities = CreateRootEntities(withCollections); - MainTypes = []; + RootEntities = CreateRootEntities(); + RelatedTypes = []; NestedTypes = []; PreRootEntities = []; } - // TODO: Remove? Relevant only for non-owned navigations public List RootEntities { get; } - public List MainTypes { get; } + + // TODO: Remove? Relevant only for non-owned navigations + public List RelatedTypes { get; } public List NestedTypes { get; } + public List PreRootEntities { get; } - public static List CreateRootEntities(bool withCollections = true) + public static List CreateRootEntities() { + var id = 1; + List rootEntities = [ - new RootEntity + // First basic entity with all properties set + CreateRootEntity(id++, description: null), + + // Second basic entity with all properties set to something different + CreateRootEntity(id++, description: "With_different_values", e => + { + SetRelatedValues(e.RequiredRelated); + + if (e.OptionalRelated is not null) + { + SetRelatedValues(e.OptionalRelated); + } + + foreach (var related in e.RelatedCollection) + { + SetRelatedValues(related); + } + + void SetRelatedValues(RelatedType related) + { + related.Int = 9; + related.String = "bar"; + related.RequiredNested.Int = 9; + related.RequiredNested.String = "bar"; + related.OptionalNested?.Int = 9; + related.OptionalNested?.String = "bar"; + foreach (var nested in related.NestedCollection) + { + nested.Int = 9; + nested.String = "bar"; + } + } + }), + + // 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 . + CreateRootEntity(id++, description: "With_referential_identity", e => + { + e.OptionalRelated = e.RequiredRelated; + e.RequiredRelated.OptionalNested = e.RequiredRelated.RequiredNested; + e.OptionalRelated.OptionalNested = e.RequiredRelated.RequiredNested; + + e.RelatedCollection.Clear(); + e.RequiredRelated.NestedCollection.Clear(); + e.OptionalRelated.NestedCollection.Clear(); + }), + + // Entity where everything optional is null + CreateRootEntity(id++, description: "All_optionals_null", e => + { + e.RequiredRelated.OptionalNested = null; + e.OptionalRelated = null; + + foreach (var related in e.RelatedCollection) + { + related.OptionalNested = null; + } + }), + + // Entity where all collections are empty + CreateRootEntity(id++, description: "All_collections_empty", e => + { + e.RelatedCollection.Clear(); + e.RequiredRelated.NestedCollection.Clear(); + e.OptionalRelated!.NestedCollection.Clear(); + }) + ]; + + return rootEntities; + + RootEntity CreateRootEntity(int id, string? description, Action? customizer = null) + { + var shortName = $"Root{id}"; + var relatedId = id * 100; + var nestedId = id * 1000; + + const int intValue = 8; + const string stringValue = "foo"; + + var rootEntity = new RootEntity { - Id = 1, - Name = "Root1", + Id = id, + Name = description is null ? shortName : $"{shortName}_{description}", RequiredRelated = new RelatedType { - Id = 100, - Name = "Root1_RequiredRelated", + Id = relatedId++, + Name = $"{shortName}_RequiredRelated", - Int = 8, - String = "foo", + Int = intValue, + String = stringValue, RequiredNested = new NestedType { - Id = 1000, - Name = "Root1_RequiredRelated_RequiredNested", + Id = nestedId++, + Name = $"{shortName}_RequiredRelated_RequiredNested", - Int = 50, - String = "foo_foo" + Int = intValue, + String = stringValue }, OptionalNested = new NestedType { - Id = 1001, - Name = "Root1_RequiredRelated_OptionalNested", + Id = nestedId++, + Name = $"{shortName}_RequiredRelated_OptionalNested", - Int = 51, - String = "foo_bar" + Int = intValue, + String = stringValue }, NestedCollection = [ new NestedType { - Id = 1002, - Name = "Root1_RequiredRelated_NestedCollection_1", + Id = nestedId++, + Name = $"{shortName}_RequiredRelated_NestedCollection_1", - Int = 52, - String = "foo_baz1" + Int = intValue, + String = stringValue }, new NestedType { - Id = 1003, - Name = "Root1_RequiredRelated_NestedCollection_2", + Id = nestedId++, + Name = $"{shortName}_RequiredRelated_NestedCollection_2", - Int = 53, - String = "foo_baz2" + Int = intValue, + String = stringValue } ] }, OptionalRelated = new RelatedType { - Id = 101, - Name = "Root1_OptionalRelated", + Id = relatedId++, + Name = $"{shortName}_OptionalRelated", - Int = 9, - String = "bar", + Int = intValue, + String = stringValue, RequiredNested = new NestedType { - Id = 1010, - Name = "Root1_OptionalRelated_RequiredNested", + Id = nestedId++, + Name = $"{shortName}_OptionalRelated_RequiredNested", - Int = 52, - String = "bar_foo" + Int = intValue, + String = stringValue }, OptionalNested = new NestedType { - Id = 1011, - Name = "Root1_OptionalRelated_OptionalNested", + Id = nestedId++, + Name = $"{shortName}_OptionalRelated_OptionalNested", - Int = 53, - String = "bar_bar" + Int = intValue, + String = stringValue }, NestedCollection = [ new NestedType { - Id = 1012, - Name = "Root1_OptionalRelated_NestedCollection_1", + Id = nestedId++, + Name = $"{shortName}_OptionalRelated_NestedCollection_1", - Int = 54, - String = "bar_baz1" + Int = intValue, + String = stringValue }, new NestedType { - Id = 1013, - Name = "Root1_OptionalRelated_NestedCollection_2", + Id = nestedId++, + Name = $"{shortName}_OptionalRelated_NestedCollection_2", - Int = 55, - String = "bar_baz2" + Int = intValue, + String = stringValue } ] }, @@ -120,174 +203,99 @@ public static List CreateRootEntities(bool withCollections = true) [ new RelatedType { - Id = 102, - Name = "Root1_RelatedCollection_1", + Id = relatedId++, + Name = $"{shortName}_RelatedCollection_1", - Int = 21, - String = "foo", + Int = intValue, + String = stringValue, RequiredNested = new NestedType { - Id = 1020, - Name = "Root1_RelatedCollection_1_RequiredNested", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_1_RequiredNested", - Int = 50, - String = "foo_foo" + Int = intValue, + String = stringValue }, OptionalNested = new NestedType { - Id = 1021, - Name = "Root1_RelatedCollection_1_OptionalNested", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_1_OptionalNested", - Int = 51, - String = "foo_bar" + Int = intValue, + String = stringValue }, NestedCollection = [ new NestedType { - Id = 1022, - Name = "Root1_RelatedCollection_1_NestedCollection_1", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_1_NestedCollection_1", - Int = 53, - String = "foo_bar" + Int = intValue, + String = stringValue }, new NestedType { - Id = 1023, - Name = "Root1_RelatedCollection_1_NestedCollection_2", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_1_NestedCollection_2", - Int = 51, - String = "foo_bar" + Int = intValue, + String = stringValue } ] }, new RelatedType { - Id = 103, - Name = "Root1_RelatedCollection_2", + Id = relatedId++, + Name = $"{shortName}_RelatedCollection_2", - Int = 22, - String = "foo", + Int = intValue, + String = stringValue, RequiredNested = new NestedType { - Id = 1030, - Name = "Root1_RelatedCollection_2_RequiredNested", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_2_RequiredNested", - Int = 50, - String = "foo_foo" + Int = intValue, + String = stringValue }, OptionalNested = new NestedType { - Id = 1031, - Name = "Root1_RelatedCollection_2_OptionalNested", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_2_OptionalNested", - Int = 51, - String = "foo_bar" + Int = intValue, + String = stringValue }, NestedCollection = [ new NestedType { - Id = 1032, - Name = "Root1_RelatedCollection_2_NestedCollection_1", + Id = nestedId++, + Name = $"{shortName}_RelatedCollection_2_NestedCollection_1", - Int = 53, - String = "foo_bar" + Int = intValue, + String = stringValue }, new NestedType { - Id = 1033, - Name = "Root1_RelatedCollection_2_NestedCollection_2", + Id = nestedId++, + Name = $"{shortName}_Root1_RelatedCollection_2_NestedCollection_2", - Int = 51, - String = "foo_bar" + Int = intValue, + String = stringValue } ] } ] - }, - new RootEntity - { - Id = 2, - Name = "Root2", - - RequiredRelated = new RelatedType - { - Id = 200, - Name = "Root2_RequiredRelated", - - Int = 10, - String = "aaa", + }; - RequiredNested = new NestedType - { - Id = 2000, - Name = "Root2_RequiredRelated_RequiredNested", - - Int = 54, - String = "aaa_xxx" - }, - OptionalNested = new NestedType - { - Id = 2001, - Name = "Root2_RequiredRelated_OptionalNested", - - Int = 55, - String = "aaa_yyy" - }, - NestedCollection = - [ - // TODO - ] - }, - OptionalRelated = new RelatedType - { - Id = 201, - Name = "Root2_OptionalRelated", - - Int = 11, - String = "bbb", - - RequiredNested = new NestedType - { - Id = 2010, - Name = "Root2_OptionalRelated_RequiredNested", + customizer?.Invoke(rootEntity); - Int = 56, - String = "bbb_xxx" - }, - OptionalNested = new NestedType - { - Id = 2011, - Name = "Root2_OptionalRelated_OptionalNested", - - Int = 57, - String = "bbb_yyy" - }, - NestedCollection = - [ - // TODO - ] - }, - RelatedCollection = - [ - ] - } - ]; - - if (!withCollections) - { - foreach (var rootEntity in rootEntities) - { - rootEntity.RelatedCollection = []; - rootEntity.RequiredRelated.NestedCollection = []; - rootEntity.OptionalRelated?.NestedCollection = []; - } + return rootEntity; } - - return rootEntities; } public IQueryable Set() @@ -295,7 +303,7 @@ public IQueryable Set() => typeof(TEntity) switch { var t when t == typeof(RootEntity) => (IQueryable)RootEntities.AsQueryable(), - var t when t == typeof(RelatedType) => (IQueryable)MainTypes.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(), diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsMiscellaneousTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsMiscellaneousTestBase.cs index a84e4b6f7d0..cbd0367d7d8 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsMiscellaneousTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsMiscellaneousTestBase.cs @@ -20,14 +20,14 @@ public virtual Task Where_related_property(bool async) public virtual Task Where_optional_related_property(bool async) => AssertQuery( async, - ss => ss.Set().Where(e => e.OptionalRelated!.Int == 9)); + ss => ss.Set().Where(e => e.OptionalRelated!.Int == 8)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_nested_related_property(bool async) => AssertQuery( async, - ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested.Int == 50)); + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested.Int == 8)); #endregion Simple filters } diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsModel.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsModel.cs index 26c448315b8..6989ee32d50 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsModel.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsModel.cs @@ -25,14 +25,16 @@ public class RootEntity [NotMapped] // Explicitly mapped via Fluent API for navigation tests only public int RequiredRelatedId { get; set; } [NotMapped] // Explicitly mapped via Fluent API for navigation tests only - public int OptionalRelatedId { get; set; } + public int? OptionalRelatedId { get; set; } + + public override string ToString() => Name; } /// /// The main type to be tested; mapped differently (entity type, complex type...) across /// different test variations. /// -public class RelatedType +public class RelatedType : IEquatable { public int Id { get; set; } public required string Name { get; set; } @@ -59,13 +61,39 @@ public class RelatedType public RootEntity RelatedCollectionInverse { get; set; } = null!; [NotMapped] public int? CollectionRootId { get; set; } + + public bool Equals(RelatedType? other) + => other is not null + && Id == other.Id + && Name == other.Name + && Int == other.Int + && String == other.String + && RequiredNested.Equals(other.RequiredNested) + && (OptionalNested is null && other.OptionalNested is null || OptionalNested?.Equals(other.RequiredNested) == true) + && NestedCollection.SequenceEqual(other.NestedCollection); + + public RelatedType DeepClone() + => new() + { + Id = Id, + Name = Name, + Int = Int, + String = String, + + RequiredNested = RequiredNested.DeepClone(), + OptionalNested = OptionalNested?.DeepClone(), + + NestedCollection = NestedCollection.Select(n => n.DeepClone()).ToList() + }; + + public override string ToString() => Name; } /// /// An additional nested type contained within , for tests which exercise /// nested relationships. /// -public class NestedType +public class NestedType : IEquatable { public int Id { get; set; } public required string Name { get; set; } @@ -83,6 +111,24 @@ public class NestedType public RelatedType NestedCollectionInverse { get; set; } = null!; [NotMapped] public int? CollectionRelatedId { get; set; } + + public bool Equals(NestedType? other) + => other is not null + && Id == other.Id + && Name == other.Name + && Int == other.Int + && String == other.String; + + public NestedType DeepClone() + => new() + { + Id = Id, + Name = Name, + Int = Int, + String = String + }; + + public override string ToString() => Name; } /// diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs index 2a381abb3f3..ea771548181 100644 --- a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsProjectionTestBase.cs @@ -18,7 +18,7 @@ public virtual Task Select_root(bool async, QueryTrackingBehavior queryTrackingB [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().Select(x => x.RequiredRelated.String), @@ -26,7 +26,7 @@ public virtual Task Select_related_property(bool async, QueryTrackingBehavior qu [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().Select(x => x.OptionalRelated!.String), @@ -34,10 +34,21 @@ public virtual Task Select_optional_related_property(bool async, QueryTrackingBe [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) + // We have an entity with OptionalRelated null, so projecting a value type property from that throws + // "Nullable object must have a value" + => Assert.ThrowsAsync(() => + AssertQuery( + async, + ss => ss.Set().Select(x => x.OptionalRelated!.Int), + queryTrackingBehavior: queryTrackingBehavior)); + + [ConditionalTheory] + [MemberData(nameof(AsyncAndTrackingData))] + public virtual Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, - ss => ss.Set().Select(x => x.OptionalRelated!.Int), + ss => ss.Set().Select(x => (int?)x.OptionalRelated!.Int), queryTrackingBehavior: queryTrackingBehavior); #endregion Simple properties @@ -62,7 +73,7 @@ public virtual Task Select_optional_related(bool async, QueryTrackingBehavior qu [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().Select(x => x.RequiredRelated.RequiredNested), @@ -70,7 +81,7 @@ public virtual Task Select_required_related_required_nested(bool async, QueryTra [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().Select(x => x.RequiredRelated.OptionalNested), @@ -78,7 +89,7 @@ public virtual Task Select_required_related_optional_nested(bool async, QueryTra [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().Select(x => x.OptionalRelated!.RequiredNested), @@ -86,7 +97,7 @@ public virtual Task Select_optional_related_required_nested(bool async, QueryTra [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().Select(x => x.OptionalRelated!.OptionalNested), @@ -111,20 +122,20 @@ public virtual Task Select_related_collection(bool async, QueryTrackingBehavior [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, - ss => ss.Set().Select(x => x.RequiredRelated.NestedCollection), + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.RequiredRelated.NestedCollection), assertOrder: true, elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: r => r.Id), queryTrackingBehavior: queryTrackingBehavior); [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, - ss => ss.Set().Select(x => x.OptionalRelated!.NestedCollection), + ss => ss.Set().OrderBy(e => e.Id).Select(x => x.OptionalRelated!.NestedCollection), assertOrder: true, elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: r => r.Id), queryTrackingBehavior: queryTrackingBehavior); @@ -139,7 +150,7 @@ public virtual Task SelectMany_related_collection(bool async, QueryTrackingBehav [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set().SelectMany(x => x.RequiredRelated.NestedCollection), @@ -147,7 +158,7 @@ public virtual Task SelectMany_required_related_nested_collection(bool async, Qu [ConditionalTheory] [MemberData(nameof(AsyncAndTrackingData))] - public virtual Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public virtual Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertQuery( async, ss => ss.Set() diff --git a/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs new file mode 100644 index 00000000000..0478c76f103 --- /dev/null +++ b/test/EFCore.Specification.Tests/Query/Relationships/RelationshipsStructuralEqualityTestBase.cs @@ -0,0 +1,123 @@ +// 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.Relationships; + +public abstract class RelationshipsStructuralEqualityTestBase(TFixture fixture) : QueryTestBase(fixture) + where TFixture : RelationshipsQueryFixtureBase, new() +{ + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Two_related(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated == e.OptionalRelated), + ss => ss.Set().Where(e => e.RequiredRelated.Equals(e.OptionalRelated))); // TODO: Rewrite equality to Equals for the entire test suite for complex + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Two_nested(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested == e.OptionalRelated!.RequiredNested), + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested.Equals(e.OptionalRelated!.RequiredNested))); // TODO: Rewrite equality to Equals for the entire test suite for complex + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Not_equals(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated != e.OptionalRelated), + ss => ss.Set().Where(e => !e.RequiredRelated.Equals(e.OptionalRelated))); // TODO: Rewrite equality to Equals for the entire test suite for complex + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Related_with_inline_null(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.OptionalRelated == null)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Related_with_parameter_null(bool async) + { + RelatedType? related = null; + + await AssertQuery( + async, + ss => ss.Set().Where(e => e.OptionalRelated == related)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nested_with_inline_null(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.OptionalNested == null)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nested_with_inline(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Where(e => e.RequiredRelated.RequiredNested == new NestedType { Id = 1000, Name = "Root1_RequiredRelated_RequiredNested", Int = 8, String = "foo" }), + ss => ss.Set() + .Where(e => e.RequiredRelated.RequiredNested.Equals(new NestedType { Id = 1000, Name = "Root1_RequiredRelated_RequiredNested", Int = 8, String = "foo" }))); // TODO: Rewrite equality to Equals for the entire test suite for complex + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Nested_with_parameter(bool async) + { + var nested = new NestedType { Id = 1000, Name = "Root1_RequiredRelated_RequiredNested", Int = 8, String = "foo" }; + + await AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested == nested), + ss => ss.Set().Where(e => e.RequiredRelated.RequiredNested.Equals(nested))); // TODO: Rewrite equality to Equals for the entire test suite for complex + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Two_nested_collections(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == e.OptionalRelated!.NestedCollection), + ss => ss.Set().Where(e => e.OptionalRelated != null && e.RequiredRelated.NestedCollection.SequenceEqual(e.OptionalRelated!.NestedCollection))); // TODO: Rewrite equality to Equals for the entire test suite for complex + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nested_collection_with_inline(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Where(e => e.RequiredRelated.NestedCollection == new List + { + new() { Id = 1002, Name = "Root1_RequiredRelated_NestedCollection_1", Int = 8, String = "foo" }, + new() { Id = 1003, Name = "Root1_RequiredRelated_NestedCollection_2", Int = 8, String = "foo" } + }), + ss => ss.Set() + .Where(e => e.RequiredRelated.NestedCollection.SequenceEqual(new List + { + new() { Id = 1002, Name = "Root1_RequiredRelated_NestedCollection_1", Int = 8, String = "foo" }, + new() { Id = 1003, Name = "Root1_RequiredRelated_NestedCollection_2", Int = 8, String = "foo" } + }))); // TODO: Rewrite equality to Equals for the entire test suite for complex + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Nested_collection_with_parameter(bool async) + { + var nestedCollection = new List + { + new() { Id = 1002, Name = "Root1_RequiredRelated_NestedCollection_1", Int = 8, String = "foo" }, + new() { Id = 1003, Name = "Root1_RequiredRelated_NestedCollection_2", Int = 8, String = "foo" } + }; + + await AssertQuery( + async, + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection == nestedCollection), + ss => ss.Set().Where(e => e.RequiredRelated.NestedCollection.SequenceEqual(nestedCollection))); // TODO: Rewrite equality to Equals for the entire test suite for complex + } + + // TODO: Equality on subquery +} diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs index 4bc165d58ad..c7c6a62b95c 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs @@ -1633,8 +1633,10 @@ public void AssertCollection( case (null, null): return; case (null, not null): + Assert.Null(actual); + throw new UnreachableException(); case (not null, null): - Assert.Fail($"Nullability doesn't match. Expected: {(expected == null ? "NULL" : "NOT NULL")}. Actual: {(actual == null ? "NULL." : "NOT NULL.")}."); + Assert.NotNull(actual); throw new UnreachableException(); case (not null, not null): break; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonCollectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonCollectionSqlServerTest.cs index b2107e5a853..62f18a651ed 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonCollectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonCollectionSqlServerTest.cs @@ -33,7 +33,7 @@ FROM [RootEntity] AS [r] WHERE ( SELECT COUNT(*) FROM OPENJSON([r].[RelatedCollection], '$') WITH ([Int] int '$.Int') AS [r0] - WHERE [r0].[Int] <> 50) = 2 + WHERE [r0].[Int] <> 8) = 2 """); } @@ -52,7 +52,7 @@ FROM OPENJSON([r].[RelatedCollection], '$') WITH ( [Int] int '$.Int' ) AS [r0] ORDER BY [r0].[Id] - OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 8 """); } @@ -66,7 +66,7 @@ public override async Task Index_constant(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0]') AS int) = 21 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0]') AS int) = 8 """); } @@ -82,7 +82,7 @@ public override async Task Index_parameter(bool async) SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST(@i AS nvarchar(max)) + ']') AS int) = 21 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST(@i AS nvarchar(max)) + ']') AS int) = 8 """); } @@ -96,7 +96,7 @@ public override async Task Index_column(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST([r].[Id] - 1 AS nvarchar(max)) + ']') AS int) = 21 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST([r].[Id] - 1 AS nvarchar(max)) + ']') AS int) = 8 """); } @@ -108,7 +108,7 @@ public override async Task Index_out_of_bounds(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[9999]') AS int) = 50 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[9999]') AS int) = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs index f88248fae34..df28539e3bd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonMiscellaneousSqlServerTest.cs @@ -28,7 +28,7 @@ public override async Task Where_optional_related_property(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[OptionalRelated], '$.Int') AS int) = 9 +WHERE CAST(JSON_VALUE([r].[OptionalRelated], '$.Int') AS int) = 8 """); } @@ -40,7 +40,7 @@ public override async Task Where_nested_related_property(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RequiredRelated], '$.RequiredNested.Int') AS int) = 50 +WHERE CAST(JSON_VALUE([r].[RequiredRelated], '$.RequiredNested.Int') AS int) = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs index 6ad2f6fd5b1..ddde150dac4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqlServerTest.cs @@ -19,9 +19,9 @@ FROM [RootEntity] AS [r] #region Simple properties - public override async Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_related_property(async, queryTrackingBehavior); + await base.Select_property_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -30,9 +30,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property(async, queryTrackingBehavior); + await base.Select_property_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -41,9 +41,20 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property_value_type(async, queryTrackingBehavior); + await base.Select_value_type_property_on_null_related_throws(async, queryTrackingBehavior); + + AssertSql( + """ +SELECT CAST(JSON_VALUE([r].[OptionalRelated], '$.Int') AS int) +FROM [RootEntity] AS [r] +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(async, queryTrackingBehavior); AssertSql( """ @@ -78,9 +89,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -89,9 +100,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -100,9 +111,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -111,9 +122,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -138,25 +149,27 @@ ORDER BY [r].[Id] """); } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_required_related(async, queryTrackingBehavior); AssertSql( """ SELECT JSON_QUERY([r].[RequiredRelated], '$.NestedCollection') FROM [RootEntity] AS [r] +ORDER BY [r].[Id] """); } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior); AssertSql( """ SELECT JSON_QUERY([r].[OptionalRelated], '$.NestedCollection') FROM [RootEntity] AS [r] +ORDER BY [r].[Id] """); } @@ -180,9 +193,9 @@ [RequiredNested] nvarchar(max) '$.RequiredNested' AS JSON """); } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -197,9 +210,9 @@ [String] nvarchar(max) '$.String' """); } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior); AssertSql( """ diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqlServerFixture.cs index ea3264387b4..257dc4714d9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqlServerFixture.cs @@ -7,99 +7,4 @@ public class ComplexJsonSqlServerFixture : ComplexJsonRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; - - protected override async Task SeedAsync(PoolableDbContext context) - { - // TODO: Temporary, until we have update pipeline support for complex JSON - await context.Database.ExecuteSqlAsync($$$""" -INSERT INTO RootEntity (Id, Name, RequiredRelated, OptionalRelated, RelatedCollection) VALUES -( - 1, - 'Root1', - -- RequiredRelated: - '{ - "Id": 100, - "Name": "Root1_RequiredRelated", - "Int": 8, - "String": "foo", - "RequiredNested": { "Id": 1000, "Name": "Root1_RequiredRelated_RequiredNested", "Int": 50, "String": "foo_foo" }, - "OptionalNested": { "Id": 1001, "Name": "Root1_RequiredRelated_OptionalNested", "Int": 51, "String": "foo_bar" }, - "NestedCollection": - [ - { "Id": 1002, "Name": "Root1_RequiredRelated_NestedCollection_1", "Int": 52, "String": "foo_baz1" }, - { "Id": 1003, "Name": "Root1_RequiredRelated_NestedCollection_2", "Int": 53, "String": "foo_baz2" } - ] - }', - -- OptionalRelated: - '{ - "Id": 101, - "Name": "Root1_OptionalRelated", - "Int": 9, - "String": "bar", - "RequiredNested": { "Id": 1010, "Name": "Root1_OptionalRelated_RequiredNested", "Int": 52, "String": "bar_foo" }, - "OptionalNested": { "Id": 1011, "Name": "Root1_OptionalRelated_OptionalNested", "Int": 53, "String": "bar_bar" }, - "NestedCollection": - [ - { "Id": 1012, "Name": "Root1_OptionalRelated_NestedCollection_1", "Int": 54, "String": "bar_baz1" }, - { "Id": 1013, "Name": "Root1_OptionalRelated_NestedCollection_2", "Int": 55, "String": "bar_baz2" } - ] - }', - -- RelatedCollection: - '[ - { - "Id": 102, - "Name": "Root1_RelatedCollection_1", - "Int": 21, - "String": "foo", - "RequiredNested": { "Id": 1020, "Name": "Root1_RelatedCollection_1_RequiredNested", "Int": 50, "String": "foo_foo" }, - "OptionalNested": { "Id": 1021, "Name": "Root1_RelatedCollection_1_OptionalNested", "Int": 51, "String": "foo_bar" }, - "NestedCollection": - [ - { "Id": 1022, "Name": "Root1_RelatedCollection_1_NestedCollection_1", "Int": 53, "String": "foo_bar" }, - { "Id": 1023, "Name": "Root1_RelatedCollection_1_NestedCollection_2", "Int": 51, "String": "foo_bar" } - ] - }, - { - "Id": 103, - "Name": "Root1_RelatedCollection_2", - "Int": 22, - "String": "foo", - "RequiredNested": { "Id": 1030, "Name": "Root1_RelatedCollection_2_RequiredNested", "Int": 50, "String": "foo_foo" }, - "OptionalNested": { "Id": 1031, "Name": "Root1_RelatedCollection_2_OptionalNested", "Int": 51, "String": "foo_bar" }, - "NestedCollection": - [ - { "Id": 1032, "Name": "Root1_RelatedCollection_2_NestedCollection_1", "Int": 53, "String": "foo_bar" }, - { "Id": 1033, "Name": "Root1_RelatedCollection_2_NestedCollection_2", "Int": 51, "String": "foo_bar" } - ] - } - ]' -), -( - 2, - 'Root2', - -- RequiredRelated: - '{ - "Id": 200, - "Name": "Root2_RequiredRelated", - "Int": 10, - "String": "aaa", - "RequiredNested": { "Id": 2000, "Name": "Root2_RequiredRelated_RequiredNested", "Int": 54, "String": "aaa_xxx" }, - "OptionalNested": { "Id": 2001, "Name": "Root2_RequiredRelated_OptionalNested", "Int": 55, "String": "aaa_yyy" }, - "NestedCollection": [] - }', - -- OptionalRelated: - '{ - "Id": 201, - "Name": "Root2_OptionalRelated", - "Int": 11, - "String": "bbb", - "RequiredNested": { "Id": 2010, "Name": "Root2_OptionalRelated_RequiredNested", "Int": 56, "String": "bbb_xxx" }, - "OptionalNested": { "Id": 2011, "Name": "Root2_OptionalRelated_OptionalNested", "Int": 57, "String": "bbb_yyy" }, - "NestedCollection": [] - }', - -- RelatedCollection: - '[]' -) -"""); - } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs new file mode 100644 index 00000000000..9dcde405af4 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs @@ -0,0 +1,148 @@ +// 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.Relationships.ComplexJson; + +public class ComplexJsonStructuralEqualitySqlServerTest(ComplexJsonSqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : ComplexJsonStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE [r].[RequiredRelated] = [r].[OptionalRelated] +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.RequiredNested') = JSON_QUERY([r].[OptionalRelated], '$.RequiredNested') +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE [r].[RequiredRelated] <> [r].[OptionalRelated] OR [r].[OptionalRelated] IS NULL +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE [r].[OptionalRelated] IS NULL +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE [r].[OptionalRelated] IS NULL +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.OptionalNested') IS NULL +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.RequiredNested') = '{"Id":1000,"Int":8,"Name":"Root1_RequiredRelated_RequiredNested","String":"foo"}' +"""); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( + """ +@entity_equality_nested='?' (Size = 80) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.RequiredNested') = @entity_equality_nested +"""); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.NestedCollection') = JSON_QUERY([r].[OptionalRelated], '$.NestedCollection') +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.NestedCollection') = '[{"Id":1002,"Int":8,"Name":"Root1_RequiredRelated_NestedCollection_1","String":"foo"},{"Id":1003,"Int":8,"Name":"Root1_RequiredRelated_NestedCollection_2","String":"foo"}]' +"""); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( + """ +@entity_equality_nestedCollection='?' (Size = 171) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.NestedCollection') = @entity_equality_nestedCollection +"""); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs index dd1a30ae2c8..c3dd0193181 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingMiscellaneousSqlServerTest.cs @@ -37,7 +37,7 @@ public override async Task Where_nested_related_property(bool async) """ 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] -WHERE [r].[RequiredRelated_RequiredNested_Int] = 50 +WHERE [r].[RequiredRelated_RequiredNested_Int] = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs index dcffc24ab0d..cf8040630b8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingProjectionSqlServerTest.cs @@ -21,9 +21,9 @@ FROM [RootEntity] AS [r] #region Simple properties - public override async Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_related_property(async, queryTrackingBehavior); + await base.Select_property_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -32,9 +32,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property(async, queryTrackingBehavior); + await base.Select_property_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -43,9 +43,20 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property_value_type(async, queryTrackingBehavior); + await base.Select_value_type_property_on_null_related_throws(async, 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] +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(async, queryTrackingBehavior); AssertSql( """ @@ -80,9 +91,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -91,9 +102,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -102,9 +113,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -113,9 +124,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -140,25 +151,27 @@ ORDER BY [r].[Id] """); } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_required_related(async, 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] """); } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_optional_related(async, 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] """); } @@ -169,16 +182,16 @@ public override async Task SelectMany_related_collection(bool async, QueryTracki AssertSql(); } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior); AssertSql(); } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior); AssertSql(); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs new file mode 100644 index 00000000000..2a8855afcb6 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqlServerTest.cs @@ -0,0 +1,106 @@ +// 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.Relationships.ComplexTableSplitting; + +public class ComplexTableSplittingStructuralEqualitySqlServerTest( + ComplexTableSplittingSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : ComplexTableSplittingStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql(); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql(); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql(); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql(); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql(); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql(); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + 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] +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' +"""); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + 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].[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] +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 +"""); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql(); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql(); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql(); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsCollectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsCollectionSqlServerTest.cs index 0235781ccb8..1a837490aae 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsCollectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsCollectionSqlServerTest.cs @@ -32,7 +32,7 @@ FROM [RootEntity] AS [r] WHERE ( SELECT COUNT(*) FROM [RelatedType] AS [r0] - WHERE [r].[Id] = [r0].[CollectionRootId] AND [r0].[Int] <> 50) = 2 + WHERE [r].[Id] = [r0].[CollectionRootId] AND [r0].[Int] <> 8) = 2 """); } @@ -49,7 +49,7 @@ SELECT [r0].[Int] FROM [RelatedType] AS [r0] WHERE [r].[Id] = [r0].[CollectionRootId] ORDER BY [r0].[Id] - OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsMiscellaneousSqlServerTest.cs index 8073a182dc4..c7ccb817270 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsMiscellaneousSqlServerTest.cs @@ -32,7 +32,7 @@ public override async Task Where_optional_related_property(bool async) SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] FROM [RootEntity] AS [r] LEFT JOIN [RelatedType] AS [r0] ON [r].[OptionalRelatedId] = [r0].[Id] -WHERE [r0].[Int] = 9 +WHERE [r0].[Int] = 8 """); } @@ -46,7 +46,7 @@ public override async Task Where_nested_related_property(bool async) FROM [RootEntity] AS [r] INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] INNER JOIN [NestedType] AS [n] ON [r0].[RequiredNestedId] = [n].[Id] -WHERE [n].[Int] = 50 +WHERE [n].[Int] = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs index 05e74200997..48d5c00ebdc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsProjectionSqlServerTest.cs @@ -19,9 +19,9 @@ FROM [RootEntity] AS [r] #region Simple properties - public override async Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_related_property(async, queryTrackingBehavior); + await base.Select_property_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -31,9 +31,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property(async, queryTrackingBehavior); + await base.Select_property_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -43,9 +43,21 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property_value_type(async, queryTrackingBehavior); + await base.Select_value_type_property_on_null_related_throws(async, queryTrackingBehavior); + + AssertSql( + """ +SELECT [r0].[Int] +FROM [RootEntity] AS [r] +LEFT JOIN [RelatedType] AS [r0] ON [r].[OptionalRelatedId] = [r0].[Id] +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(async, queryTrackingBehavior); AssertSql( """ @@ -83,9 +95,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -96,9 +108,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -109,9 +121,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -122,9 +134,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -152,9 +164,9 @@ ORDER BY [r].[Id] """); } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -166,9 +178,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -192,9 +204,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -205,9 +217,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior); AssertSql( """ diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs new file mode 100644 index 00000000000..0407d0514ad --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqlServerTest.cs @@ -0,0 +1,156 @@ +// 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.Relationships.Navigations; + +public class NavigationsStructuralEqualitySqlServerTest( + NavigationsSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : NavigationsStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +WHERE [r0].[Id] = [r1].[Id] +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +INNER JOIN [NestedType] AS [n] ON [r0].[RequiredNestedId] = [n].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +LEFT JOIN [NestedType] AS [n0] ON [r1].[RequiredNestedId] = [n0].[Id] +WHERE [n].[Id] = [n0].[Id] +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +WHERE [r0].[Id] <> [r1].[Id] OR [r1].[Id] IS NULL +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +LEFT JOIN [RelatedType] AS [r0] ON [r].[OptionalRelatedId] = [r0].[Id] +WHERE [r0].[Id] IS NULL +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +LEFT JOIN [RelatedType] AS [r0] ON [r].[OptionalRelatedId] = [r0].[Id] +WHERE [r0].[Id] IS NULL +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [NestedType] AS [n] ON [r0].[OptionalNestedId] = [n].[Id] +WHERE [n].[Id] IS NULL +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +INNER JOIN [NestedType] AS [n] ON [r0].[RequiredNestedId] = [n].[Id] +WHERE [n].[Id] = 1000 +"""); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( + """ +@entity_equality_nested_Id='1000' (Nullable = true) + +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +INNER JOIN [NestedType] AS [n] ON [r0].[RequiredNestedId] = [n].[Id] +WHERE [n].[Id] = @entity_equality_nested_Id +"""); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelatedId], [r].[RequiredRelatedId] +FROM [RootEntity] AS [r] +INNER JOIN [RelatedType] AS [r0] ON [r].[RequiredRelatedId] = [r0].[Id] +LEFT JOIN [RelatedType] AS [r1] ON [r].[OptionalRelatedId] = [r1].[Id] +WHERE [r0].[Id] = [r1].[Id] +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql(); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql(); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonCollectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonCollectionSqlServerTest.cs index 3027dda6ed9..885fbf0ce99 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonCollectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonCollectionSqlServerTest.cs @@ -36,7 +36,7 @@ FROM OPENJSON([r].[RelatedCollection], '$') WITH ( [Name] nvarchar(max) '$.Name', [String] nvarchar(max) '$.String' ) AS [r0] - WHERE [r0].[Int] <> 50) = 2 + WHERE [r0].[Int] <> 8) = 2 """); } @@ -48,7 +48,7 @@ public override async Task Index_constant(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0].Int') AS int) = 21 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0].Int') AS int) = 8 """); } @@ -69,7 +69,7 @@ [Name] nvarchar(max) '$.Name', [String] nvarchar(max) '$.String' ) AS [r0] ORDER BY [r0].[Id] - OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 8 """); } @@ -83,7 +83,7 @@ public override async Task Index_parameter(bool async) SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST(@i AS nvarchar(max)) + '].Int') AS int) = 21 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST(@i AS nvarchar(max)) + '].Int') AS int) = 8 """); } @@ -95,7 +95,7 @@ public override async Task Index_column(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST([r].[Id] - 1 AS nvarchar(max)) + '].Int') AS int) = 21 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST([r].[Id] - 1 AS nvarchar(max)) + '].Int') AS int) = 8 """); } @@ -107,7 +107,7 @@ public override async Task Index_out_of_bounds(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[9999].Int') AS int) = 50 +WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[9999].Int') AS int) = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonMiscellaneousSqlServerTest.cs index 29b9b51349b..18e22b47897 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonMiscellaneousSqlServerTest.cs @@ -30,7 +30,7 @@ public override async Task Where_optional_related_property(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[OptionalRelated], '$.Int') AS int) = 9 +WHERE CAST(JSON_VALUE([r].[OptionalRelated], '$.Int') AS int) = 8 """); } @@ -42,7 +42,7 @@ public override async Task Where_nested_related_property(bool async) """ SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] FROM [RootEntity] AS [r] -WHERE CAST(JSON_VALUE([r].[RequiredRelated], '$.RequiredNested.Int') AS int) = 50 +WHERE CAST(JSON_VALUE([r].[RequiredRelated], '$.RequiredNested.Int') AS int) = 8 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs index 5d40d7aa531..d6168ab3090 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqlServerTest.cs @@ -19,9 +19,9 @@ FROM [RootEntity] AS [r] #region Simple properties - public override async Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_related_property(async, queryTrackingBehavior); + await base.Select_property_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -30,9 +30,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property(async, queryTrackingBehavior); + await base.Select_property_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -41,9 +41,20 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property_value_type(async, queryTrackingBehavior); + await base.Select_value_type_property_on_null_related_throws(async, queryTrackingBehavior); + + AssertSql( + """ +SELECT CAST(JSON_VALUE([r].[OptionalRelated], '$.Int') AS int) +FROM [RootEntity] AS [r] +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(async, queryTrackingBehavior); AssertSql( """ @@ -84,9 +95,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -98,9 +109,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -112,9 +123,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -126,9 +137,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -159,9 +170,9 @@ ORDER BY [r].[Id] } } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -169,13 +180,14 @@ public override async Task Select_required_related_nested_collection(bool async, """ SELECT JSON_QUERY([r].[RequiredRelated], '$.NestedCollection'), [r].[Id] FROM [RootEntity] AS [r] +ORDER BY [r].[Id] """); } } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -183,6 +195,7 @@ public override async Task Select_optional_related_nested_collection(bool async, """ SELECT JSON_QUERY([r].[OptionalRelated], '$.NestedCollection'), [r].[Id] FROM [RootEntity] AS [r] +ORDER BY [r].[Id] """); } } @@ -210,9 +223,9 @@ [RequiredNested] nvarchar(max) '$.RequiredNested' AS JSON } } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -230,9 +243,9 @@ [String] nvarchar(max) '$.String' } } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs new file mode 100644 index 00000000000..ee5f03dfdc1 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqlServerTest.cs @@ -0,0 +1,130 @@ +// 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.Relationships.OwnedJson; + +public class OwnedJsonStructuralEqualitySqlServerTest( + OwnedJsonSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : OwnedJsonStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE 0 = 1 +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE 0 = 1 +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE 0 = 1 +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE [r].[OptionalRelated] IS NULL +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE 0 = 1 +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE JSON_QUERY([r].[RequiredRelated], '$.OptionalNested') IS NULL +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( +); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated] +FROM [RootEntity] AS [r] +WHERE 0 = 1 +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( +); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionSqlServerTest.cs index 945dfaa9abf..eaea700c1b2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsCollectionSqlServerTest.cs @@ -63,7 +63,7 @@ FROM [RelatedCollection] AS [r4] WHERE ( SELECT COUNT(*) FROM [RelatedCollection] AS [r0] - WHERE [r].[Id] = [r0].[RootEntityId] AND [r0].[Int] <> 50) = 2 + WHERE [r].[Id] = [r0].[RootEntityId] AND [r0].[Int] <> 8) = 2 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r8].[RelatedTypeRootEntityId] """); } @@ -96,7 +96,7 @@ SELECT [r0].[Int] FROM [RelatedCollection] AS [r0] WHERE [r].[Id] = [r0].[RootEntityId] ORDER BY [r0].[Id] - OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 8 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r8].[RelatedTypeRootEntityId] """); } @@ -129,7 +129,7 @@ SELECT [r0].[Int] FROM [RelatedCollection] AS [r0] WHERE [r].[Id] = [r0].[RootEntityId] ORDER BY (SELECT 1) - OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) = 8 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r8].[RelatedTypeRootEntityId] """); } @@ -164,7 +164,7 @@ SELECT [r0].[Int] FROM [RelatedCollection] AS [r0] WHERE [r].[Id] = [r0].[RootEntityId] ORDER BY (SELECT 1) - OFFSET @i ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET @i ROWS FETCH NEXT 1 ROWS ONLY) = 8 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r8].[RelatedTypeRootEntityId] """); } @@ -197,7 +197,7 @@ SELECT [r0].[Int] FROM [RelatedCollection] AS [r0] WHERE [r].[Id] = [r0].[RootEntityId] ORDER BY (SELECT 1) - OFFSET [r].[Id] - 1 ROWS FETCH NEXT 1 ROWS ONLY) = 21 + OFFSET [r].[Id] - 1 ROWS FETCH NEXT 1 ROWS ONLY) = 8 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r8].[RelatedTypeRootEntityId] """); } @@ -230,7 +230,7 @@ SELECT [r0].[Int] FROM [RelatedCollection] AS [r0] WHERE [r].[Id] = [r0].[RootEntityId] ORDER BY (SELECT 1) - OFFSET 9999 ROWS FETCH NEXT 1 ROWS ONLY) = 50 + OFFSET 9999 ROWS FETCH NEXT 1 ROWS ONLY) = 8 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RootEntityId], [r2].[RelatedTypeRootEntityId], [r3].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r8].[RelatedTypeRootEntityId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousSqlServerTest.cs index d77c780ad18..4b2a53dd335 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsMiscellaneousSqlServerTest.cs @@ -63,7 +63,7 @@ FROM [RelatedCollection] AS [r3] LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] ) AS [s] ON [r].[Id] = [s].[RootEntityId] LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] -WHERE [o].[Int] = 9 +WHERE [o].[Int] = 8 ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] """); } @@ -91,7 +91,7 @@ FROM [RelatedCollection] AS [r3] LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] ) AS [s] ON [r].[Id] = [s].[RootEntityId] LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] -WHERE [r1].[Int] = 50 +WHERE [r1].[Int] = 8 ORDER BY [r].[Id], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs index b7116ff2604..4030d755f1b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsProjectionSqlServerTest.cs @@ -35,9 +35,9 @@ FROM [RelatedCollection] AS [r3] #region Simple properties - public override async Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_related_property(async, queryTrackingBehavior); + await base.Select_property_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -47,9 +47,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property(async, queryTrackingBehavior); + await base.Select_property_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -59,9 +59,21 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property_value_type(async, queryTrackingBehavior); + await base.Select_value_type_property_on_null_related_throws(async, queryTrackingBehavior); + + AssertSql( + """ +SELECT [o].[Int] +FROM [RootEntity] AS [r] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(async, queryTrackingBehavior); AssertSql( """ @@ -113,9 +125,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -129,9 +141,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -145,9 +157,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -161,9 +173,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -203,9 +215,9 @@ FROM [RelatedCollection] AS [r0] } } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -220,9 +232,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -256,9 +268,9 @@ FROM [RootEntity] AS [r] } } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -272,9 +284,9 @@ FROM [RootEntity] AS [r] } } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs new file mode 100644 index 00000000000..dca4c85fe6c --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqlServerTest.cs @@ -0,0 +1,242 @@ +// 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.Relationships.OwnedNavigations; + +public class OwnedNavigationsStructuralEqualitySqlServerTest( + OwnedNavigationsSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : OwnedNavigationsStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [r0].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [r0].[RootEntityId], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [r0].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [r0].[RootEntityId], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE [o].[RootEntityId] IS NULL +ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE [o].[RootEntityId] IS NULL +ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE [r1].[RelatedTypeRootEntityId] IS NULL +ORDER BY [r].[Id], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( +); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [o].[RootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[String], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [o2].[Int], [o2].[Name], [o2].[String], [o0].[Id], [o0].[Int], [o0].[Name], [o0].[String], [o1].[Id], [o1].[Int], [o1].[Name], [o1].[String], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[Id1], [s].[Int1], [s].[Name1], [s].[String1], [s].[Id2], [s].[Int2], [s].[Name2], [s].[String2], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r7].[RelatedTypeRootEntityId], [r7].[Id], [r7].[Int], [r7].[Name], [r7].[String], [r1].[Id], [r1].[Int], [r1].[Name], [r1].[String], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[String] +FROM [RootEntity] AS [r] +LEFT JOIN [OptionalRelated] AS [o] ON [r].[Id] = [o].[RootEntityId] +LEFT JOIN [OptionalRelated_OptionalNested] AS [o0] ON [o].[RootEntityId] = [o0].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_RequiredNested] AS [o1] ON [o].[RootEntityId] = [o1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated] AS [r0] ON [r].[Id] = [r0].[RootEntityId] +LEFT JOIN [RequiredRelated_OptionalNested] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] +LEFT JOIN [RequiredRelated_RequiredNested] AS [r2] ON [r0].[RootEntityId] = [r2].[RelatedTypeRootEntityId] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o2] ON [o].[RootEntityId] = [o2].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r3].[RootEntityId], [r3].[Id], [r3].[Int], [r3].[Name], [r3].[String], [r4].[RelatedTypeRootEntityId], [r4].[RelatedTypeId], [r5].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId0], [r5].[RelatedTypeId] AS [RelatedTypeId0], [r6].[RelatedTypeRootEntityId] AS [RelatedTypeRootEntityId1], [r6].[RelatedTypeId] AS [RelatedTypeId1], [r6].[Id] AS [Id0], [r6].[Int] AS [Int0], [r6].[Name] AS [Name0], [r6].[String] AS [String0], [r4].[Id] AS [Id1], [r4].[Int] AS [Int1], [r4].[Name] AS [Name1], [r4].[String] AS [String1], [r5].[Id] AS [Id2], [r5].[Int] AS [Int2], [r5].[Name] AS [Name2], [r5].[String] AS [String2] + FROM [RelatedCollection] AS [r3] + LEFT JOIN [RelatedCollection_OptionalNested] AS [r4] ON [r3].[RootEntityId] = [r4].[RelatedTypeRootEntityId] AND [r3].[Id] = [r4].[RelatedTypeId] + LEFT JOIN [RelatedCollection_RequiredNested] AS [r5] ON [r3].[RootEntityId] = [r5].[RelatedTypeRootEntityId] AND [r3].[Id] = [r5].[RelatedTypeId] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r6] ON [r3].[RootEntityId] = [r6].[RelatedTypeRootEntityId] AND [r3].[Id] = [r6].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r7] ON [r0].[RootEntityId] = [r7].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [o].[RootEntityId], [o0].[RelatedTypeRootEntityId], [o1].[RelatedTypeRootEntityId], [r0].[RootEntityId], [r1].[RelatedTypeRootEntityId], [r2].[RelatedTypeRootEntityId], [o2].[RelatedTypeRootEntityId], [o2].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[RelatedTypeRootEntityId0], [s].[RelatedTypeId0], [s].[RelatedTypeRootEntityId1], [s].[RelatedTypeId1], [s].[Id0], [r7].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( +); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingMiscellaneousSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingMiscellaneousSqlServerTest.cs index 108828ddde3..bf07d54db61 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingMiscellaneousSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingMiscellaneousSqlServerTest.cs @@ -51,7 +51,7 @@ FROM [RelatedCollection] AS [r0] LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] ) AS [s] ON [r].[Id] = [s].[RootEntityId] LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] -WHERE [r].[OptionalRelated_Int] = 9 +WHERE [r].[OptionalRelated_Int] = 8 ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] """); } @@ -73,7 +73,7 @@ FROM [RelatedCollection] AS [r0] LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] ) AS [s] ON [r].[Id] = [s].[RootEntityId] LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] -WHERE [r].[RequiredRelated_RequiredNested_Int] = 50 +WHERE [r].[RequiredRelated_RequiredNested_Int] = 8 ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs index 466c6bd6a4b..466f0c7df3f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingProjectionSqlServerTest.cs @@ -29,9 +29,9 @@ FROM [RelatedCollection] AS [r0] #region Simple properties - public override async Task Select_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_related_property(async, queryTrackingBehavior); + await base.Select_property_on_required_related(async, queryTrackingBehavior); AssertSql( """ @@ -40,9 +40,9 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_property_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property(async, queryTrackingBehavior); + await base.Select_property_on_optional_related(async, queryTrackingBehavior); AssertSql( """ @@ -51,9 +51,20 @@ FROM [RootEntity] AS [r] """); } - public override async Task Select_optional_related_property_value_type(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_value_type_property_on_null_related_throws(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_property_value_type(async, queryTrackingBehavior); + await base.Select_value_type_property_on_null_related_throws(async, queryTrackingBehavior); + + AssertSql( + """ +SELECT [r].[OptionalRelated_Int] +FROM [RootEntity] AS [r] +"""); + } + + public override async Task Select_nullable_value_type_property_on_null_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + await base.Select_nullable_value_type_property_on_null_related(async, queryTrackingBehavior); AssertSql( """ @@ -100,9 +111,9 @@ WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT N } } - public override async Task Select_required_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -114,9 +125,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_required_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -128,9 +139,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_required_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_required_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_required_nested(async, queryTrackingBehavior); + await base.Select_required_nested_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -142,9 +153,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_optional_nested(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_optional_nested_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_optional_nested(async, queryTrackingBehavior); + await base.Select_optional_nested_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -180,9 +191,9 @@ FROM [RelatedCollection] AS [r0] } } - public override async Task Select_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_required_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -196,9 +207,9 @@ FROM [RootEntity] AS [r] } } - public override async Task Select_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task Select_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.Select_optional_related_nested_collection(async, queryTrackingBehavior); + await base.Select_nested_collection_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -231,9 +242,9 @@ FROM [RootEntity] AS [r] } } - public override async Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { @@ -246,9 +257,9 @@ FROM [RootEntity] AS [r] } } - public override async Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override async Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) { - await base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior); + await base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior); if (queryTrackingBehavior is not QueryTrackingBehavior.TrackAll) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs new file mode 100644 index 00000000000..6ab6772d03b --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqlServerTest.cs @@ -0,0 +1,202 @@ +// 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.Relationships.OwnedTableSplitting; + +public class OwnedTableSplittingStructuralEqualitySqlServerTest( + OwnedTableSplittingSqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : OwnedTableSplittingStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE [r].[OptionalRelated_Id] IS NULL OR [r].[OptionalRelated_Int] IS NULL OR [r].[OptionalRelated_Name] IS NULL OR [r].[OptionalRelated_String] IS NULL +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END IS NULL +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE [r].[RequiredRelated_OptionalNested_Id] IS NULL OR [r].[RequiredRelated_OptionalNested_Int] IS NULL OR [r].[RequiredRelated_OptionalNested_Name] IS NULL OR [r].[RequiredRelated_OptionalNested_String] IS NULL +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( +); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT [r].[Id], [r].[Name], [r].[OptionalRelated_Id], [r].[OptionalRelated_Int], [r].[OptionalRelated_Name], [r].[OptionalRelated_String], [o].[RelatedTypeRootEntityId], [o].[Id], [o].[Int], [o].[Name], [o].[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], [s].[RootEntityId], [s].[Id], [s].[Int], [s].[Name], [s].[String], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [s].[Int0], [s].[Name0], [s].[String0], [s].[OptionalNested_Id], [s].[OptionalNested_Int], [s].[OptionalNested_Name], [s].[OptionalNested_String], [s].[RequiredNested_Id], [s].[RequiredNested_Int], [s].[RequiredNested_Name], [s].[RequiredNested_String], [r].[RequiredRelated_Id], [r].[RequiredRelated_Int], [r].[RequiredRelated_Name], [r].[RequiredRelated_String], [r2].[RelatedTypeRootEntityId], [r2].[Id], [r2].[Int], [r2].[Name], [r2].[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] +LEFT JOIN [OptionalRelated_NestedCollection] AS [o] ON CASE + WHEN [r].[OptionalRelated_Id] IS NOT NULL AND [r].[OptionalRelated_Int] IS NOT NULL AND [r].[OptionalRelated_Name] IS NOT NULL AND [r].[OptionalRelated_String] IS NOT NULL THEN [r].[Id] +END = [o].[RelatedTypeRootEntityId] +LEFT JOIN ( + SELECT [r0].[RootEntityId], [r0].[Id], [r0].[Int], [r0].[Name], [r0].[String], [r1].[RelatedTypeRootEntityId], [r1].[RelatedTypeId], [r1].[Id] AS [Id0], [r1].[Int] AS [Int0], [r1].[Name] AS [Name0], [r1].[String] AS [String0], [r0].[OptionalNested_Id], [r0].[OptionalNested_Int], [r0].[OptionalNested_Name], [r0].[OptionalNested_String], [r0].[RequiredNested_Id], [r0].[RequiredNested_Int], [r0].[RequiredNested_Name], [r0].[RequiredNested_String] + FROM [RelatedCollection] AS [r0] + LEFT JOIN [RelatedCollection_NestedCollection] AS [r1] ON [r0].[RootEntityId] = [r1].[RelatedTypeRootEntityId] AND [r0].[Id] = [r1].[RelatedTypeId] +) AS [s] ON [r].[Id] = [s].[RootEntityId] +LEFT JOIN [RequiredRelated_NestedCollection] AS [r2] ON [r].[Id] = [r2].[RelatedTypeRootEntityId] +WHERE 0 = 1 +ORDER BY [r].[Id], [o].[RelatedTypeRootEntityId], [o].[Id], [s].[RootEntityId], [s].[Id], [s].[RelatedTypeRootEntityId], [s].[RelatedTypeId], [s].[Id0], [r2].[RelatedTypeRootEntityId] +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( +); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqliteTest.cs index 59c16b94cbb..ef86a835bfd 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonProjectionSqliteTest.cs @@ -11,11 +11,11 @@ public class ComplexJsonProjectionSqliteTest(ComplexJsonSqliteFixture fixture, I public override Task SelectMany_related_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertApplyNotSupported(() => base.SelectMany_related_collection(async, queryTrackingBehavior)); - public override Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertApplyNotSupported(() => base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior)); + public override Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertApplyNotSupported(() => base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior)); - public override Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) - => AssertApplyNotSupported(() => base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior)); + public override Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) + => AssertApplyNotSupported(() => base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior)); public override Task Select_subquery_required_related_FirstOrDefault(bool async, QueryTrackingBehavior queryTrackingBehavior) => AssertApplyNotSupported(() => base.Select_subquery_required_related_FirstOrDefault(async, queryTrackingBehavior)); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqliteFixture.cs index da9b8bcac5d..1f7704ff4a9 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonSqliteFixture.cs @@ -7,99 +7,4 @@ public class ComplexJsonSqliteFixture : ComplexJsonRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; - - protected override async Task SeedAsync(PoolableDbContext context) - { - // TODO: Temporary, until we have update pipeline support for complex JSON - await context.Database.ExecuteSqlAsync($$$""" -INSERT INTO RootEntity (Id, Name, RequiredRelated, OptionalRelated, RelatedCollection) VALUES -( - 1, - 'Root1', - -- RequiredRelated: - '{ - "Id": 100, - "Name": "Root1_RequiredRelated", - "Int": 8, - "String": "foo", - "RequiredNested": { "Id": 1000, "Name": "Root1_RequiredRelated_RequiredNested", "Int": 50, "String": "foo_foo" }, - "OptionalNested": { "Id": 1001, "Name": "Root1_RequiredRelated_OptionalNested", "Int": 51, "String": "foo_bar" }, - "NestedCollection": - [ - { "Id": 1002, "Name": "Root1_RequiredRelated_NestedCollection_1", "Int": 52, "String": "foo_baz1" }, - { "Id": 1003, "Name": "Root1_RequiredRelated_NestedCollection_2", "Int": 53, "String": "foo_baz2" } - ] - }', - -- OptionalRelated: - '{ - "Id": 101, - "Name": "Root1_OptionalRelated", - "Int": 9, - "String": "bar", - "RequiredNested": { "Id": 1010, "Name": "Root1_OptionalRelated_RequiredNested", "Int": 52, "String": "bar_foo" }, - "OptionalNested": { "Id": 1011, "Name": "Root1_OptionalRelated_OptionalNested", "Int": 53, "String": "bar_bar" }, - "NestedCollection": - [ - { "Id": 1012, "Name": "Root1_OptionalRelated_NestedCollection_1", "Int": 54, "String": "bar_baz1" }, - { "Id": 1013, "Name": "Root1_OptionalRelated_NestedCollection_2", "Int": 55, "String": "bar_baz2" } - ] - }', - -- RelatedCollection: - '[ - { - "Id": 102, - "Name": "Root1_RelatedCollection_1", - "Int": 21, - "String": "foo", - "RequiredNested": { "Id": 1020, "Name": "Root1_RelatedCollection_1_RequiredNested", "Int": 50, "String": "foo_foo" }, - "OptionalNested": { "Id": 1021, "Name": "Root1_RelatedCollection_1_OptionalNested", "Int": 51, "String": "foo_bar" }, - "NestedCollection": - [ - { "Id": 1022, "Name": "Root1_RelatedCollection_1_NestedCollection_1", "Int": 53, "String": "foo_bar" }, - { "Id": 1023, "Name": "Root1_RelatedCollection_1_NestedCollection_2", "Int": 51, "String": "foo_bar" } - ] - }, - { - "Id": 103, - "Name": "Root1_RelatedCollection_2", - "Int": 22, - "String": "foo", - "RequiredNested": { "Id": 1030, "Name": "Root1_RelatedCollection_2_RequiredNested", "Int": 50, "String": "foo_foo" }, - "OptionalNested": { "Id": 1031, "Name": "Root1_RelatedCollection_2_OptionalNested", "Int": 51, "String": "foo_bar" }, - "NestedCollection": - [ - { "Id": 1032, "Name": "Root1_RelatedCollection_2_NestedCollection_1", "Int": 53, "String": "foo_bar" }, - { "Id": 1033, "Name": "Root1_RelatedCollection_2_NestedCollection_2", "Int": 51, "String": "foo_bar" } - ] - } - ]' -), -( - 2, - 'Root2', - -- RequiredRelated: - '{ - "Id": 200, - "Name": "Root2_RequiredRelated", - "Int": 10, - "String": "aaa", - "RequiredNested": { "Id": 2000, "Name": "Root2_RequiredRelated_RequiredNested", "Int": 54, "String": "aaa_xxx" }, - "OptionalNested": { "Id": 2001, "Name": "Root2_RequiredRelated_OptionalNested", "Int": 55, "String": "aaa_yyy" }, - "NestedCollection": [] - }', - -- OptionalRelated: - '{ - "Id": 201, - "Name": "Root2_OptionalRelated", - "Int": 11, - "String": "bbb", - "RequiredNested": { "Id": 2010, "Name": "Root2_OptionalRelated_RequiredNested", "Int": 56, "String": "bbb_xxx" }, - "OptionalNested": { "Id": 2011, "Name": "Root2_OptionalRelated_OptionalNested", "Int": 57, "String": "bbb_yyy" }, - "NestedCollection": [] - }', - -- RelatedCollection: - '[]' -) -"""); - } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs new file mode 100644 index 00000000000..3af30bcc3a4 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexJson/ComplexJsonStructuralEqualitySqliteTest.cs @@ -0,0 +1,148 @@ +// 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.Relationships.ComplexJson; + +public class ComplexJsonStructuralEqualitySqliteTest(ComplexJsonSqliteFixture fixture, ITestOutputHelper testOutputHelper) + : ComplexJsonStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" = "r"."OptionalRelated" +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'RequiredNested' = "r"."OptionalRelated" ->> 'RequiredNested' +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" <> "r"."OptionalRelated" OR "r"."OptionalRelated" IS NULL +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."OptionalRelated" IS NULL +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."OptionalRelated" IS NULL +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'OptionalNested' IS NULL +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'RequiredNested' = '{"Id":1000,"Int":8,"Name":"Root1_RequiredRelated_RequiredNested","String":"foo"}' +"""); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( + """ +@entity_equality_nested='?' (Size = 80) + +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'RequiredNested' = @entity_equality_nested +"""); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'NestedCollection' = "r"."OptionalRelated" ->> 'NestedCollection' +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'NestedCollection' = '[{"Id":1002,"Int":8,"Name":"Root1_RequiredRelated_NestedCollection_1","String":"foo"},{"Id":1003,"Int":8,"Name":"Root1_RequiredRelated_NestedCollection_2","String":"foo"}]' +"""); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( + """ +@entity_equality_nestedCollection='?' (Size = 171) + +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'NestedCollection' = @entity_equality_nestedCollection +"""); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs new file mode 100644 index 00000000000..fd1b5c357e5 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/ComplexTableSplitting/ComplexTableSplittingStructuralEqualitySqliteTest.cs @@ -0,0 +1,106 @@ +// 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.Relationships.ComplexTableSplitting; + +public class ComplexTableSplittingStructuralEqualitySqliteTest( + ComplexTableSplittingSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : ComplexTableSplittingStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql(); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql(); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql(); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql(); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql(); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql(); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + 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" +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' +"""); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( + """ +@entity_equality_nested_Id='?' (DbType = Int32) +@entity_equality_nested_Int='?' (DbType = Int32) +@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" +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 +"""); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql(); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql(); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql(); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqliteTest.cs new file mode 100644 index 00000000000..9f25b195ce4 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/Navigations/NavigationsStructuralEqualitySqliteTest.cs @@ -0,0 +1,156 @@ +// 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.Relationships.Navigations; + +public class NavigationsStructuralEqualitySqliteTest( + NavigationsSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : NavigationsStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +LEFT JOIN "RelatedType" AS "r1" ON "r"."OptionalRelatedId" = "r1"."Id" +WHERE "r0"."Id" = "r1"."Id" +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +INNER JOIN "NestedType" AS "n" ON "r0"."RequiredNestedId" = "n"."Id" +LEFT JOIN "RelatedType" AS "r1" ON "r"."OptionalRelatedId" = "r1"."Id" +LEFT JOIN "NestedType" AS "n0" ON "r1"."RequiredNestedId" = "n0"."Id" +WHERE "n"."Id" = "n0"."Id" +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +LEFT JOIN "RelatedType" AS "r1" ON "r"."OptionalRelatedId" = "r1"."Id" +WHERE "r0"."Id" <> "r1"."Id" OR "r1"."Id" IS NULL +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +LEFT JOIN "RelatedType" AS "r0" ON "r"."OptionalRelatedId" = "r0"."Id" +WHERE "r0"."Id" IS NULL +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +LEFT JOIN "RelatedType" AS "r0" ON "r"."OptionalRelatedId" = "r0"."Id" +WHERE "r0"."Id" IS NULL +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +LEFT JOIN "NestedType" AS "n" ON "r0"."OptionalNestedId" = "n"."Id" +WHERE "n"."Id" IS NULL +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +INNER JOIN "NestedType" AS "n" ON "r0"."RequiredNestedId" = "n"."Id" +WHERE "n"."Id" = 1000 +"""); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( + """ +@entity_equality_nested_Id='1000' (Nullable = true) + +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +INNER JOIN "NestedType" AS "n" ON "r0"."RequiredNestedId" = "n"."Id" +WHERE "n"."Id" = @entity_equality_nested_Id +"""); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelatedId", "r"."RequiredRelatedId" +FROM "RootEntity" AS "r" +INNER JOIN "RelatedType" AS "r0" ON "r"."RequiredRelatedId" = "r0"."Id" +LEFT JOIN "RelatedType" AS "r1" ON "r"."OptionalRelatedId" = "r1"."Id" +WHERE "r0"."Id" = "r1"."Id" +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql(); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql(); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqliteTest.cs index 051cf960a40..0f430bf1f08 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonProjectionSqliteTest.cs @@ -13,15 +13,15 @@ public override Task SelectMany_related_collection(bool async, QueryTrackingBeha ? Task.CompletedTask // Base test expects "can't track owned entities" exception, but with SQLite we get "no CROSS APPLY" : AssertApplyNotSupported(() => base.SelectMany_related_collection(async, queryTrackingBehavior)); - public override Task SelectMany_required_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override Task SelectMany_nested_collection_on_required_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => queryTrackingBehavior is QueryTrackingBehavior.TrackAll ? Task.CompletedTask // Base test expects "can't track owned entities" exception, but with SQLite we get "no CROSS APPLY" - : AssertApplyNotSupported(() => base.SelectMany_required_related_nested_collection(async, queryTrackingBehavior)); + : AssertApplyNotSupported(() => base.SelectMany_nested_collection_on_required_related(async, queryTrackingBehavior)); - public override Task SelectMany_optional_related_nested_collection(bool async, QueryTrackingBehavior queryTrackingBehavior) + public override Task SelectMany_nested_collection_on_optional_related(bool async, QueryTrackingBehavior queryTrackingBehavior) => queryTrackingBehavior is QueryTrackingBehavior.TrackAll ? Task.CompletedTask // Base test expects "can't track owned entities" exception, but with SQLite we get "no CROSS APPLY" - : AssertApplyNotSupported(() => base.SelectMany_optional_related_nested_collection(async, queryTrackingBehavior)); + : AssertApplyNotSupported(() => base.SelectMany_nested_collection_on_optional_related(async, queryTrackingBehavior)); public override Task Select_subquery_required_related_FirstOrDefault(bool async, QueryTrackingBehavior queryTrackingBehavior) => queryTrackingBehavior is QueryTrackingBehavior.TrackAll diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs new file mode 100644 index 00000000000..fd4f7ee01ff --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedJson/OwnedJsonStructuralEqualitySqliteTest.cs @@ -0,0 +1,130 @@ +// 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.Relationships.OwnedJson; + +public class OwnedJsonStructuralEqualitySqliteTest( + OwnedJsonSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : OwnedJsonStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE 0 +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE 0 +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE 0 +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."OptionalRelated" IS NULL +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE 0 +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE "r"."RequiredRelated" ->> 'OptionalNested' IS NULL +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( +); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated", "r"."RelatedCollection", "r"."RequiredRelated" +FROM "RootEntity" AS "r" +WHERE 0 +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( +); + } + + [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 new file mode 100644 index 00000000000..a3d51b8099f --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedNavigations/OwnedNavigationsStructuralEqualitySqliteTest.cs @@ -0,0 +1,242 @@ +// 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.Relationships.OwnedNavigations; + +public class OwnedNavigationsStructuralEqualitySqliteTest( + OwnedNavigationsSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : OwnedNavigationsStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "r0"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "r0"."RootEntityId", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "r0"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "r0"."RootEntityId", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE "o"."RootEntityId" IS NULL +ORDER BY "r"."Id", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE "o"."RootEntityId" IS NULL +ORDER BY "r"."Id", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE "r1"."RelatedTypeRootEntityId" IS NULL +ORDER BY "r"."Id", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( +); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "o"."RootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."String", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "o2"."Int", "o2"."Name", "o2"."String", "o0"."Id", "o0"."Int", "o0"."Name", "o0"."String", "o1"."Id", "o1"."Int", "o1"."Name", "o1"."String", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."Id1", "s"."Int1", "s"."Name1", "s"."String1", "s"."Id2", "s"."Int2", "s"."Name2", "s"."String2", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r7"."RelatedTypeRootEntityId", "r7"."Id", "r7"."Int", "r7"."Name", "r7"."String", "r1"."Id", "r1"."Int", "r1"."Name", "r1"."String", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."String" +FROM "RootEntity" AS "r" +LEFT JOIN "OptionalRelated" AS "o" ON "r"."Id" = "o"."RootEntityId" +LEFT JOIN "OptionalRelated_OptionalNested" AS "o0" ON "o"."RootEntityId" = "o0"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_RequiredNested" AS "o1" ON "o"."RootEntityId" = "o1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated" AS "r0" ON "r"."Id" = "r0"."RootEntityId" +LEFT JOIN "RequiredRelated_OptionalNested" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" +LEFT JOIN "RequiredRelated_RequiredNested" AS "r2" ON "r0"."RootEntityId" = "r2"."RelatedTypeRootEntityId" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o2" ON "o"."RootEntityId" = "o2"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r3"."RootEntityId", "r3"."Id", "r3"."Int", "r3"."Name", "r3"."String", "r4"."RelatedTypeRootEntityId", "r4"."RelatedTypeId", "r5"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId0", "r5"."RelatedTypeId" AS "RelatedTypeId0", "r6"."RelatedTypeRootEntityId" AS "RelatedTypeRootEntityId1", "r6"."RelatedTypeId" AS "RelatedTypeId1", "r6"."Id" AS "Id0", "r6"."Int" AS "Int0", "r6"."Name" AS "Name0", "r6"."String" AS "String0", "r4"."Id" AS "Id1", "r4"."Int" AS "Int1", "r4"."Name" AS "Name1", "r4"."String" AS "String1", "r5"."Id" AS "Id2", "r5"."Int" AS "Int2", "r5"."Name" AS "Name2", "r5"."String" AS "String2" + FROM "RelatedCollection" AS "r3" + LEFT JOIN "RelatedCollection_OptionalNested" AS "r4" ON "r3"."RootEntityId" = "r4"."RelatedTypeRootEntityId" AND "r3"."Id" = "r4"."RelatedTypeId" + LEFT JOIN "RelatedCollection_RequiredNested" AS "r5" ON "r3"."RootEntityId" = "r5"."RelatedTypeRootEntityId" AND "r3"."Id" = "r5"."RelatedTypeId" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r6" ON "r3"."RootEntityId" = "r6"."RelatedTypeRootEntityId" AND "r3"."Id" = "r6"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r7" ON "r0"."RootEntityId" = "r7"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "o"."RootEntityId", "o0"."RelatedTypeRootEntityId", "o1"."RelatedTypeRootEntityId", "r0"."RootEntityId", "r1"."RelatedTypeRootEntityId", "r2"."RelatedTypeRootEntityId", "o2"."RelatedTypeRootEntityId", "o2"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."RelatedTypeRootEntityId0", "s"."RelatedTypeId0", "s"."RelatedTypeRootEntityId1", "s"."RelatedTypeId1", "s"."Id0", "r7"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( +); + } + + [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 new file mode 100644 index 00000000000..0675d3a3097 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Relationships/OwnedTableSplitting/OwnedTableSplittingStructuralEqualitySqliteTest.cs @@ -0,0 +1,202 @@ +// 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.Relationships.OwnedTableSplitting; + +public class OwnedTableSplittingStructuralEqualitySqliteTest( + OwnedTableSplittingSqliteFixture fixture, + ITestOutputHelper testOutputHelper) + : OwnedTableSplittingStructuralEqualityRelationalTestBase(fixture, testOutputHelper) +{ + public override async Task Two_related(bool async) + { + await base.Two_related(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Two_nested(bool async) + { + await base.Two_nested(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Not_equals(bool async) + { + await base.Not_equals(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Related_with_inline_null(bool async) + { + await base.Related_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE "r"."OptionalRelated_Id" IS NULL OR "r"."OptionalRelated_Int" IS NULL OR "r"."OptionalRelated_Name" IS NULL OR "r"."OptionalRelated_String" IS NULL +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Related_with_parameter_null(bool async) + { + await base.Related_with_parameter_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END IS NULL +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Nested_with_inline_null(bool async) + { + await base.Nested_with_inline_null(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE "r"."RequiredRelated_OptionalNested_Id" IS NULL OR "r"."RequiredRelated_OptionalNested_Int" IS NULL OR "r"."RequiredRelated_OptionalNested_Name" IS NULL OR "r"."RequiredRelated_OptionalNested_String" IS NULL +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Nested_with_inline(bool async) + { + await base.Nested_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_with_parameter(bool async) + { + await base.Nested_with_parameter(async); + + AssertSql( +); + } + + public override async Task Two_nested_collections(bool async) + { + await base.Two_nested_collections(async); + + AssertSql( + """ +SELECT "r"."Id", "r"."Name", "r"."OptionalRelated_Id", "r"."OptionalRelated_Int", "r"."OptionalRelated_Name", "r"."OptionalRelated_String", "o"."RelatedTypeRootEntityId", "o"."Id", "o"."Int", "o"."Name", "o"."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", "s"."RootEntityId", "s"."Id", "s"."Int", "s"."Name", "s"."String", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "s"."Int0", "s"."Name0", "s"."String0", "s"."OptionalNested_Id", "s"."OptionalNested_Int", "s"."OptionalNested_Name", "s"."OptionalNested_String", "s"."RequiredNested_Id", "s"."RequiredNested_Int", "s"."RequiredNested_Name", "s"."RequiredNested_String", "r"."RequiredRelated_Id", "r"."RequiredRelated_Int", "r"."RequiredRelated_Name", "r"."RequiredRelated_String", "r2"."RelatedTypeRootEntityId", "r2"."Id", "r2"."Int", "r2"."Name", "r2"."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" +LEFT JOIN "OptionalRelated_NestedCollection" AS "o" ON CASE + WHEN "r"."OptionalRelated_Id" IS NOT NULL AND "r"."OptionalRelated_Int" IS NOT NULL AND "r"."OptionalRelated_Name" IS NOT NULL AND "r"."OptionalRelated_String" IS NOT NULL THEN "r"."Id" +END = "o"."RelatedTypeRootEntityId" +LEFT JOIN ( + SELECT "r0"."RootEntityId", "r0"."Id", "r0"."Int", "r0"."Name", "r0"."String", "r1"."RelatedTypeRootEntityId", "r1"."RelatedTypeId", "r1"."Id" AS "Id0", "r1"."Int" AS "Int0", "r1"."Name" AS "Name0", "r1"."String" AS "String0", "r0"."OptionalNested_Id", "r0"."OptionalNested_Int", "r0"."OptionalNested_Name", "r0"."OptionalNested_String", "r0"."RequiredNested_Id", "r0"."RequiredNested_Int", "r0"."RequiredNested_Name", "r0"."RequiredNested_String" + FROM "RelatedCollection" AS "r0" + LEFT JOIN "RelatedCollection_NestedCollection" AS "r1" ON "r0"."RootEntityId" = "r1"."RelatedTypeRootEntityId" AND "r0"."Id" = "r1"."RelatedTypeId" +) AS "s" ON "r"."Id" = "s"."RootEntityId" +LEFT JOIN "RequiredRelated_NestedCollection" AS "r2" ON "r"."Id" = "r2"."RelatedTypeRootEntityId" +WHERE 0 +ORDER BY "r"."Id", "o"."RelatedTypeRootEntityId", "o"."Id", "s"."RootEntityId", "s"."Id", "s"."RelatedTypeRootEntityId", "s"."RelatedTypeId", "s"."Id0", "r2"."RelatedTypeRootEntityId" +"""); + } + + public override async Task Nested_collection_with_inline(bool async) + { + await base.Nested_collection_with_inline(async); + + AssertSql( +); + } + + public override async Task Nested_collection_with_parameter(bool async) + { + await base.Nested_collection_with_parameter(async); + + AssertSql( +); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); +}