From b3846a8e7b36f6d2bd36e4915f721edacabf0f6d Mon Sep 17 00:00:00 2001 From: maumar Date: Wed, 14 Jul 2021 15:16:28 -0700 Subject: [PATCH] minor cleanup for temporal table feature --- ...ensions.cs => SqlServerDbSetExtensions.cs} | 6 ++-- .../SqlServerTemporalConvention.cs | 14 ++++++++ .../SqlExpressions/TemporalTableExpression.cs | 33 +++++++++++++------ .../Query/TemporalPointInTimeQueryRewriter.cs | 2 +- .../SqlServerModelBuilderGenericTest.cs | 20 +++++++++++ 5 files changed, 61 insertions(+), 14 deletions(-) rename src/EFCore.SqlServer/Extensions/{SqlServerQueryableExtensions.cs => SqlServerDbSetExtensions.cs} (98%) diff --git a/src/EFCore.SqlServer/Extensions/SqlServerQueryableExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerDbSetExtensions.cs similarity index 98% rename from src/EFCore.SqlServer/Extensions/SqlServerQueryableExtensions.cs rename to src/EFCore.SqlServer/Extensions/SqlServerDbSetExtensions.cs index dd2a9b249a9..5cea981ff43 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerQueryableExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerDbSetExtensions.cs @@ -12,9 +12,9 @@ namespace Microsoft.EntityFrameworkCore { /// - /// Sql Server database specific extension methods for LINQ queries. + /// Sql Server database specific extension methods for LINQ queries rooted in DbSet. /// - public static class SqlServerQueryableExtensions + public static class SqlServerDbSetExtensions { /// /// @@ -97,7 +97,7 @@ public static IQueryable TemporalFromTo( /// Point in time representing the end of the period for which results should be returned. /// An representing the entities present in a given time range. public static IQueryable TemporalBetween( - this IQueryable source, + this DbSet source, DateTime from, DateTime to) where TEntity : class diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerTemporalConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerTemporalConvention.cs index 6f81e36a72d..a4d3d6ac4b3 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerTemporalConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerTemporalConvention.cs @@ -36,6 +36,20 @@ public virtual void ProcessEntityTypeAnnotationChanged( { entityTypeBuilder.HasPeriodEnd(PeriodEndDefaultName); } + + foreach (var skipLevelNavigation in entityTypeBuilder.Metadata.GetSkipNavigations()) + { + if (skipLevelNavigation.DeclaringEntityType.IsTemporal() + && skipLevelNavigation.Inverse is IConventionSkipNavigation inverse + && inverse.DeclaringEntityType.IsTemporal() + && skipLevelNavigation.JoinEntityType is IConventionEntityType joinEntityType + && joinEntityType.HasSharedClrType + && !joinEntityType.IsTemporal() + && joinEntityType.GetConfigurationSource() == ConfigurationSource.Convention) + { + joinEntityType.SetIsTemporal(true); + } + } } else { diff --git a/src/EFCore.SqlServer/Query/SqlExpressions/TemporalTableExpression.cs b/src/EFCore.SqlServer/Query/SqlExpressions/TemporalTableExpression.cs index 0120b9be29e..16b836c269f 100644 --- a/src/EFCore.SqlServer/Query/SqlExpressions/TemporalTableExpression.cs +++ b/src/EFCore.SqlServer/Query/SqlExpressions/TemporalTableExpression.cs @@ -10,13 +10,20 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.SqlExpressions { /// - /// Fill in later + /// + /// An expression that represents a temporal table source in a SQL tree. + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// /// public class TemporalTableExpression : TableExpressionBase, ICloneable { /// - /// Fill in later + /// Creates a new instance of the class representing temporal 'All' operation. /// + /// A table source. public TemporalTableExpression(ITableBase table) : base(table.Name.Substring(0, 1).ToLowerInvariant()) { @@ -26,8 +33,10 @@ public TemporalTableExpression(ITableBase table) } /// - /// Fill in later + /// Creates a new instance of the class representing temporal 'AsOf' operation. /// + /// A table source. + /// Point in time. public TemporalTableExpression(ITableBase table, DateTime pointInTime) : base(table.Name.Substring(0, 1).ToLowerInvariant()) { @@ -38,8 +47,12 @@ public TemporalTableExpression(ITableBase table, DateTime pointInTime) } /// - /// Fill in later + /// Creates a new instance of the class representing temporal range operation. /// + /// A table source. + /// Start of the time range. + /// End of the time range. + /// Temporal operation type. public TemporalTableExpression(ITableBase table, DateTime from, DateTime to, TemporalOperationType temporalOperationType) : base(table.Name.Substring(0, 1).ToLowerInvariant()) { @@ -69,32 +82,32 @@ private TemporalTableExpression( } /// - /// Fill in later + /// Table schema. /// public virtual string? Schema { get; } /// - /// Fill in later + /// Table name. /// public virtual string Name { get; } /// - /// Fill in later + /// Point in time for the temporal 'AsOf' operation. /// public virtual DateTime? PointInTime { get; } /// - /// Fill in later + /// Start date for the temporal range operation. /// public virtual DateTime? From { get; } /// - /// Fill in later + /// End date for the temporal range operation. /// public virtual DateTime? To { get; } /// - /// Fill in later + /// Temporal operation type. /// public virtual TemporalOperationType TemporalOperationType { get; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalPointInTimeQueryRewriter.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalPointInTimeQueryRewriter.cs index 6ff8bd04028..d5cec2fc85c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalPointInTimeQueryRewriter.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalPointInTimeQueryRewriter.cs @@ -16,7 +16,7 @@ private static readonly MethodInfo _setMethodInfo = typeof(ISetSource).GetMethod(nameof(ISetSource.Set)); private static readonly MethodInfo _asOfMethodInfo - = typeof(SqlServerQueryableExtensions).GetMethod(nameof(SqlServerQueryableExtensions.TemporalAsOf)); + = typeof(SqlServerDbSetExtensions).GetMethod(nameof(SqlServerDbSetExtensions.TemporalAsOf)); private readonly DateTime _pointInTime; diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 619dac13446..2a0a43a4209 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -1097,6 +1097,26 @@ public virtual void Switching_from_temporal_to_non_temporal_default_settings() Assert.Equal(3, entity.GetProperties().Count()); } + [ConditionalFact] + public virtual void Implicit_many_to_many_converted_from_non_temporal_to_temporal() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(ImplicitManyToManyA)); + var joinEntity = entity.GetSkipNavigations().Single().JoinEntityType; + + Assert.True(joinEntity.IsTemporal()); + } + protected override TestModelBuilder CreateModelBuilder(Action configure = null) => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); }