diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index b2d81d357d7..142fb671c76 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -57,7 +57,7 @@ public static EntityTypeBuilder ToTable( { Check.NotNull(buildAction, nameof(buildAction)); - buildAction(new TableBuilder(null, null, entityTypeBuilder.Metadata)); + buildAction(new TableBuilder(null, null, entityTypeBuilder)); return entityTypeBuilder; } diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 12201c32968..e78f66c23e6 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -79,6 +79,7 @@ public static readonly IDictionary RelationalServi { typeof(ISqlExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IRelationalQueryStringFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IRelationalParameterBasedSqlProcessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IRelationalSharedTypeEntityExpansionHelper), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMigrationsModelDiffer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMigrationsSqlGenerator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMigrator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -190,6 +191,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton() @@ -225,7 +227,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() - .AddDependencyScoped(); + .AddDependencyScoped() + .AddDependencyScoped(); return base.TryAddCoreServices(); } diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs index 92e92e9b9f5..fc84e889e4e 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs @@ -19,16 +19,26 @@ public class TableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TableBuilder(string? name, string? schema, IMutableEntityType entityType) + public TableBuilder(string? name, string? schema, EntityTypeBuilder entityTypeBuilder) { - Metadata = entityType; + EntityTypeBuilder = entityTypeBuilder; + Metadata = entityTypeBuilder.Metadata; } + //[EntityFrameworkInternal] + //public TableBuilder(string? name, string? schema, IMutableEntityType entityType) + //{ + // Metadata = entityType; + //} /// /// The entity type being configured. /// public virtual IMutableEntityType Metadata { get; } + /// + /// The entity type builder. + /// + public virtual EntityTypeBuilder EntityTypeBuilder { get; } /// /// Configures the table to be ignored by migrations. /// diff --git a/src/EFCore.Relational/Query/IRelationalSharedTypeEntityExpansionHelper.cs b/src/EFCore.Relational/Query/IRelationalSharedTypeEntityExpansionHelper.cs new file mode 100644 index 00000000000..38248942ecc --- /dev/null +++ b/src/EFCore.Relational/Query/IRelationalSharedTypeEntityExpansionHelper.cs @@ -0,0 +1,40 @@ +// 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.Generic; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Query +{ + /// + /// Service which helps with various aspects of shared type entity expansion extensibility for relational providrers. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + /// See Implementation of database providers and extensions + /// and How EF Core queries work for more information. + /// + public interface IRelationalSharedTypeEntityExpansionHelper + { + /// + /// Creates a SelectExpression representing owned type. + /// + public SelectExpression CreateInnerSelectExpression( + TableExpressionBase sourceTable, + IEntityType targetEntityType); + + /// + /// Returns true if the given table expression matches table metadata, false otherwise. + /// + public bool TableMatchesMetadata( + TableExpressionBase tableExpression, + ITableBase tableMetadata); + } +} diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 336560ca752..c6b3e0c2b12 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -46,7 +46,10 @@ public RelationalQueryableMethodTranslatingExpressionVisitor( _queryCompilationContext = queryCompilationContext; _sqlTranslator = relationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(queryCompilationContext, this); _sharedTypeEntityExpandingExpressionVisitor = - new SharedTypeEntityExpandingExpressionVisitor(_sqlTranslator, sqlExpressionFactory); + new SharedTypeEntityExpandingExpressionVisitor( + _sqlTranslator, + sqlExpressionFactory, + relationalDependencies.RelationalSharedTypeEntityExpansionHelper); _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = sqlExpressionFactory; _subquery = false; @@ -70,7 +73,11 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( _sqlTranslator = RelationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create( parentVisitor._queryCompilationContext, parentVisitor); _sharedTypeEntityExpandingExpressionVisitor = - new SharedTypeEntityExpandingExpressionVisitor(_sqlTranslator, parentVisitor._sqlExpressionFactory); + new SharedTypeEntityExpandingExpressionVisitor( + _sqlTranslator, + parentVisitor._sqlExpressionFactory, + RelationalDependencies.RelationalSharedTypeEntityExpansionHelper); + _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; _subquery = true; @@ -997,15 +1004,18 @@ private static readonly MethodInfo _objectEqualsMethodInfo private readonly RelationalSqlTranslatingExpressionVisitor _sqlTranslator; private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly IRelationalSharedTypeEntityExpansionHelper _sharedTypeEntityExpansionHelper; private SelectExpression _selectExpression; public SharedTypeEntityExpandingExpressionVisitor( RelationalSqlTranslatingExpressionVisitor sqlTranslator, - ISqlExpressionFactory sqlExpressionFactory) + ISqlExpressionFactory sqlExpressionFactory, + IRelationalSharedTypeEntityExpansionHelper sharedTypeEntityExpansionHelper) { _sqlTranslator = sqlTranslator; _sqlExpressionFactory = sqlExpressionFactory; + _sharedTypeEntityExpansionHelper = sharedTypeEntityExpansionHelper; _selectExpression = null!; } @@ -1087,11 +1097,21 @@ protected override Expression VisitExtension(Expression extensionExpression) return null; } + var entityProjectionExpression = (EntityProjectionExpression) + (entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression + ? _selectExpression.GetProjection(projectionBindingExpression) + : entityShaperExpression.ValueBufferExpression); + var foreignKey = navigation.ForeignKey; if (navigation.IsCollection) { - var innerShapedQuery = CreateShapedQueryExpression( - targetEntityType, _sqlExpressionFactory.Select(targetEntityType)); + var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( + entityProjectionExpression, + navigation, + foreignKey, + targetEntityType); + + var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression); var makeNullable = foreignKey.PrincipalKey.Properties .Concat(foreignKey.Properties) @@ -1139,11 +1159,6 @@ outerKey is NewArrayExpression newArrayExpression Expression.Quote(correlationPredicate)); } - var entityProjectionExpression = (EntityProjectionExpression) - (entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression - ? _selectExpression.GetProjection(projectionBindingExpression) - : entityShaperExpression.ValueBufferExpression); - var innerShaper = entityProjectionExpression.BindNavigation(navigation); if (innerShaper == null) { @@ -1171,7 +1186,12 @@ outerKey is NewArrayExpression newArrayExpression && navigation.DeclaringEntityType.IsStrictlyDerivedFrom(entityShaperExpression.EntityType)); var entityProjection = _selectExpression.GenerateWeakEntityProjectionExpression( - targetEntityType, table, identifyingColumn.Name, identifyingColumn.Table, principalNullable); + targetEntityType, + table, + identifyingColumn.Name, + identifyingColumn.Table, + _sharedTypeEntityExpansionHelper, + principalNullable); if (entityProjection != null) { @@ -1186,7 +1206,13 @@ outerKey is NewArrayExpression newArrayExpression // Owned types don't support inheritance See https://github.com/dotnet/efcore/issues/9630 // So there is no handling for dependent having TPT table = targetEntityType.GetViewOrTableMappings().Single().Table; - var innerSelectExpression = _sqlExpressionFactory.Select(targetEntityType); + + var innerSelectExpression = BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( + entityProjectionExpression, + navigation, + foreignKey, + targetEntityType); + var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression); var makeNullable = foreignKey.PrincipalKey.Properties @@ -1212,7 +1238,12 @@ outerKey is NewArrayExpression newArrayExpression innerShaper = new RelationalEntityShaperExpression( targetEntityType, _selectExpression.GenerateWeakEntityProjectionExpression( - targetEntityType, table, null, leftJoinTable, nullable: true)!, + targetEntityType, + table, + null, + leftJoinTable, + _sharedTypeEntityExpansionHelper, + nullable: true)!, nullable: true); } @@ -1220,6 +1251,47 @@ outerKey is NewArrayExpression newArrayExpression } return innerShaper; + + SelectExpression BuildInnerSelectExpressionForOwnedTypeMappedToDifferentTable( + EntityProjectionExpression entityProjectionExpression, + INavigation navigation, + IForeignKey foreignKey, + IEntityType targetEntityType) + { + // just need any column - we use it only to extract the table it originated from + var sourceColumn = entityProjectionExpression + .BindProperty( + navigation.IsOnDependent + ? foreignKey.Properties[0] + : foreignKey.PrincipalKey.Properties[0]); + + var sourceTable = FindRootTableExpressionForColumn(sourceColumn.Table, sourceColumn.Name); + + return _sharedTypeEntityExpansionHelper.CreateInnerSelectExpression( + sourceTable, + targetEntityType); + } + + static TableExpressionBase FindRootTableExpressionForColumn(TableExpressionBase table, string columnName) + { + if (table is JoinExpressionBase joinExpressionBase) + { + table = joinExpressionBase.Table; + } + else if (table is SetOperationBase setOperationBase) + { + table = setOperationBase.Source1; + } + + if (table is SelectExpression selectExpression) + { + var matchingProjection = (ColumnExpression)selectExpression.Projection.Where(p => p.Alias == columnName).Single().Expression; + + return FindRootTableExpressionForColumn(matchingProjection.Table, matchingProjection.Name); + } + + return table; + } } private static Expression AddConvertToObject(Expression expression) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs index 807527d1cb0..8fd51eca6c8 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs @@ -50,10 +50,12 @@ public sealed record RelationalQueryableMethodTranslatingExpressionVisitorDepend [EntityFrameworkInternal] public RelationalQueryableMethodTranslatingExpressionVisitorDependencies( IRelationalSqlTranslatingExpressionVisitorFactory relationalSqlTranslatingExpressionVisitorFactory, - ISqlExpressionFactory sqlExpressionFactory) + ISqlExpressionFactory sqlExpressionFactory, + IRelationalSharedTypeEntityExpansionHelper relationalSharedTypeEntityExpansionHelper) { RelationalSqlTranslatingExpressionVisitorFactory = relationalSqlTranslatingExpressionVisitorFactory; SqlExpressionFactory = sqlExpressionFactory; + RelationalSharedTypeEntityExpansionHelper = relationalSharedTypeEntityExpansionHelper; } /// @@ -65,5 +67,10 @@ public RelationalQueryableMethodTranslatingExpressionVisitorDependencies( /// The SQL expression factory. /// public ISqlExpressionFactory SqlExpressionFactory { get; init; } + + /// + /// Shared type entity expansion helper. + /// + public IRelationalSharedTypeEntityExpansionHelper RelationalSharedTypeEntityExpansionHelper { get; init; } } } diff --git a/src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelper.cs b/src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelper.cs new file mode 100644 index 00000000000..9549f1879ba --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelper.cs @@ -0,0 +1,38 @@ +// 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.Metadata; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Query +{ + /// + public class RelationalSharedTypeEntityExpansionHelper : IRelationalSharedTypeEntityExpansionHelper + { + /// + /// Creates a new instance of the class. + /// + /// Dependencies for this service. + public RelationalSharedTypeEntityExpansionHelper(RelationalSharedTypeEntityExpansionHelperDependencies dependencies) + { + Dependencies = dependencies; + } + + /// + /// Dependencies for this service. + /// + protected virtual RelationalSharedTypeEntityExpansionHelperDependencies Dependencies { get; } + + /// + public virtual SelectExpression CreateInnerSelectExpression( + TableExpressionBase sourceTable, + IEntityType targetEntityType) + => Dependencies.SqlExpressionFactory.Select(targetEntityType); + + /// + public virtual bool TableMatchesMetadata(TableExpressionBase tableExpression, ITableBase tableMetadata) + => tableExpression is TableExpression table + && table.Name == tableMetadata.Name + && table.Schema == tableMetadata.Schema; + } +} diff --git a/src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelperDependencies.cs b/src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelperDependencies.cs new file mode 100644 index 00000000000..8ae0349dc65 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelperDependencies.cs @@ -0,0 +1,67 @@ +// 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.Infrastructure; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Query +{ + /// + /// + /// Service dependencies parameter class for + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// Do not construct instances of this class directly from either provider or application code as the + /// constructor signature may change as new dependencies are added. Instead, use this type in + /// your constructor so that an instance will be created and injected automatically by the + /// dependency injection container. To create an instance with some dependent services replaced, + /// first resolve the object from the dependency injection container, then replace selected + /// services using the 'With...' methods. Do not call the constructor at any point in this process. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + public sealed record RelationalSharedTypeEntityExpansionHelperDependencies + { + /// + /// + /// Creates the service dependencies parameter object for a . + /// + /// + /// Do not call this constructor directly from either provider or application code as it may change + /// as new dependencies are added. Instead, use this type in your constructor so that an instance + /// will be created and injected automatically by the dependency injection container. To create + /// an instance with some dependent services replaced, first resolve the object from the dependency + /// injection container, then replace selected services using the 'With...' methods. Do not call + /// the constructor at any point in this process. + /// + /// + /// 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 RelationalSharedTypeEntityExpansionHelperDependencies(ISqlExpressionFactory sqlExpressionFactory) + { + Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + + SqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// The SQL expression factory. + /// + public ISqlExpressionFactory SqlExpressionFactory { get; init; } + } +} diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 05799d9dcff..ecf3df13740 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -1802,6 +1802,7 @@ public void ApplyDefaultIfEmpty(ISqlExpressionFactory sqlExpressionFactory) ITableBase table, string? columnName, TableExpressionBase tableExpressionBase, + IRelationalSharedTypeEntityExpansionHelper relationalSharedTypeEntityExpansionHelper, bool nullable = true) { if (columnName == null) @@ -1815,7 +1816,13 @@ public void ApplyDefaultIfEmpty(ISqlExpressionFactory sqlExpressionFactory) else { var propertyExpressions = GetPropertyExpressionFromSameTable( - entityType, table, this, tableExpressionBase, columnName, nullable); + entityType, + table, + this, + tableExpressionBase, + columnName, + nullable, + relationalSharedTypeEntityExpansionHelper); return propertyExpressions == null ? null @@ -1834,36 +1841,12 @@ static TableReferenceExpression FindTableReference(SelectExpression selectExpres SelectExpression selectExpression, TableExpressionBase tableExpressionBase, string columnName, - bool nullable) + bool nullable, + IRelationalSharedTypeEntityExpansionHelper relationalSharedTypeEntityExpansionHelper) { var unwrappedTable = UnwrapJoinExpression(tableExpressionBase); - if (unwrappedTable is TableExpression tableExpression) - { - if (!string.Equals(tableExpression.Name, table.Name, StringComparison.OrdinalIgnoreCase)) - { - // Fetch the table for the type which is defining the navigation since dependent would be in that table - tableExpressionBase = selectExpression.Tables - .First( - e => - { - var t = (TableExpression)UnwrapJoinExpression(e); - return t.Name == table.Name && t.Schema == table.Schema; - }); - } - - var propertyExpressions = new Dictionary(); - var tableReferenceExpression = FindTableReference(selectExpression, tableExpressionBase); - foreach (var property in entityType - .GetAllBaseTypes().Concat(entityType.GetDerivedTypesInclusive()) - .SelectMany(t => t.GetDeclaredProperties())) - { - propertyExpressions[property] = new ConcreteColumnExpression( - property, table.FindColumn(property)!, tableReferenceExpression, nullable || !property.IsPrimaryKey()); - } - - return propertyExpressions; - } + var tableReferenceExpression = FindTableReference(selectExpression, tableExpressionBase); if (unwrappedTable is SelectExpression subquery) { var subqueryIdentifyingColumn = (ColumnExpression)subquery.Projection @@ -1871,14 +1854,20 @@ static TableReferenceExpression FindTableReference(SelectExpression selectExpres .Expression; var subqueryPropertyExpressions = GetPropertyExpressionFromSameTable( - entityType, table, subquery, subqueryIdentifyingColumn.Table, subqueryIdentifyingColumn.Name, nullable); + entityType, + table, + subquery, + subqueryIdentifyingColumn.Table, + subqueryIdentifyingColumn.Name, + nullable, + relationalSharedTypeEntityExpansionHelper); + if (subqueryPropertyExpressions == null) { return null; } var newPropertyExpressions = new Dictionary(); - var tableReferenceExpression = FindTableReference(selectExpression, tableExpressionBase); foreach (var item in subqueryPropertyExpressions) { newPropertyExpressions[item.Key] = subquery.GenerateOuterColumn(tableReferenceExpression, item.Value); @@ -1887,7 +1876,40 @@ static TableReferenceExpression FindTableReference(SelectExpression selectExpres return newPropertyExpressions; } - return null; + if (unwrappedTable is FromSqlExpression + || unwrappedTable is TableValuedFunctionExpression) + { + return null; + } + + // TODO: should we be match case here or not? + if (!relationalSharedTypeEntityExpansionHelper.TableMatchesMetadata(unwrappedTable, table)) + { + // Fetch the table for the type which is defining the navigation since dependent would be in that table + var matchingTableExpressionBase = selectExpression.Tables + .FirstOrDefault(e => relationalSharedTypeEntityExpansionHelper.TableMatchesMetadata( + UnwrapJoinExpression(e), table)); + + if (matchingTableExpressionBase != null) + { + tableReferenceExpression = FindTableReference(selectExpression, matchingTableExpressionBase); + } + else + { + return null; + } + } + + var propertyExpressions = new Dictionary(); + foreach (var property in entityType + .GetAllBaseTypes().Concat(entityType.GetDerivedTypesInclusive()) + .SelectMany(t => t.GetDeclaredProperties())) + { + propertyExpressions[property] = new ConcreteColumnExpression( + property, table.FindColumn(property)!, tableReferenceExpression, nullable || !property.IsPrimaryKey()); + } + + return propertyExpressions; } static IReadOnlyDictionary GetPropertyExpressionsFromJoinedTable( diff --git a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs index be62bc027f2..530d5d6dd70 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerServiceCollectionExtensions.cs @@ -139,6 +139,7 @@ public static IServiceCollection AddEntityFrameworkSqlServer(this IServiceCollec .TryAdd() .TryAdd() .TryAdd() + .TryAdd() .TryAddProviderSpecificServices( b => b .TryAddSingleton() diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index 55f794d3f41..8a3e5067ab4 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -341,7 +341,49 @@ protected override void ValidateSharedTableCompatibility( if (mappedTypes.Any(t => t.IsTemporal()) && mappedTypes.Select(t => t.GetRootType()).Distinct().Count() > 1) { - throw new InvalidOperationException(SqlServerStrings.TemporalNotSupportedForTableSplitting(tableName)); + // table splitting is only supported when all entites mapped to this table + // have consistent temporal period mappings also + + var requiredPeriodStartColumnName = default(string); + var requiredPeriodEndColumnName = default(string); + + foreach (var mappedType in mappedTypes.Where(t => t.BaseType == null)) + { + if (!mappedType.IsTemporal()) + { + throw new InvalidOperationException("either none or all types mapped to same table must be temporal"); + } + + var periodStartPropertyName = mappedType.GetPeriodStartPropertyName(); + var periodEndPropertyName = mappedType.GetPeriodEndPropertyName(); + + var periodStartProperty = mappedType.GetProperty(periodStartPropertyName!); + var periodEndProperty = mappedType.GetProperty(periodEndPropertyName!); + + var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, mappedType.GetSchema()); + var periodStartColumnName = periodStartProperty.GetColumnName(storeObjectIdentifier); + var periodEndColumnName = periodEndProperty.GetColumnName(storeObjectIdentifier); + + if (requiredPeriodStartColumnName == null) + { + requiredPeriodStartColumnName = periodStartColumnName; + } + else if (requiredPeriodStartColumnName != periodStartColumnName) + { + throw new InvalidOperationException("periods must match"); + } + + if (requiredPeriodEndColumnName == null) + { + requiredPeriodEndColumnName = periodEndColumnName; + } + else if (requiredPeriodEndColumnName != periodEndColumnName) + { + throw new InvalidOperationException("periods must match"); + } + } + + //throw new InvalidOperationException(SqlServerStrings.TemporalNotSupportedForTableSplitting(tableName)); } base.ValidateSharedTableCompatibility(mappedTypes, tableName, schema, logger); diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs index ed679faafaf..20f6f592cd5 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// public class TemporalPeriodPropertyBuilder { - private readonly IMutableEntityType _entityType; + private readonly EntityTypeBuilder _entityTypeBuilder; private readonly string _periodPropertyName; /// @@ -22,9 +22,9 @@ public class TemporalPeriodPropertyBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TemporalPeriodPropertyBuilder(IMutableEntityType entityType, string periodPropertyName) + public TemporalPeriodPropertyBuilder(EntityTypeBuilder entityTypeBuilder, string periodPropertyName) { - _entityType = entityType; + _entityTypeBuilder = entityTypeBuilder; _periodPropertyName = periodPropertyName; } @@ -39,7 +39,7 @@ public TemporalPeriodPropertyBuilder(IMutableEntityType entityType, string perio /// The same builder instance so that multiple calls can be chained. public virtual TemporalPeriodPropertyBuilder HasColumnName(string name) { - _entityType.GetProperty(_periodPropertyName).SetColumnName(name); + _entityTypeBuilder.Property(_periodPropertyName).HasColumnName(name); return this; } diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs index 7e76a765d39..61488dd955d 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders /// public class TemporalTableBuilder { - private readonly IMutableEntityType _entityType; + private readonly EntityTypeBuilder _entityTypeBuilder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -21,9 +21,9 @@ public class TemporalTableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TemporalTableBuilder(IMutableEntityType entityType) + public TemporalTableBuilder(EntityTypeBuilder entityTypeBuilder) { - _entityType = entityType; + _entityTypeBuilder = entityTypeBuilder; } /// @@ -37,7 +37,7 @@ public TemporalTableBuilder(IMutableEntityType entityType) /// The same builder instance so that multiple calls can be chained. public virtual TemporalTableBuilder UseHistoryTable(string name) { - _entityType.SetHistoryTableName(name); + _entityTypeBuilder.Metadata.SetHistoryTableName(name); return this; } @@ -54,8 +54,8 @@ public virtual TemporalTableBuilder UseHistoryTable(string name) /// The same builder instance so that multiple calls can be chained. public virtual TemporalTableBuilder UseHistoryTable(string name, string? schema) { - _entityType.SetHistoryTableName(name); - _entityType.SetHistoryTableSchema(schema); + _entityTypeBuilder.Metadata.SetHistoryTableName(name); + _entityTypeBuilder.Metadata.SetHistoryTableSchema(schema); return this; } @@ -71,9 +71,10 @@ public virtual TemporalTableBuilder UseHistoryTable(string name, string? schema) /// An object that can be used to configure the period start property. public virtual TemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) { - _entityType.SetPeriodStartPropertyName(propertyName); + _entityTypeBuilder.Metadata.SetPeriodStartPropertyName(propertyName); + ConfigurePeriodProperty(propertyName); - return new TemporalPeriodPropertyBuilder(_entityType, propertyName); + return new TemporalPeriodPropertyBuilder(_entityTypeBuilder, propertyName); } /// @@ -87,9 +88,27 @@ public virtual TemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) /// An object that can be used to configure the period end property. public virtual TemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) { - _entityType.SetPeriodEndPropertyName(propertyName); + _entityTypeBuilder.Metadata.SetPeriodEndPropertyName(propertyName); + ConfigurePeriodProperty(propertyName); - return new TemporalPeriodPropertyBuilder(_entityType, propertyName); + return new TemporalPeriodPropertyBuilder(_entityTypeBuilder, propertyName); + } + + private void ConfigurePeriodProperty(string propertyName) + { + var property = _entityTypeBuilder.Property(propertyName); + //var property = _entityTypeBuilder.Property(typeof(DateTime), propertyName, setTypeConfigurationSource: false); + property.ValueGeneratedOnAddOrUpdate(); + + // TODO: do we need to set the default column name here? + + //if (property == null) + //{ + // property = _entityType.AddProperty(propertyName, typeof(DateTime)); + // property.SetColumnName(propertyName); + //} + + //property.ValueGenerated = ValueGenerated.OnAddOrUpdate; } #region Hidden System.Object members diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs index f1e39a54e0d..47ed99ba99a 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs @@ -20,8 +20,8 @@ public class TemporalTableBuilder : TemporalTableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TemporalTableBuilder(IMutableEntityType entityType) - : base(entityType) + public TemporalTableBuilder(EntityTypeBuilder entityTypeBuilder) + : base(entityTypeBuilder) { } diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index 23fd122e7e9..ba978d7792f 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -331,6 +331,14 @@ public static string TemporalSetOperationOnMismatchedSources(object? entityType) GetString("TemporalSetOperationOnMismatchedSources", nameof(entityType)), entityType); + /// + /// Only '{operationName}' temporal operation is supported for entitiy that owns another entity which is mapped different table. + /// + public static string TemporalOwnedTypeMappedToDifferentTableOnlySupportedForAsOf(object? operationName) + => string.Format( + GetString("TemporalOwnedTypeMappedToDifferentTableOnlySupportedForAsOf", nameof(operationName)), + operationName); + /// /// An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure' to the 'UseSqlServer' call. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index 5770c19ea28..464cda3061d 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -325,6 +325,9 @@ Set operation can't be applied on entity '{entityType}' because temporal operations on both arguments don't match. + + Only '{operationName}' temporal operation is supported for entitiy that owns another entity which is mapped different table. + An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure' to the 'UseSqlServer' call. diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSharedTypeEntityExpansionHelper.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSharedTypeEntityExpansionHelper.cs new file mode 100644 index 00000000000..6ad9bc0b795 --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSharedTypeEntityExpansionHelper.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal +{ + /// + public class SqlServerSharedTypeEntityExpansionHelper : RelationalSharedTypeEntityExpansionHelper + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlServerSharedTypeEntityExpansionHelper(RelationalSharedTypeEntityExpansionHelperDependencies dependencies) + : base(dependencies) + { + } + + /// + public override SelectExpression CreateInnerSelectExpression( + TableExpressionBase sourceTable, + IEntityType targetEntityType) + { + if (sourceTable is TemporalAsOfTableExpression temporalAsOf) + { + var table = targetEntityType.GetTableMappings().Single().Table; + var temporalTableExpression = new TemporalAsOfTableExpression(table, temporalAsOf.PointInTime); + + return Dependencies.SqlExpressionFactory.Select(targetEntityType, temporalTableExpression); + } + + if (sourceTable is TemporalTableExpression) + { + throw new InvalidOperationException( + SqlServerStrings.TemporalOwnedTypeMappedToDifferentTableOnlySupportedForAsOf("AsOf")); + } + + return base.CreateInnerSelectExpression(sourceTable, targetEntityType); + } + + /// + public override bool TableMatchesMetadata(TableExpressionBase tableExpression, ITableBase tableMetadata) + => base.TableMatchesMetadata(tableExpression, tableMetadata) + || (tableExpression is TemporalTableExpression table + && table.Name == tableMetadata.Name + && table.Schema == tableMetadata.Schema); + } +} diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 081a2e7540c..315c257b607 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -170,6 +170,32 @@ public virtual PropertyBuilder Property(Type propertyType, string propertyName) Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + ///// + ///// Returns an object that can be used to configure a property of the entity type. + ///// If no property with the given name exists, then a new property will be added. + ///// + ///// + ///// When adding a new property, if a property with the same name exists in the entity class + ///// then it will be added to the model. If no property exists in the entity class, then + ///// a new shadow state property will be added. A shadow state property is one that does not have a + ///// corresponding property in the entity class. The current value for the property is stored in + ///// the rather than being stored in instances of the entity class. + ///// + ///// The type of the property to be configured. + ///// The name of the property to be configured. + ///// Indicates whether the type configuration source should be set. + ///// An object that can be used to configure the property. + //public virtual PropertyBuilder Property( + // Type propertyType, + // string propertyName, + // bool setTypeConfigurationSource = true) + // => new( + // Builder.Property( + // Check.NotNull(propertyType, nameof(propertyType)), + // Check.NotEmpty(propertyName, nameof(propertyName)), + // ConfigurationSource.Explicit, + // setTypeConfigurationSource ? ConfigurationSource.Explicit : null)!.Metadata); + /// /// Returns an object that can be used to configure a property of the entity type. /// If no property with the given name exists, then a new property will be added. diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index 246188130f6..02240115e74 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -928,6 +928,15 @@ public virtual async Task Filter_on_indexer_using_function_argument(bool async) await myFunc(async, zipCode); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Simple_query_entity_with_owned_collection(bool async) + { + return AssertQuery( + async, + ss => ss.Set()); + } + protected virtual DbContext CreateContext() => Fixture.CreateContext(); @@ -1094,6 +1103,7 @@ public IReadOnlyDictionary GetEntityAsserters() var aa = (Planet)a; Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); Assert.Equal(ee.StarId, aa.StarId); } } @@ -1500,7 +1510,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con }); }); - modelBuilder.Entity(pb => pb.HasData(new Planet { Id = 1, StarId = 1 })); + modelBuilder.Entity(pb => pb.HasData(new Planet { Id = 1, StarId = 1, Name = "Earth" })); modelBuilder.Entity( mb => mb.HasData( @@ -1630,11 +1640,16 @@ public virtual IQueryable Set() return (IQueryable)_bartons.AsQueryable(); } + if (typeof(TEntity) == typeof(Star)) + { + return (IQueryable)_stars.AsQueryable(); + } + throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); } private static IReadOnlyList CreatePlanets() - => new List { new() { Id = 1, StarId = 1 } }; + => new List { new() { Id = 1, StarId = 1, Name = "Earth" } }; private static IReadOnlyList CreateStars() => new List @@ -1978,6 +1993,8 @@ protected class Planet { public int Id { get; set; } + public string Name { get; set; } + public int StarId { get; set; } public Star Star { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 6647fb21fea..744fc4c7cf1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -229,7 +229,7 @@ public override async Task Navigation_rewrite_on_owned_reference_followed_by_reg await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async); AssertSql( - @"SELECT [p].[Id], [p].[StarId] + @"SELECT [p].[Id], [p].[Name], [p].[StarId] FROM [OwnedPerson] AS [o] LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id]"); } @@ -256,7 +256,7 @@ public override async Task Project_multiple_owned_navigations(bool async) await base.Project_multiple_owned_navigations(async); AssertSql( - @"SELECT [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [p].[StarId] + @"SELECT [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [p].[Name], [p].[StarId] FROM [OwnedPerson] AS [o] LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] LEFT JOIN ( @@ -278,7 +278,7 @@ FROM [Order] AS [o0] LEFT JOIN [OwnedPerson] AS [o1] ON [o0].[ClientId] = [o1].[Id] LEFT JOIN [Planet] AS [p0] ON [o1].[PersonAddress_Country_PlanetId] = [p0].[Id] LEFT JOIN [Star] AS [s] ON [p0].[StarId] = [s].[Id] - WHERE ([o].[Id] = [o0].[ClientId]) AND (([s].[Id] <> 42) OR [s].[Id] IS NULL)) AS [Count], [p].[Id], [p].[StarId] + WHERE ([o].[Id] = [o0].[ClientId]) AND (([s].[Id] <> 42) OR [s].[Id] IS NULL)) AS [Count], [p].[Id], [p].[Name], [p].[StarId] FROM [OwnedPerson] AS [o] LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] ORDER BY [o].[Id]"); @@ -962,7 +962,7 @@ public override async Task Project_multiple_owned_navigations_split(bool async) await base.Project_multiple_owned_navigations_split(async); AssertSql( - @"SELECT [o].[Id], [p].[Id], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [p].[StarId] + @"SELECT [o].[Id], [p].[Id], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [p].[Name], [p].[StarId] FROM [OwnedPerson] AS [o] LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] ORDER BY [o].[Id], [p].[Id]", @@ -1166,7 +1166,7 @@ public override async Task Projecting_collection_correlated_with_keyless_entity_ await base.Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(async); AssertSql( - @"SELECT [b].[Throned_Value], [f].[Id], [b].[Id], [p].[Id], [p].[StarId] + @"SELECT [b].[Throned_Value], [f].[Id], [b].[Id], [p].[Id], [p].[Name], [p].[StarId] FROM [Fink] AS [f] LEFT JOIN [Barton] AS [b] ON [f].[BartonId] = [b].[Id] LEFT JOIN [Planet] AS [p] ON ([b].[Throned_Value] <> [p].[Id]) OR [b].[Throned_Value] IS NULL diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 03369fbe834..000778df10d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -18,7 +18,6 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs new file mode 100644 index 00000000000..2bf475573b6 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs @@ -0,0 +1,1738 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit.Abstractions; + +namespace Microsoft.EntityFrameworkCore.Query +{ + [SqlServerCondition(SqlServerCondition.SupportsTemporalTablesCascadeDelete)] + public class TemporalOwnedQuerySqlServerTest : OwnedQueryRelationalTestBase + { + public TemporalOwnedQuerySqlServerTest(TemporalOwnedQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected override bool CanExecuteQueryString + => true; + + protected override Expression RewriteServerQueryExpression(Expression serverQueryExpression) + { + var temporalEntityTypes = new List + { + typeof(OwnedPerson), + typeof(Branch), + typeof(LeafA), + typeof(LeafB), + typeof(Barton), + typeof(Star), + typeof(Planet), + typeof(Moon), + }; + + var rewriter = new TemporalPointInTimeQueryRewriter(Fixture.ChangesDate, temporalEntityTypes); + + return rewriter.Visit(serverQueryExpression); + } + + public override async Task Query_with_owned_entity_equality_operator(bool async) + { + await base.Query_with_owned_entity_equality_operator(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +CROSS JOIN ( + SELECT [o0].[Id] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + WHERE [o0].[Discriminator] = N'LeafB' +) AS [t] +LEFT JOIN ( + SELECT [o1].[ClientId], [o1].[Id], [o1].[OrderDate], [o1].[PeriodEnd], [o1].[PeriodStart], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id] AS [Id0], [o2].[Detail], [o2].[PeriodEnd] AS [PeriodEnd0], [o2].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o1].[ClientId] = [o2].[OrderClientId]) AND ([o1].[Id] = [o2].[OrderId]) +) AS [t0] ON [o].[Id] = [t0].[ClientId] +WHERE 0 = 1 +ORDER BY [o].[Id], [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Query_for_base_type_loads_all_owned_navs(bool async) + { + await base.Query_for_base_type_loads_all_owned_navs(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task No_ignored_include_warning_when_implicit_load(bool async) + { + await base.No_ignored_include_warning_when_implicit_load(async); + + AssertSql( + @"SELECT COUNT(*) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Query_for_branch_type_loads_all_owned_navs(bool async) + { + await base.Query_for_branch_type_loads_all_owned_navs(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[Discriminator] IN (N'Branch', N'LeafA') +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Query_for_branch_type_loads_all_owned_navs_tracking(bool async) + { + await base.Query_for_branch_type_loads_all_owned_navs_tracking(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[Discriminator] IN (N'Branch', N'LeafA') +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Query_for_leaf_type_loads_all_owned_navs(bool async) + { + await base.Query_for_leaf_type_loads_all_owned_navs(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[Discriminator] = N'LeafA' +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Query_when_subquery(bool async) + { + await base.Query_when_subquery(async); + + AssertSql( + @"@__p_0='5' + +SELECT [t0].[Id], [t0].[Discriminator], [t0].[Name], [t0].[PeriodEnd], [t0].[PeriodStart], [t1].[ClientId], [t1].[Id], [t1].[OrderDate], [t1].[PeriodEnd], [t1].[PeriodStart], [t1].[OrderClientId], [t1].[OrderId], [t1].[Id0], [t1].[Detail], [t1].[PeriodEnd0], [t1].[PeriodStart0], [t0].[PersonAddress_AddressLine], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t0].[PersonAddress_PlaceType], [t0].[PersonAddress_ZipCode], [t0].[PersonAddress_Country_Name], [t0].[PersonAddress_Country_PlanetId], [t0].[BranchAddress_BranchName], [t0].[BranchAddress_PlaceType], [t0].[BranchAddress_Country_Name], [t0].[BranchAddress_Country_PlanetId], [t0].[LeafBAddress_LeafBType], [t0].[LeafBAddress_PlaceType], [t0].[LeafBAddress_Country_Name], [t0].[LeafBAddress_Country_PlanetId], [t0].[LeafAAddress_LeafType], [t0].[LeafAAddress_PlaceType], [t0].[LeafAAddress_Country_Name], [t0].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT TOP(@__p_0) [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] + FROM ( + SELECT DISTINCT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ) AS [t] + ORDER BY [t].[Id] +) AS [t0] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t1] ON [t0].[Id] = [t1].[ClientId] +ORDER BY [t0].[Id], [t1].[ClientId], [t1].[Id], [t1].[OrderClientId], [t1].[OrderId]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_projecting_scalar(bool async) + { + await base.Navigation_rewrite_on_owned_reference_projecting_scalar(async); + + AssertSql( + @"SELECT [o].[PersonAddress_Country_Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[PersonAddress_Country_Name] = N'USA'"); + } + + public override async Task Navigation_rewrite_on_owned_reference_projecting_entity(bool async) + { + await base.Navigation_rewrite_on_owned_reference_projecting_entity(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[PersonAddress_Country_Name] = N'USA' +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Navigation_rewrite_on_owned_collection(bool async) + { + await base.Navigation_rewrite_on_owned_collection(async); + + AssertSql( + @"SELECT [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o1].[ClientId], [o1].[Id], [o1].[OrderDate], [o1].[PeriodEnd], [o1].[PeriodStart], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id] AS [Id0], [o2].[Detail], [o2].[PeriodEnd] AS [PeriodEnd0], [o2].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o1].[ClientId] = [o2].[OrderClientId]) AND ([o1].[Id] = [o2].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE ( + SELECT COUNT(*) + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + WHERE [o].[Id] = [o0].[ClientId]) > 0 +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Navigation_rewrite_on_owned_collection_with_composition(bool async) + { + await base.Navigation_rewrite_on_owned_collection_with_composition(async); + + AssertSql( + @"SELECT COALESCE(( + SELECT TOP(1) CASE + WHEN [o0].[Id] <> 42 THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) + END + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + WHERE [o].[Id] = [o0].[ClientId] + ORDER BY [o0].[Id]), CAST(0 AS bit)) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +ORDER BY [o].[Id]"); + } + + public override async Task Navigation_rewrite_on_owned_collection_with_composition_complex(bool async) + { + await base.Navigation_rewrite_on_owned_collection_with_composition_complex(async); + + AssertSql( + @"SELECT ( + SELECT TOP(1) [o1].[PersonAddress_Country_Name] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON [o0].[ClientId] = [o1].[Id] + WHERE [o].[Id] = [o0].[ClientId] + ORDER BY [o0].[Id]) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task SelectMany_on_owned_collection(bool async) + { + await base.SelectMany_on_owned_collection(async); + + AssertSql( + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id], [o1].[OrderClientId], [o1].[OrderId]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity(async); + + AssertSql( + @"SELECT [p].[Id], [p].[Name], [p].[PeriodEnd], [p].[PeriodStart], [p].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id]"); + } + + public override async Task Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(bool async) + { + await base.Filter_owned_entity_chained_with_regular_entity_followed_by_projecting_owned_collection(async); + + AssertSql( + @"SELECT [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE ([p].[Id] <> 42) OR [p].[Id] IS NULL +ORDER BY [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Project_multiple_owned_navigations(bool async) + { + await base.Project_multiple_owned_navigations(async); + + AssertSql( + @"SELECT [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [p].[Name], [p].[PeriodEnd], [p].[PeriodStart], [p].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +ORDER BY [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Project_multiple_owned_navigations_with_expansion_on_owned_collections(bool async) + { + await base.Project_multiple_owned_navigations_with_expansion_on_owned_collections(async); + + AssertSql( + @"SELECT ( + SELECT COUNT(*) + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON [o0].[ClientId] = [o1].[Id] + LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p0] ON [o1].[PersonAddress_Country_PlanetId] = [p0].[Id] + LEFT JOIN [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [p0].[StarId] = [s].[Id] + WHERE ([o].[Id] = [o0].[ClientId]) AND (([s].[Id] <> 42) OR [s].[Id] IS NULL)) AS [Count], [p].[Id], [p].[Name], [p].[PeriodEnd], [p].[PeriodStart], [p].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +ORDER BY [o].[Id]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_filter(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE ([p].[Id] <> 7) OR [p].[Id] IS NULL +ORDER BY [o].[Id], [p].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_property(async); + + AssertSql( + @"SELECT [p].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection(async); + + AssertSql( + @"SELECT [o].[Id], [p].[Id], [m].[Id], [m].[Diameter], [m].[PeriodEnd], [m].[PeriodStart], [m].[PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Moon] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m] ON [p].[Id] = [m].[PlanetId] +ORDER BY [o].[Id], [p].[Id]"); + } + + public override async Task SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(bool async) + { + await base.SelectMany_on_owned_reference_followed_by_regular_entity_and_collection(async); + + AssertSql( + @"SELECT [m].[Id], [m].[Diameter], [m].[PeriodEnd], [m].[PeriodStart], [m].[PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +INNER JOIN [Moon] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m] ON [p].[Id] = [m].[PlanetId]"); + } + + public override async Task SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(bool async) + { + await base.SelectMany_on_owned_reference_with_entity_in_between_ending_in_owned_collection(async); + + AssertSql( + @"SELECT [e].[Id], [e].[Name], [e].[PeriodEnd], [e].[PeriodStart], [e].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [p].[StarId] = [s].[Id] +INNER JOIN [Element] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e] ON [s].[Id] = [e].[StarId]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(async); + + AssertSql( + @"SELECT [s].[Id], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart], [o].[Id], [p].[Id], [e].[Id], [e].[Name], [e].[PeriodEnd], [e].[PeriodStart], [e].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [p].[StarId] = [s].[Id] +LEFT JOIN [Element] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e] ON [s].[Id] = [e].[StarId] +ORDER BY [o].[Id], [p].[Id], [s].[Id]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar( + bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar(async); + + AssertSql( + @"SELECT [s].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [p].[StarId] = [s].[Id]"); + } + + public override async Task + Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection( + async); + + AssertSql( + @"SELECT [s].[Id], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart], [o].[Id], [p].[Id], [e].[Id], [e].[Name], [e].[PeriodEnd], [e].[PeriodStart], [e].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [p].[StarId] = [s].[Id] +LEFT JOIN [Element] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e] ON [s].[Id] = [e].[StarId] +WHERE [s].[Name] = N'Sol' +ORDER BY [o].[Id], [p].[Id], [s].[Id]"); + } + + public override async Task Query_with_OfType_eagerly_loads_correct_owned_navigations(bool async) + { + await base.Query_with_OfType_eagerly_loads_correct_owned_navigations(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[Discriminator] = N'LeafA' +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Unmapped_property_projection_loads_owned_navigations(bool async) + { + await base.Unmapped_property_projection_loads_owned_navigations(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[Id] = 1 +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Client_method_skip_loads_owned_navigations(bool async) + { + await base.Client_method_skip_loads_owned_navigations(async); + + AssertSql( + @"@__p_0='1' + +SELECT [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ORDER BY [o].[Id] + OFFSET @__p_0 ROWS +) AS [t] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t0] ON [t].[Id] = [t0].[ClientId] +ORDER BY [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Client_method_take_loads_owned_navigations(bool async) + { + await base.Client_method_take_loads_owned_navigations(async); + + AssertSql( + @"@__p_0='2' + +SELECT [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT TOP(@__p_0) [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ORDER BY [o].[Id] +) AS [t] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t0] ON [t].[Id] = [t0].[ClientId] +ORDER BY [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Client_method_skip_take_loads_owned_navigations(bool async) + { + await base.Client_method_skip_take_loads_owned_navigations(async); + + AssertSql( + @"@__p_0='1' +@__p_1='2' + +SELECT [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ORDER BY [o].[Id] + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY +) AS [t] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t0] ON [t].[Id] = [t0].[ClientId] +ORDER BY [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Client_method_skip_loads_owned_navigations_variation_2(bool async) + { + await base.Client_method_skip_loads_owned_navigations_variation_2(async); + + AssertSql( + @"@__p_0='1' + +SELECT [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ORDER BY [o].[Id] + OFFSET @__p_0 ROWS +) AS [t] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t0] ON [t].[Id] = [t0].[ClientId] +ORDER BY [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Client_method_take_loads_owned_navigations_variation_2(bool async) + { + await base.Client_method_take_loads_owned_navigations_variation_2(async); + + AssertSql( + @"@__p_0='2' + +SELECT [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT TOP(@__p_0) [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ORDER BY [o].[Id] +) AS [t] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t0] ON [t].[Id] = [t0].[ClientId] +ORDER BY [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Client_method_skip_take_loads_owned_navigations_variation_2(bool async) + { + await base.Client_method_skip_take_loads_owned_navigations_variation_2(async); + + AssertSql( + @"@__p_0='1' +@__p_1='2' + +SELECT [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t0].[ClientId], [t0].[Id], [t0].[OrderDate], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[OrderClientId], [t0].[OrderId], [t0].[Id0], [t0].[Detail], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ORDER BY [o].[Id] + OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY +) AS [t] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t0] ON [t].[Id] = [t0].[ClientId] +ORDER BY [t].[Id], [t0].[ClientId], [t0].[Id], [t0].[OrderClientId], [t0].[OrderId]"); + } + + public override async Task Where_owned_collection_navigation_ToList_Count(bool async) + { + await base.Where_owned_collection_navigation_ToList_Count(async); + + AssertSql( + @"SELECT [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id], [o2].[Detail], [o2].[PeriodEnd], [o2].[PeriodStart] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o0].[ClientId] = [o2].[OrderClientId]) AND ([o0].[Id] = [o2].[OrderId]) +WHERE ( + SELECT COUNT(*) + FROM [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + WHERE ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId])) = 0 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId]"); + } + + public override async Task Where_collection_navigation_ToArray_Count(bool async) + { + await base.Where_collection_navigation_ToArray_Count(async); + + AssertSql( + @"SELECT [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id], [o2].[Detail], [o2].[PeriodEnd], [o2].[PeriodStart] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o0].[ClientId] = [o2].[OrderClientId]) AND ([o0].[Id] = [o2].[OrderId]) +WHERE ( + SELECT COUNT(*) + FROM [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + WHERE ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId])) = 0 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId]"); + } + + public override async Task Where_collection_navigation_AsEnumerable_Count(bool async) + { + await base.Where_collection_navigation_AsEnumerable_Count(async); + + AssertSql( + @"SELECT [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id], [o2].[Detail], [o2].[PeriodEnd], [o2].[PeriodStart] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o0].[ClientId] = [o2].[OrderClientId]) AND ([o0].[Id] = [o2].[OrderId]) +WHERE ( + SELECT COUNT(*) + FROM [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + WHERE ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId])) = 0 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId]"); + } + + public override async Task Where_collection_navigation_ToList_Count_member(bool async) + { + await base.Where_collection_navigation_ToList_Count_member(async); + + AssertSql( + @"SELECT [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id], [o2].[Detail], [o2].[PeriodEnd], [o2].[PeriodStart] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o0].[ClientId] = [o2].[OrderClientId]) AND ([o0].[Id] = [o2].[OrderId]) +WHERE ( + SELECT COUNT(*) + FROM [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + WHERE ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId])) = 0 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId]"); + } + + public override async Task Where_collection_navigation_ToArray_Length_member(bool async) + { + await base.Where_collection_navigation_ToArray_Length_member(async); + + AssertSql( + @"SELECT [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId], [o2].[Id], [o2].[Detail], [o2].[PeriodEnd], [o2].[PeriodStart] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o2] ON ([o0].[ClientId] = [o2].[OrderClientId]) AND ([o0].[Id] = [o2].[OrderId]) +WHERE ( + SELECT COUNT(*) + FROM [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] + WHERE ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId])) = 0 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id], [o2].[OrderClientId], [o2].[OrderId]"); + } + + public override async Task Can_query_on_indexer_properties(bool async) + { + await base.Can_query_on_indexer_properties(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[Name] = N'Mona Cy' +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Can_query_on_owned_indexer_properties(bool async) + { + await base.Can_query_on_owned_indexer_properties(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[PersonAddress_ZipCode] = 38654"); + } + + public override async Task Can_query_on_indexer_property_when_property_name_from_closure(bool async) + { + await base.Can_query_on_indexer_property_when_property_name_from_closure(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[Name] = N'Mona Cy'"); + } + + public override async Task Can_project_indexer_properties(bool async) + { + await base.Can_project_indexer_properties(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Can_project_owned_indexer_properties(bool async) + { + await base.Can_project_owned_indexer_properties(async); + + AssertSql( + @"SELECT [o].[PersonAddress_AddressLine] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Can_project_indexer_properties_converted(bool async) + { + await base.Can_project_indexer_properties_converted(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Can_project_owned_indexer_properties_converted(bool async) + { + await base.Can_project_owned_indexer_properties_converted(async); + + AssertSql( + @"SELECT [o].[PersonAddress_AddressLine] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Can_OrderBy_indexer_properties(bool async) + { + await base.Can_OrderBy_indexer_properties(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +ORDER BY [o].[Name], [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Can_OrderBy_indexer_properties_converted(bool async) + { + await base.Can_OrderBy_indexer_properties_converted(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +ORDER BY [o].[Name], [o].[Id]"); + } + + public override async Task Can_OrderBy_owned_indexer_properties(bool async) + { + await base.Can_OrderBy_owned_indexer_properties(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +ORDER BY [o].[PersonAddress_ZipCode], [o].[Id]"); + } + + public override async Task Can_OrderBy_owened_indexer_properties_converted(bool async) + { + await base.Can_OrderBy_owened_indexer_properties_converted(async); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +ORDER BY [o].[PersonAddress_ZipCode], [o].[Id]"); + } + + public override async Task Can_group_by_indexer_property(bool isAsync) + { + await base.Can_group_by_indexer_property(isAsync); + + AssertSql( + @"SELECT COUNT(*) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +GROUP BY [o].[Name]"); + } + + public override async Task Can_group_by_converted_indexer_property(bool isAsync) + { + await base.Can_group_by_converted_indexer_property(isAsync); + + AssertSql( + @"SELECT COUNT(*) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +GROUP BY [o].[Name]"); + } + + public override async Task Can_group_by_owned_indexer_property(bool isAsync) + { + await base.Can_group_by_owned_indexer_property(isAsync); + + AssertSql( + @"SELECT COUNT(*) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +GROUP BY [o].[PersonAddress_ZipCode]"); + } + + public override async Task Can_group_by_converted_owned_indexer_property(bool isAsync) + { + await base.Can_group_by_converted_owned_indexer_property(isAsync); + + AssertSql( + @"SELECT COUNT(*) +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +GROUP BY [o].[PersonAddress_ZipCode]"); + } + + public override async Task Can_join_on_indexer_property_on_query(bool isAsync) + { + await base.Can_join_on_indexer_property_on_query(isAsync); + + AssertSql( + @"SELECT [o].[Id], [o0].[PersonAddress_Country_Name] AS [Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[PersonAddress_ZipCode] = [o0].[PersonAddress_ZipCode]"); + } + + public override async Task Projecting_indexer_property_ignores_include(bool isAsync) + { + await base.Projecting_indexer_property_ignores_include(isAsync); + + AssertSql( + @"SELECT [o].[PersonAddress_ZipCode] AS [Nation] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Projecting_indexer_property_ignores_include_converted(bool isAsync) + { + await base.Projecting_indexer_property_ignores_include_converted(isAsync); + + AssertSql( + @"SELECT [o].[PersonAddress_ZipCode] AS [Nation] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o]"); + } + + public override async Task Indexer_property_is_pushdown_into_subquery(bool isAsync) + { + await base.Indexer_property_is_pushdown_into_subquery(isAsync); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE ( + SELECT TOP(1) [o0].[Name] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + WHERE [o0].[Id] = [o].[Id]) = N'Mona Cy'"); + } + + public override async Task Can_query_indexer_property_on_owned_collection(bool isAsync) + { + await base.Can_query_indexer_property_on_owned_collection(isAsync); + + AssertSql( + @"SELECT [o].[Name] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE ( + SELECT COUNT(*) + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + WHERE ([o].[Id] = [o0].[ClientId]) AND (DATEPART(year, [o0].[OrderDate]) = 2018)) = 1"); + } + + public override async Task Query_for_base_type_loads_all_owned_navs_split(bool async) + { + await base.Query_for_base_type_loads_all_owned_navs_split(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +ORDER BY [o].[Id]", + // + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]", + // + @"SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [o].[Id], [o0].[ClientId], [o0].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task Query_for_branch_type_loads_all_owned_navs_split(bool async) + { + await base.Query_for_branch_type_loads_all_owned_navs_split(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[Discriminator] IN (N'Branch', N'LeafA') +ORDER BY [o].[Id]", + // + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +WHERE [o].[Discriminator] IN (N'Branch', N'LeafA') +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]", + // + @"SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [o].[Id], [o0].[ClientId], [o0].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +WHERE [o].[Discriminator] IN (N'Branch', N'LeafA') +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task Query_when_subquery_split(bool async) + { + await base.Query_when_subquery_split(async); + + AssertSql( + @"@__p_0='5' + +SELECT TOP(@__p_0) [t].[Id], [t].[Discriminator], [t].[Name], [t].[PeriodEnd], [t].[PeriodStart], [t].[PersonAddress_AddressLine], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t].[BranchAddress_BranchName], [t].[BranchAddress_PlaceType], [t].[BranchAddress_Country_Name], [t].[BranchAddress_Country_PlanetId], [t].[LeafBAddress_LeafBType], [t].[LeafBAddress_PlaceType], [t].[LeafBAddress_Country_Name], [t].[LeafBAddress_Country_PlanetId], [t].[LeafAAddress_LeafType], [t].[LeafAAddress_PlaceType], [t].[LeafAAddress_Country_Name], [t].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT DISTINCT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +) AS [t] +ORDER BY [t].[Id]", + // + @"@__p_0='5' + +SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [t0].[Id] +FROM ( + SELECT TOP(@__p_0) [t].[Id] + FROM ( + SELECT DISTINCT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ) AS [t] + ORDER BY [t].[Id] +) AS [t0] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [t0].[Id] = [o0].[ClientId] +ORDER BY [t0].[Id], [o0].[ClientId], [o0].[Id]", + // + @"@__p_0='5' + +SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [t0].[Id], [o0].[ClientId], [o0].[Id] +FROM ( + SELECT TOP(@__p_0) [t].[Id] + FROM ( + SELECT DISTINCT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] + ) AS [t] + ORDER BY [t].[Id] +) AS [t0] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [t0].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +ORDER BY [t0].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task Project_multiple_owned_navigations_split(bool async) + { + await base.Project_multiple_owned_navigations_split(async); + + AssertSql( + @"SELECT [o].[Id], [p].[Id], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [p].[Name], [p].[PeriodEnd], [p].[PeriodStart], [p].[StarId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +ORDER BY [o].[Id], [p].[Id]", + // + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id], [p].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +ORDER BY [o].[Id], [p].[Id], [o0].[ClientId], [o0].[Id]", + // + @"SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [o].[Id], [p].[Id], [o0].[ClientId], [o0].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +ORDER BY [o].[Id], [p].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_split(bool async) + { + await base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_collection_split(async); + + AssertSql( + @"SELECT [o].[Id], [p].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +ORDER BY [o].[Id], [p].[Id]", + // + @"SELECT [m].[Id], [m].[Diameter], [m].[PeriodEnd], [m].[PeriodStart], [m].[PlanetId], [o].[Id], [p].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] +INNER JOIN [Moon] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m] ON [p].[Id] = [m].[PlanetId] +ORDER BY [o].[Id], [p].[Id]"); + } + + public override async Task Query_with_OfType_eagerly_loads_correct_owned_navigations_split(bool async) + { + await base.Query_with_OfType_eagerly_loads_correct_owned_navigations_split(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[Discriminator] = N'LeafA' +ORDER BY [o].[Id]", + // + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +WHERE [o].[Discriminator] = N'LeafA' +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]", + // + @"SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [o].[Id], [o0].[ClientId], [o0].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +WHERE [o].[Discriminator] = N'LeafA' +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task Unmapped_property_projection_loads_owned_navigations_split(bool async) + { + await base.Unmapped_property_projection_loads_owned_navigations_split(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[Id] = 1 +ORDER BY [o].[Id]", + // + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +WHERE [o].[Id] = 1 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]", + // + @"SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [o].[Id], [o0].[ClientId], [o0].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +WHERE [o].[Id] = 1 +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task Can_query_on_indexer_properties_split(bool async) + { + await base.Can_query_on_indexer_properties_split(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +WHERE [o].[Name] = N'Mona Cy' +ORDER BY [o].[Id]", + // + @"SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +WHERE [o].[Name] = N'Mona Cy' +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]", + // + @"SELECT [o1].[OrderClientId], [o1].[OrderId], [o1].[Id], [o1].[Detail], [o1].[PeriodEnd], [o1].[PeriodStart], [o].[Id], [o0].[ClientId], [o0].[Id] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +INNER JOIN [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] ON [o].[Id] = [o0].[ClientId] +INNER JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +WHERE [o].[Name] = N'Mona Cy' +ORDER BY [o].[Id], [o0].[ClientId], [o0].[Id]"); + } + + public override async Task GroupBy_with_multiple_aggregates_on_owned_navigation_properties(bool async) + { + await base.GroupBy_with_multiple_aggregates_on_owned_navigation_properties(async); + + AssertSql( + @"SELECT AVG(CAST([s].[Id] AS float)) AS [p1], COALESCE(SUM([s].[Id]), 0) AS [p2], MAX(CAST(LEN([s].[Name]) AS int)) AS [p3] +FROM ( + SELECT 1 AS [Key], [o].[PersonAddress_Country_PlanetId] + FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +) AS [t] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON [t].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [p].[StarId] = [s].[Id] +GROUP BY [t].[Key]"); + } + + public override async Task Ordering_by_identifying_projection(bool async) + { + await base.Ordering_by_identifying_projection(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +ORDER BY [o].[PersonAddress_PlaceType], [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Using_from_sql_on_owner_generates_join_with_table_for_owned_shared_dependents(bool async) + { + await base.Using_from_sql_on_owner_generates_join_with_table_for_owned_shared_dependents(async); + + AssertSql( + @"SELECT [m].[Id], [m].[Discriminator], [m].[Name], [m].[PeriodEnd], [m].[PeriodStart], [t].[Id], [t].[Id0], [t0].[Id], [t0].[Id0], [t2].[Id], [t2].[Id0], [t4].[Id], [t4].[Id0], [t6].[ClientId], [t6].[Id], [t6].[OrderDate], [t6].[PeriodEnd], [t6].[PeriodStart], [t6].[OrderClientId], [t6].[OrderId], [t6].[Id0], [t6].[Detail], [t6].[PeriodEnd0], [t6].[PeriodStart0], [t].[PersonAddress_AddressLine], [t].[PeriodEnd], [t].[PeriodStart], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[Id1], [t].[PersonAddress_Country_Name], [t].[PeriodEnd0], [t].[PeriodStart0], [t].[PersonAddress_Country_PlanetId], [t0].[BranchAddress_BranchName], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[BranchAddress_PlaceType], [t0].[Id1], [t0].[BranchAddress_Country_Name], [t0].[PeriodEnd0], [t0].[PeriodStart0], [t0].[BranchAddress_Country_PlanetId], [t2].[LeafBAddress_LeafBType], [t2].[PeriodEnd], [t2].[PeriodStart], [t2].[LeafBAddress_PlaceType], [t2].[Id1], [t2].[LeafBAddress_Country_Name], [t2].[PeriodEnd0], [t2].[PeriodStart0], [t2].[LeafBAddress_Country_PlanetId], [t4].[LeafAAddress_LeafType], [t4].[PeriodEnd], [t4].[PeriodStart], [t4].[LeafAAddress_PlaceType], [t4].[Id1], [t4].[LeafAAddress_Country_Name], [t4].[PeriodEnd0], [t4].[PeriodStart0], [t4].[LeafAAddress_Country_PlanetId] +FROM ( + SELECT * FROM ""OwnedPerson"" +) AS [m] +LEFT JOIN ( + SELECT [o].[Id], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o0].[Id] AS [Id0], [o].[Id] AS [Id1], [o].[PersonAddress_Country_Name], [o].[PeriodEnd] AS [PeriodEnd0], [o].[PeriodStart] AS [PeriodStart0], [o].[PersonAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o] + INNER JOIN [OwnedPerson] AS [o0] ON [o].[Id] = [o0].[Id] + WHERE [o].[PersonAddress_ZipCode] IS NOT NULL +) AS [t] ON [m].[Id] = CASE + WHEN [t].[PersonAddress_ZipCode] IS NOT NULL THEN [t].[Id] +END +LEFT JOIN ( + SELECT [o1].[Id], [o1].[BranchAddress_BranchName], [o1].[PeriodEnd], [o1].[PeriodStart], [o1].[BranchAddress_PlaceType], [t1].[Id] AS [Id0], [o1].[Id] AS [Id1], [o1].[BranchAddress_Country_Name], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0], [o1].[BranchAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o1] + INNER JOIN ( + SELECT [o2].[Id] + FROM [OwnedPerson] AS [o2] + WHERE [o2].[Discriminator] IN (N'Branch', N'LeafA') + ) AS [t1] ON [o1].[Id] = [t1].[Id] + WHERE [o1].[BranchAddress_BranchName] IS NOT NULL +) AS [t0] ON [m].[Id] = CASE + WHEN [t0].[BranchAddress_BranchName] IS NOT NULL THEN [t0].[Id] +END +LEFT JOIN ( + SELECT [o3].[Id], [o3].[LeafBAddress_LeafBType], [o3].[PeriodEnd], [o3].[PeriodStart], [o3].[LeafBAddress_PlaceType], [t3].[Id] AS [Id0], [o3].[Id] AS [Id1], [o3].[LeafBAddress_Country_Name], [o3].[PeriodEnd] AS [PeriodEnd0], [o3].[PeriodStart] AS [PeriodStart0], [o3].[LeafBAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o3] + INNER JOIN ( + SELECT [o4].[Id] + FROM [OwnedPerson] AS [o4] + WHERE [o4].[Discriminator] = N'LeafB' + ) AS [t3] ON [o3].[Id] = [t3].[Id] + WHERE [o3].[LeafBAddress_LeafBType] IS NOT NULL +) AS [t2] ON [m].[Id] = CASE + WHEN [t2].[LeafBAddress_LeafBType] IS NOT NULL THEN [t2].[Id] +END +LEFT JOIN ( + SELECT [o5].[Id], [o5].[LeafAAddress_LeafType], [o5].[PeriodEnd], [o5].[PeriodStart], [o5].[LeafAAddress_PlaceType], [t5].[Id] AS [Id0], [o5].[Id] AS [Id1], [o5].[LeafAAddress_Country_Name], [o5].[PeriodEnd] AS [PeriodEnd0], [o5].[PeriodStart] AS [PeriodStart0], [o5].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o5] + INNER JOIN ( + SELECT [o6].[Id] + FROM [OwnedPerson] AS [o6] + WHERE [o6].[Discriminator] = N'LeafA' + ) AS [t5] ON [o5].[Id] = [t5].[Id] + WHERE [o5].[LeafAAddress_LeafType] IS NOT NULL +) AS [t4] ON [m].[Id] = CASE + WHEN [t4].[LeafAAddress_LeafType] IS NOT NULL THEN [t4].[Id] +END +LEFT JOIN ( + SELECT [o7].[ClientId], [o7].[Id], [o7].[OrderDate], [o7].[PeriodEnd], [o7].[PeriodStart], [o8].[OrderClientId], [o8].[OrderId], [o8].[Id] AS [Id0], [o8].[Detail], [o8].[PeriodEnd] AS [PeriodEnd0], [o8].[PeriodStart] AS [PeriodStart0] + FROM [Order] AS [o7] + LEFT JOIN [OrderDetail] AS [o8] ON ([o7].[ClientId] = [o8].[OrderClientId]) AND ([o7].[Id] = [o8].[OrderId]) +) AS [t6] ON [m].[Id] = [t6].[ClientId] +ORDER BY [m].[Id], [t].[Id], [t].[Id0], [t0].[Id], [t0].[Id0], [t2].[Id], [t2].[Id0], [t4].[Id], [t4].[Id0], [t6].[ClientId], [t6].[Id], [t6].[OrderClientId], [t6].[OrderId]"); + } + + public override async Task Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(bool async) + { + await base.Projecting_collection_correlated_with_keyless_entity_after_navigation_works_using_parent_identifiers(async); + + AssertSql( + @"SELECT [b].[Throned_Value], [f].[Id], [b].[Id], [p].[Id], [p].[Name], [p].[PeriodEnd], [p].[PeriodStart], [p].[StarId] +FROM [Fink] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [f] +LEFT JOIN [Barton] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [b] ON [f].[BartonId] = [b].[Id] +LEFT JOIN [Planet] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [p] ON ([b].[Throned_Value] <> [p].[Id]) OR [b].[Throned_Value] IS NULL +ORDER BY [f].[Id], [b].[Id]"); + } + + public override async Task Filter_on_indexer_using_closure(bool async) + { + await base.Filter_on_indexer_using_closure(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PeriodEnd], [o].[PeriodStart], [t].[ClientId], [t].[Id], [t].[OrderDate], [t].[PeriodEnd], [t].[PeriodStart], [t].[OrderClientId], [t].[OrderId], [t].[Id0], [t].[Detail], [t].[PeriodEnd0], [t].[PeriodStart0], [o].[PersonAddress_AddressLine], [o].[PeriodEnd], [o].[PeriodStart], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId] +FROM [OwnedPerson] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o] +LEFT JOIN ( + SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o0].[PeriodEnd], [o0].[PeriodStart], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail], [o1].[PeriodEnd] AS [PeriodEnd0], [o1].[PeriodStart] AS [PeriodStart0] + FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] + LEFT JOIN [OrderDetail] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o1] ON ([o0].[ClientId] = [o1].[OrderClientId]) AND ([o0].[Id] = [o1].[OrderId]) +) AS [t] ON [o].[Id] = [t].[ClientId] +WHERE [o].[PersonAddress_ZipCode] = 38654 +ORDER BY [o].[Id], [t].[ClientId], [t].[Id], [t].[OrderClientId], [t].[OrderId]"); + } + + public override async Task Query_loads_reference_nav_automatically_in_projection(bool async) + { + await base.Query_loads_reference_nav_automatically_in_projection(async); + + AssertSql( + @"SELECT TOP(2) [b].[Id], [b].[PeriodEnd], [b].[PeriodStart], [b].[Simple], [b].[Throned_Property], [b].[Throned_Value] +FROM [Fink] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [f] +LEFT JOIN [Barton] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [b] ON [f].[BartonId] = [b].[Id]"); + } + + public override async Task Simple_query_entity_with_owned_collection(bool async) + { + await base.Simple_query_entity_with_owned_collection(async); + + AssertSql( + @"SELECT [s].[Id], [s].[Name], [s].[PeriodEnd], [s].[PeriodStart], [e].[Id], [e].[Name], [e].[PeriodEnd], [e].[PeriodStart], [e].[StarId] +FROM [Star] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] +LEFT JOIN [Element] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e] ON [s].[Id] = [e].[StarId] +ORDER BY [s].[Id]"); + } + + // not AssertQuery so original (non-temporal) query gets executed, but data is modified + // so results don't match expectations + public override Task Owned_entity_without_owner_does_not_throw_for_identity_resolution(bool async, bool useAsTracking) + => Task.CompletedTask; + + public override Task Preserve_includes_when_applying_skip_take_after_anonymous_type_select(bool async) + => Task.CompletedTask; + + public override Task Query_on_collection_entry_works_for_owned_collection(bool async) + => Task.CompletedTask; + + public override Task NoTracking_Include_with_cycles_does_not_throw_when_performing_identity_resolution( + bool async, + bool useAsTracking) + => Task.CompletedTask; + + public override Task Filter_on_indexer_using_function_argument(bool async) + => Task.CompletedTask; + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + public class TemporalOwnedQuerySqlServerFixture : RelationalOwnedQueryFixture + { + protected override ITestStoreFactory TestStoreFactory + => SqlServerTestStoreFactory.Instance; + + protected override string StoreName { get; } = "TemporalOwnedQueryTest"; + + public DateTime ChangesDate { get; private set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.Entity( + eb => + { + eb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + eb.IndexerProperty("Name"); + var ownedPerson = new OwnedPerson { Id = 1 }; + ownedPerson["Name"] = "Mona Cy"; + eb.HasData(ownedPerson); + + eb.OwnsOne( + p => p.PersonAddress, ab => + { + ab.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + ab.IndexerProperty("AddressLine"); + ab.IndexerProperty(typeof(int), "ZipCode"); + ab.HasData( + new + { + OwnedPersonId = 1, + PlaceType = "Land", + AddressLine = "804 S. Lakeshore Road", + ZipCode = 38654 + }, + new + { + OwnedPersonId = 2, + PlaceType = "Land", + AddressLine = "7 Church Dr.", + ZipCode = 28655 + }, + new + { + OwnedPersonId = 3, + PlaceType = "Land", + AddressLine = "72 Hickory Rd.", + ZipCode = 07728 + }, + new + { + OwnedPersonId = 4, + PlaceType = "Land", + AddressLine = "28 Strawberry St.", + ZipCode = 19053 + }); + + ab.OwnsOne( + a => a.Country, cb => + { + cb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + cb.HasData( + new + { + OwnedAddressOwnedPersonId = 1, + PlanetId = 1, + Name = "USA" + }, + new + { + OwnedAddressOwnedPersonId = 2, + PlanetId = 1, + Name = "USA" + }, + new + { + OwnedAddressOwnedPersonId = 3, + PlanetId = 1, + Name = "USA" + }, + new + { + OwnedAddressOwnedPersonId = 4, + PlanetId = 1, + Name = "USA" + }); + + cb.HasOne(cc => cc.Planet).WithMany().HasForeignKey(ee => ee.PlanetId) + .OnDelete(DeleteBehavior.Restrict); + }); + }); + + eb.OwnsMany( + p => p.Orders, ob => + { + ob.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + ob.IndexerProperty("OrderDate"); + ob.HasData( + new + { + Id = -10, + ClientId = 1, + OrderDate = Convert.ToDateTime("2018-07-11 10:01:41") + }, + new + { + Id = -11, + ClientId = 1, + OrderDate = Convert.ToDateTime("2015-03-03 04:37:59") + }, + new + { + Id = -20, + ClientId = 2, + OrderDate = Convert.ToDateTime("2015-05-25 20:35:48") + }, + new + { + Id = -30, + ClientId = 3, + OrderDate = Convert.ToDateTime("2014-11-10 04:32:42") + }, + new + { + Id = -40, + ClientId = 4, + OrderDate = Convert.ToDateTime("2016-04-25 19:23:56") + } + ); + + ob.OwnsMany(e => e.Details, odb => + { + odb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + odb.HasData( + new + { + Id = -100, + OrderId = -10, + OrderClientId = 1, + Detail = "Discounted Order" + }, + new + { + Id = -101, + OrderId = -10, + OrderClientId = 1, + Detail = "Full Price Order" + }, + new + { + Id = -200, + OrderId = -20, + OrderClientId = 2, + Detail = "Internal Order" + }, + new + { + Id = -300, + OrderId = -30, + OrderClientId = 3, + Detail = "Bulk Order" + }); + }); + }); + }); + + modelBuilder.Entity( + eb => + { + eb.HasData(new { Id = 2, Name = "Antigonus Mitul" }); + + eb.OwnsOne( + p => p.BranchAddress, ab => + { + ab.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + ab.IndexerProperty("BranchName").IsRequired(); + ab.HasData( + new + { + BranchId = 2, + PlaceType = "Land", + BranchName = "BranchA" + }, + new + { + BranchId = 3, + PlaceType = "Land", + BranchName = "BranchB" + }); + + ab.OwnsOne( + a => a.Country, cb => + { + cb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + cb.HasData( + new + { + OwnedAddressBranchId = 2, + PlanetId = 1, + Name = "Canada" + }, + new + { + OwnedAddressBranchId = 3, + PlanetId = 1, + Name = "Canada" + }); + }); + }); + }); + + modelBuilder.Entity( + eb => + { + var leafA = new LeafA { Id = 3 }; + leafA["Name"] = "Madalena Morana"; + eb.HasData(leafA); + + eb.OwnsOne( + p => p.LeafAAddress, ab => + { + ab.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + ab.IndexerProperty("LeafType"); + + ab.HasData( + new + { + LeafAId = 3, + PlaceType = "Land", + LeafType = 1 + }); + + ab.OwnsOne( + a => a.Country, cb => + { + cb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + cb.HasOne(c => c.Planet).WithMany().HasForeignKey(c => c.PlanetId) + .OnDelete(DeleteBehavior.Restrict); + + cb.HasData( + new + { + OwnedAddressLeafAId = 3, + PlanetId = 1, + Name = "Mexico" + }); + }); + }); + }); + + modelBuilder.Entity( + eb => + { + var leafB = new LeafB { Id = 4 }; + leafB["Name"] = "Vanda Waldemar"; + eb.HasData(leafB); + + eb.OwnsOne( + p => p.LeafBAddress, ab => + { + ab.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + ab.IndexerProperty("LeafBType").IsRequired(); + ab.HasData( + new + { + LeafBId = 4, + PlaceType = "Land", + LeafBType = "Green" + }); + + ab.OwnsOne( + a => a.Country, cb => + { + cb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + cb.HasOne(c => c.Planet).WithMany().HasForeignKey(c => c.PlanetId) + .OnDelete(DeleteBehavior.Restrict); + + cb.HasData( + new + { + OwnedAddressLeafBId = 4, + PlanetId = 1, + Name = "Panama" + }); + }); + }); + }); + + modelBuilder.Entity(pb => + { + pb.ToTable(tb => tb.IsTemporal()); + pb.HasData(new Planet { Id = 1, StarId = 1, Name = "Earth" }); + }); + + modelBuilder.Entity(mb => + { + mb.ToTable(tb => tb.IsTemporal()); + mb.HasData( + new Moon + { + Id = 1, + PlanetId = 1, + Diameter = 3474 + }); + }); + + modelBuilder.Entity( + sb => + { + sb.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + sb.HasData(new Star { Id = 1, Name = "Sol" }); + sb.OwnsMany( + s => s.Composition, ob => + { + ob.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + ob.HasKey(e => e.Id); + ob.HasData( + new + { + Id = "H", + Name = "Hydrogen", + StarId = 1 + }, + new + { + Id = "He", + Name = "Helium", + StarId = 1 + }); + }); + }); + + modelBuilder.Entity( + b => + { + b.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + b.OwnsOne( + e => e.Throned, b => + { + b.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("PeriodStart").HasColumnName("PeriodStart"); + ttb.HasPeriodEnd("PeriodEnd").HasColumnName("PeriodEnd"); + })); + b.HasData( + new + { + BartonId = 1, + Property = "Property", + Value = 42 + }); + }); + b.HasData( + new Barton { Id = 1, Simple = "Simple" }, + new Barton { Id = 2, Simple = "Not" }); + }); + + modelBuilder.Entity() + .ToTable(tb => tb.IsTemporal()) + .HasData(new { Id = 1, BartonId = 1 }); + } + + protected override void Seed(PoolableDbContext context) + { + base.Seed(context); + + ChangesDate = new DateTime(2010, 1, 1); + + var ownedPeople = context.Set().AsTracking().ToList(); + foreach (var ownedPerson in ownedPeople) + { + ownedPerson["Name"] = "Modified" + ownedPerson["Name"]; + var orders = ownedPerson.Orders; + foreach (var order in orders) + { + order["OrderDate"] = new DateTime(1000, 1, 1); + var details = order.Details; + foreach (var detail in details) + { + detail.Detail = "Modified" + detail.Detail; + } + } + } + + var stars = context.Set().AsTracking().ToList(); + foreach (var star in stars) + { + star.Name = "Modified" + star.Name; + if (star.Composition.Any()) + { + foreach (var comp in star.Composition) + { + comp.Name = "Modified" + comp.Name; + } + } + } + + var planets = context.Set().AsTracking().ToList(); + foreach (var planet in planets) + { + planet.Name = "Modified" + planet.Name; + } + + var moons = context.Set().AsTracking().ToList(); + foreach (var moon in moons) + { + moon.Diameter += 1000; + } + + var finks = context.Set().AsTracking().ToList(); + context.Set().RemoveRange(finks); + + var bartons = context.Set().Include(x => x.Throned).AsTracking().ToList(); + foreach (var barton in bartons) + { + barton.Simple = "Modified" + barton.Simple; + if (barton.Throned != null) + { + barton.Throned.Property = "Modified" + barton.Throned.Property; + } + } + + context.SaveChanges(); + + var tableNames = new List + { + nameof(Barton), + nameof(Element), + nameof(Fink), + nameof(Moon), + nameof(Order), + nameof(OrderDetail), + nameof(OwnedPerson), + nameof(Planet), + nameof(Star), + }; + + foreach (var tableName in tableNames) + { + context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] SET (SYSTEM_VERSIONING = OFF)"); + context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] DROP PERIOD FOR SYSTEM_TIME"); + + context.Database.ExecuteSqlRaw($"UPDATE [{tableName + "History"}] SET PeriodStart = '2000-01-01T01:00:00.0000000Z'"); + context.Database.ExecuteSqlRaw($"UPDATE [{tableName + "History"}] SET PeriodEnd = '2020-07-01T07:00:00.0000000Z'"); + + context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] ADD PERIOD FOR SYSTEM_TIME ([PeriodStart], [PeriodEnd])"); + context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[{tableName + "History"}]))"); + } + } + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs new file mode 100644 index 00000000000..38fba7261a0 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs @@ -0,0 +1,300 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Query +{ + [SqlServerCondition(SqlServerCondition.SupportsTemporalTablesCascadeDelete)] + public class TemporalTableSqlServerTest : NonSharedModelTestBase + { + protected override string StoreName => "TemporalTableSqlServerTest"; + + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + + protected void AssertSql(params string[] expected) => TestSqlLoggerFactory.AssertBaseline(expected); + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_basic(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities.TemporalAsOf(date); + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + AssertSql( + @"SELECT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime] +FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m] +LEFT JOIN [OwnedEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [o] ON [m].[Id] = [o].[MainEntityId]"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_join(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities + .TemporalAsOf(date) + .Join(context.MainEntities, o => o.Id, i => i.Id, (o, i) => new { o, i }); + + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + AssertSql( + @"SELECT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime], [m0].[Id], [m0].[Description], [m0].[EndTime], [m0].[StartTime], [o0].[MainEntityId], [o0].[Description], [o0].[EndTime], [o0].[StartTime] +FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m] +INNER JOIN [MainEntity] AS [m0] ON [m].[Id] = [m0].[Id] +LEFT JOIN [OwnedEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [o] ON [m].[Id] = [o].[MainEntityId] +LEFT JOIN [OwnedEntity] AS [o0] ON [m0].[Id] = [o0].[MainEntityId]"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_set_operation(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities + .TemporalAsOf(date) + .Union(context.MainEntities.TemporalAsOf(date)); + + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + AssertSql( + @"SELECT [t].[Id], [t].[Description], [t].[EndTime], [t].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime] +FROM ( + SELECT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime] + FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m] + UNION + SELECT [m0].[Id], [m0].[Description], [m0].[EndTime], [m0].[StartTime] + FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m0] +) AS [t] +LEFT JOIN [OwnedEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [o] ON [t].[Id] = [o].[MainEntityId]"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_FromSql(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities.FromSqlRaw( + @"SELECT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime] +FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m]"); + + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + // just making sure we don't do anything weird here - there is no way to extract temporal information + // from the FromSql so owned entity will always be treated as a regular query + AssertSql( + @"SELECT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime] +FROM ( + SELECT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime] + FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m] +) AS [m] +LEFT JOIN [OwnedEntity] AS [o] ON [m].[Id] = [o].[MainEntityId]"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_subquery(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities + .TemporalAsOf(date) + .Distinct() + .OrderByDescending(x => x.Id) + .Take(3); + + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + AssertSql( + @"@__p_0='3' + +SELECT [t0].[Id], [t0].[Description], [t0].[EndTime], [t0].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime] +FROM ( + SELECT TOP(@__p_0) [t].[Id], [t].[Description], [t].[EndTime], [t].[StartTime] + FROM ( + SELECT DISTINCT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime] + FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m] + ) AS [t] + ORDER BY [t].[Id] DESC +) AS [t0] +LEFT JOIN [OwnedEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [o] ON [t0].[Id] = [o].[MainEntityId] +ORDER BY [t0].[Id] DESC"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_complex(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities.TemporalAsOf(date) + .Join(context.MainEntities, x => x.Id, x => x.Id, (o, i) => new { o, i }) + .Distinct().OrderByDescending(x => x.o.Id).Take(3) + .Join(context.MainEntities, xx => xx.o.Id, x => x.Id, (o, i) => new { o, i }); + + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + AssertSql( + @"@__p_0='3' + +SELECT [t0].[Id], [t0].[Description], [t0].[EndTime], [t0].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime], [t0].[Id0], [t0].[Description0], [t0].[EndTime0], [t0].[StartTime0], [o0].[MainEntityId], [o0].[Description], [o0].[EndTime], [o0].[StartTime], [m1].[Id], [m1].[Description], [m1].[EndTime], [m1].[StartTime], [o1].[MainEntityId], [o1].[Description], [o1].[EndTime], [o1].[StartTime] +FROM ( + SELECT TOP(@__p_0) [t].[Id], [t].[Description], [t].[EndTime], [t].[StartTime], [t].[Id0], [t].[Description0], [t].[EndTime0], [t].[StartTime0] + FROM ( + SELECT DISTINCT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime], [m0].[Id] AS [Id0], [m0].[Description] AS [Description0], [m0].[EndTime] AS [EndTime0], [m0].[StartTime] AS [StartTime0] + FROM [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m] + INNER JOIN [MainEntity] AS [m0] ON [m].[Id] = [m0].[Id] + ) AS [t] + ORDER BY [t].[Id] DESC +) AS [t0] +INNER JOIN [MainEntity] AS [m1] ON [t0].[Id] = [m1].[Id] +LEFT JOIN [OwnedEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [o] ON [t0].[Id] = [o].[MainEntityId] +LEFT JOIN [OwnedEntity] AS [o0] ON [t0].[Id0] = [o0].[MainEntityId] +LEFT JOIN [OwnedEntity] AS [o1] ON [m1].[Id] = [o1].[MainEntityId] +ORDER BY [t0].[Id] DESC"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_complex_with_nontrivial_alias(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var date = new DateTime(2000, 1, 1); + + var query = context.MainEntities + .Join(context.MainEntities.TemporalAsOf(date), x => x.Id, x => x.Id, (o, i) => new { o, i }) + .Distinct().OrderByDescending(x => x.o.Id).Take(3) + .Join(context.MainEntities, xx => xx.o.Id, x => x.Id, (o, i) => new { o, i }); + + var _ = async ? await query.ToListAsync() : query.ToList(); + } + + AssertSql( + @"@__p_0='3' + +SELECT [t0].[Id], [t0].[Description], [t0].[EndTime], [t0].[StartTime], [o].[MainEntityId], [o].[Description], [o].[EndTime], [o].[StartTime], [t0].[Id0], [t0].[Description0], [t0].[EndTime0], [t0].[StartTime0], [o0].[MainEntityId], [o0].[Description], [o0].[EndTime], [o0].[StartTime], [m1].[Id], [m1].[Description], [m1].[EndTime], [m1].[StartTime], [o1].[MainEntityId], [o1].[Description], [o1].[EndTime], [o1].[StartTime] +FROM ( + SELECT TOP(@__p_0) [t].[Id], [t].[Description], [t].[EndTime], [t].[StartTime], [t].[Id0], [t].[Description0], [t].[EndTime0], [t].[StartTime0] + FROM ( + SELECT DISTINCT [m].[Id], [m].[Description], [m].[EndTime], [m].[StartTime], [m0].[Id] AS [Id0], [m0].[Description] AS [Description0], [m0].[EndTime] AS [EndTime0], [m0].[StartTime] AS [StartTime0] + FROM [MainEntity] AS [m] + INNER JOIN [MainEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [m0] ON [m].[Id] = [m0].[Id] + ) AS [t] + ORDER BY [t].[Id] DESC +) AS [t0] +INNER JOIN [MainEntity] AS [m1] ON [t0].[Id] = [m1].[Id] +LEFT JOIN [OwnedEntity] AS [o] ON [t0].[Id] = [o].[MainEntityId] +LEFT JOIN [OwnedEntity] FOR SYSTEM_TIME AS OF '2000-01-01T00:00:00.0000000' AS [o0] ON [t0].[Id0] = [o0].[MainEntityId] +LEFT JOIN [OwnedEntity] AS [o1] ON [m1].[Id] = [o1].[MainEntityId] +ORDER BY [t0].[Id] DESC"); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Temporal_owned_range_operation_negative(bool async) + { + var contextFactory = await InitializeAsync(); + using (var context = contextFactory.CreateContext()) + { + var message = async + ? (await Assert.ThrowsAsync( + () => context.MainEntities.TemporalAll().ToListAsync())).Message + : Assert.Throws(() => context.MainEntities.TemporalAll().ToList()).Message; + + Assert.Equal( + SqlServerStrings.TemporalOwnedTypeMappedToDifferentTableOnlySupportedForAsOf("AsOf"), + message); + } + } + + public class MainEntity + { + public int Id { get; set; } + public string Description { get; set; } + public OwnedEntity OwnedEntity { get; set; } + } + + public class OwnedEntity + { + public string Description { get; set; } + } + + public class MyContext26451 : DbContext + { + public MyContext26451(DbContextOptions options) + : base(options) + { + } + + public DbSet MainEntities { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().ToTable("MainEntity", tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("StartTime"); + ttb.HasPeriodEnd("EndTime"); + ttb.UseHistoryTable("ConfHistory"); + })); + modelBuilder.Entity().Property(me => me.Id).UseIdentityColumn(); + modelBuilder.Entity().OwnsOne(me => me.OwnedEntity).WithOwner(); + modelBuilder.Entity().OwnsOne(me => me.OwnedEntity, oe => + { + oe.ToTable("OwnedEntity", tb => tb.IsTemporal(ttb => + { + ttb.HasPeriodStart("StartTime"); + ttb.HasPeriodEnd("EndTime"); + ttb.UseHistoryTable("OwnedEntityHistory"); + })); + }); + } + } + } +} diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 26a1ef72333..c12cb9d55b5 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -710,21 +710,22 @@ public void Temporal_period_property_must_be_in_shadow_state() VerifyError(SqlServerStrings.TemporalPeriodPropertyMustBeInShadowState(nameof(Human), "DateOfBirth"), modelBuilder); } - [ConditionalFact] - public void Temporal_period_property_must_non_nullable_datetime() - { - var modelBuilder1 = CreateConventionalModelBuilder(); - modelBuilder1.Entity().Property(typeof(DateTime?), "Start"); - modelBuilder1.Entity().ToTable(tb => tb.IsTemporal(ttb => ttb.HasPeriodStart("Start"))); + // TODO: move to model builder tests + //[ConditionalFact] + //public void Temporal_period_property_must_non_nullable_datetime() + //{ + // var modelBuilder1 = CreateConventionalModelBuilder(); + // modelBuilder1.Entity().Property(typeof(DateTime?), "Start"); + // modelBuilder1.Entity().ToTable(tb => tb.IsTemporal(ttb => ttb.HasPeriodStart("Start"))); - VerifyError(SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime(nameof(Dog), "Start", nameof(DateTime)), modelBuilder1); + // VerifyError(SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime(nameof(Dog), "Start", nameof(DateTime)), modelBuilder1); - var modelBuilder2 = CreateConventionalModelBuilder(); - modelBuilder2.Entity().Property(typeof(int), "Start"); - modelBuilder2.Entity().ToTable(tb => tb.IsTemporal(ttb => ttb.HasPeriodStart("Start"))); + // var modelBuilder2 = CreateConventionalModelBuilder(); + // modelBuilder2.Entity().Property(typeof(int), "Start"); + // modelBuilder2.Entity().ToTable(tb => tb.IsTemporal(ttb => ttb.HasPeriodStart("Start"))); - VerifyError(SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime(nameof(Dog), "Start", nameof(DateTime)), modelBuilder2); - } + // VerifyError(SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime(nameof(Dog), "Start", nameof(DateTime)), modelBuilder2); + //} [ConditionalFact] public void Temporal_period_property_must_be_mapped_to_datetime2() @@ -781,16 +782,16 @@ public void Temporal_doesnt_work_on_TPH() VerifyError(SqlServerStrings.TemporalOnlySupportedForTPH(nameof(Animal)), modelBuilder); } - [ConditionalFact] - public void Temporal_doesnt_work_on_table_splitting() - { - var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity().ToTable("Splitting", tb => tb.IsTemporal()); - modelBuilder.Entity().ToTable("Splitting", tb => tb.IsTemporal()); - modelBuilder.Entity().HasOne(x => x.Details).WithOne().HasForeignKey(x => x.Id); + //[ConditionalFact] + //public void Temporal_doesnt_work_on_table_splitting() + //{ + // var modelBuilder = CreateConventionalModelBuilder(); + // modelBuilder.Entity().ToTable("Splitting", tb => tb.IsTemporal()); + // modelBuilder.Entity().ToTable("Splitting", tb => tb.IsTemporal()); + // modelBuilder.Entity().HasOne(x => x.Details).WithOne().HasForeignKey(x => x.Id); - VerifyError(SqlServerStrings.TemporalNotSupportedForTableSplitting("Splitting"), modelBuilder); - } + // VerifyError(SqlServerStrings.TemporalNotSupportedForTableSplitting("Splitting"), modelBuilder); + //} public class Human {