From 36dcd84768fa74c2abbf3f26baf583283d23657b Mon Sep 17 00:00:00 2001 From: maumar Date: Wed, 3 Nov 2021 03:53:38 -0700 Subject: [PATCH 1/2] Adding support for owned types mapped to same table as well as different tables Fix to #26451 - Temporal Table: Owned Entities support Fix to #26469 - Query: enable temporal tables for table splitting scenarios Fix to #26705 - Query: model building for temporal table with explicitly defined period columns could fail if conventions are executed in a delayed fashion Just like any other nav expansion, this only works for AsOf operation. Added extensibility point to SharedTypeEntityExpandingExpressionVisitor so that we can create temporal table expressions representing owned entities, if their owner is also a temporal table expression. We also need to match TableExpression information with ITable metadata, for provider specific table-like expressions. Relaxed validation for table splitting when the table is temporal - it's now allowed as long as all (base type) entities mapped to this table have congruent period and history table mappings. Column period property is now created explicitly in the temporal table builder rather than relying on conventions to avoid scenario when conventions are delayed and the period property they are supposed to create is not ready by the time we need it. --- ...ntityFrameworkRelationalServicesBuilder.cs | 5 +- ...lationalSharedTypeEntityExpansionHelper.cs | 40 + ...yableMethodTranslatingExpressionVisitor.cs | 98 +- ...ranslatingExpressionVisitorDependencies.cs | 9 +- ...lationalSharedTypeEntityExpansionHelper.cs | 38 + ...edTypeEntityExpansionHelperDependencies.cs | 67 + .../Query/SqlExpressions/SelectExpression.cs | 84 +- .../SqlServerServiceCollectionExtensions.cs | 1 + .../Internal/SqlServerModelValidator.cs | 44 +- .../Metadata/Builders/TemporalTableBuilder.cs | 23 + .../Properties/SqlServerStrings.Designer.cs | 8 + .../Properties/SqlServerStrings.resx | 3 + ...qlServerSharedTypeEntityExpansionHelper.cs | 56 + .../Query/OwnedQueryTestBase.cs | 21 +- .../Query/OwnedQuerySqlServerTest.cs | 10 +- .../Query/QueryBugsTest.cs | 1 - .../Query/TemporalOwnedQuerySqlServerTest.cs | 1738 +++++++++++++++++ .../Query/TemporalTableSqlServerTest.cs | 300 +++ .../SqlServerModelValidatorTest.cs | 43 +- 19 files changed, 2513 insertions(+), 76 deletions(-) create mode 100644 src/EFCore.Relational/Query/IRelationalSharedTypeEntityExpansionHelper.cs create mode 100644 src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelper.cs create mode 100644 src/EFCore.Relational/Query/RelationalSharedTypeEntityExpansionHelperDependencies.cs create mode 100644 src/EFCore.SqlServer/Query/Internal/SqlServerSharedTypeEntityExpansionHelper.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/Query/TemporalTableSqlServerTest.cs 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/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/TemporalTableBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs index 7e76a765d39..522f300e88e 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs @@ -1,8 +1,10 @@ // 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.ComponentModel; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Builders { @@ -72,6 +74,7 @@ public virtual TemporalTableBuilder UseHistoryTable(string name, string? schema) public virtual TemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) { _entityType.SetPeriodStartPropertyName(propertyName); + EnsurePeriodPropertyExists(propertyName); return new TemporalPeriodPropertyBuilder(_entityType, propertyName); } @@ -88,10 +91,30 @@ public virtual TemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) public virtual TemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) { _entityType.SetPeriodEndPropertyName(propertyName); + EnsurePeriodPropertyExists(propertyName); return new TemporalPeriodPropertyBuilder(_entityType, propertyName); } + private void EnsurePeriodPropertyExists(string propertyName) + { + var property = _entityType.FindProperty(propertyName); + if (property != null && property.ClrType != typeof(DateTime)) + { + throw new InvalidOperationException( + SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime( + _entityType.DisplayName(), propertyName, nameof(DateTime))); + } + + 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/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/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 { From 3a9f52ef8dca1b6e83ef3872cf806d1e1b16ecc0 Mon Sep 17 00:00:00 2001 From: maumar Date: Wed, 17 Nov 2021 15:31:03 -0800 Subject: [PATCH 2/2] GfG --- .../RelationalEntityTypeBuilderExtensions.cs | 2 +- .../Metadata/Builders/TableBuilder.cs | 14 ++++- .../Builders/TemporalPeriodPropertyBuilder.cs | 8 +-- .../Metadata/Builders/TemporalTableBuilder.cs | 56 +++++++++---------- .../Builders/TemporalTableBuilder`.cs | 4 +- .../Metadata/Builders/EntityTypeBuilder.cs | 26 +++++++++ 6 files changed, 71 insertions(+), 39 deletions(-) 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/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.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 522f300e88e..61488dd955d 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs @@ -1,10 +1,8 @@ // 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.ComponentModel; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.SqlServer.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Builders { @@ -14,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 @@ -23,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; } /// @@ -39,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; } @@ -56,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; } @@ -73,10 +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); - EnsurePeriodPropertyExists(propertyName); + _entityTypeBuilder.Metadata.SetPeriodStartPropertyName(propertyName); + ConfigurePeriodProperty(propertyName); - return new TemporalPeriodPropertyBuilder(_entityType, propertyName); + return new TemporalPeriodPropertyBuilder(_entityTypeBuilder, propertyName); } /// @@ -90,29 +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); - EnsurePeriodPropertyExists(propertyName); + _entityTypeBuilder.Metadata.SetPeriodEndPropertyName(propertyName); + ConfigurePeriodProperty(propertyName); - return new TemporalPeriodPropertyBuilder(_entityType, propertyName); + return new TemporalPeriodPropertyBuilder(_entityTypeBuilder, propertyName); } - private void EnsurePeriodPropertyExists(string propertyName) + private void ConfigurePeriodProperty(string propertyName) { - var property = _entityType.FindProperty(propertyName); - if (property != null && property.ClrType != typeof(DateTime)) - { - throw new InvalidOperationException( - SqlServerStrings.TemporalPeriodPropertyMustBeNonNullableDateTime( - _entityType.DisplayName(), propertyName, nameof(DateTime))); - } - - if (property == null) - { - property = _entityType.AddProperty(propertyName, typeof(DateTime)); - property.SetColumnName(propertyName); - } - - property.ValueGenerated = ValueGenerated.OnAddOrUpdate; + 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/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.