TemporalAll() in Temporal Tables with child entity gives System.InvalidOperationException when used with Include()
See original GitHub issueWhen using a Temporal table which has a Child Entity navigation, the TemporalAll()
gives System.InvalidOperationException
when used with Include()
Include your code
using Microsoft.EntityFrameworkCore;
using TemporalTableWithNavigation;
var options = new DbContextOptionsBuilder<TemporaltablewithnavigationContext>()
.UseSqlServer("Database=localhost;User Id=sa;Password=P@ssw0rd")
.Options;
var context = new TemporaltablewithnavigationContext(options);
//This line works
var entityWithoutNavigation = context.Employees.Include(x => x.Status).ToList();
//This gives IOE.
var entityWithNavigation = context.Employees.TemporalAll().Include(x => x.Status).ToList();
How to reproduce
- Creates a sample repo here https://github.com/iSatishYadav/TemporalTableWithNavigation, otherwise use following steps:
- Create a new Console App with .NET 6.
- Create a sample database with Temporal Table and a child table. Use following script: TemporaltablewithnavigationContext.cs
CREATE DATABASE TemporalTableWithNavigation;;
GO
USE TemporalTableWithNavigation;
GO
CREATE TABLE [dbo].[Statuses]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY,
[Description] NVARCHAR(10) NOT NULL
)
GO
CREATE TABLE [dbo].[Employees] (
[EmployeeId] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (100) NULL,
[Position] NVARCHAR (100) NULL,
[Department] NVARCHAR (100) NULL,
[Address] NVARCHAR (1024) NULL,
[AnnualSalary] DECIMAL (10, 2) NOT NULL,
[PeriodEnd] DATETIME2 (7) GENERATED ALWAYS AS ROW END NOT NULL,
[PeriodStart] DATETIME2 (7) GENERATED ALWAYS AS ROW START NOT NULL,
[StatusId] INT NOT NULL,
CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED ([EmployeeId] ASC),
CONSTRAINT [FK_Employees_Statuses] FOREIGN KEY ([StatusId]) REFERENCES [Statuses]([Id]),
PERIOD FOR SYSTEM_TIME ([PeriodStart], [PeriodEnd])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[EmployeeHistory], DATA_CONSISTENCY_CHECK=ON));
GO
- Reverse Engineer the database to create
TemporaltablewithnavigationContext
with 2 EntitiesEmployees
andStatuses
.
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace TemporalTableWithNavigation
{
public partial class TemporaltablewithnavigationContext : DbContext
{
public TemporaltablewithnavigationContext()
{
}
public TemporaltablewithnavigationContext(DbContextOptions<TemporaltablewithnavigationContext> options)
: base(options)
{
}
public virtual DbSet<Employees> Employees { get; set; }
public virtual DbSet<Statuses> Statuses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employees>(entity =>
{
entity.HasKey(e => e.EmployeeId);
entity.ToTable(tb => tb.IsTemporal(ttb =>
{
ttb.UseHistoryTable("EmployeeHistory", "dbo");
ttb
.HasPeriodStart("PeriodStart")
.HasColumnName("PeriodStart");
ttb
.HasPeriodEnd("PeriodEnd")
.HasColumnName("PeriodEnd");
}
));
entity.Property(e => e.EmployeeId).ValueGeneratedNever();
entity.Property(e => e.Address).HasMaxLength(1024);
entity.Property(e => e.AnnualSalary).HasColumnType("decimal(10, 2)");
entity.Property(e => e.Department).HasMaxLength(100);
entity.Property(e => e.Name).HasMaxLength(100);
entity.Property(e => e.Position).HasMaxLength(100);
entity.HasOne(d => d.Status)
.WithMany(p => p.Employees)
.HasForeignKey(d => d.StatusId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Employees_Statuses");
});
modelBuilder.Entity<Statuses>(entity =>
{
entity.Property(e => e.Description)
.IsRequired()
.HasMaxLength(10);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}
Employees.cs
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using System;
using System.Collections.Generic;
namespace TemporalTableWithNavigation
{
public partial class Employees
{
public Guid EmployeeId { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public string Department { get; set; }
public string Address { get; set; }
public decimal AnnualSalary { get; set; }
public int StatusId { get; set; }
public virtual Statuses Status { get; set; }
}
}
Statuses.cs
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
using System;
using System.Collections.Generic;
namespace TemporalTableWithNavigation
{
public partial class Statuses
{
public Statuses()
{
Employees = new HashSet<Employees>();
}
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Employees> Employees { get; set; }
}
}
Include stack traces
Include the full exception message and stack trace for any exception you encounter.
Use triple-tick fences for stack traces. For example:
Unhandled exception. System.InvalidOperationException: Temporal query is trying to use navigation to an entity 'Statuses' which itself doesn't map to temporal table. Either map the entity to temporal table or use join manually to access it.
at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerNavigationExpansionExtensibilityHelper.CreateQueryRoot(IEntityType entityType, QueryRootExpression source)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ExpandingExpressionVisitor.ExpandForeignKey(Expression root, EntityReference entityReference, IForeignKey foreignKey, Boolean onDependent, Boolean derivedTypeConversion)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ExpandingExpressionVisitor.ExpandNavigation(Expression root, EntityReference entityReference, INavigation navigation, Boolean derivedTypeConversion)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.IncludeExpandingExpressionVisitor.ExpandIncludesHelper(Expression root, EntityReference entityReference, INavigationBase previousNavigation)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.IncludeExpandingExpressionVisitor.ExpandInclude(Expression root, EntityReference entityReference)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.IncludeExpandingExpressionVisitor.VisitExtension(Expression extensionExpression)
at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ExpandingExpressionVisitor.Expand(Expression expression, Boolean applyIncludes)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.PendingSelectorExpandingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.Expand(Expression query)
at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IncludableQueryable`2.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Program.<Main>$(String[] args) in ...
Provider and version information
EF Core version: 6.0.0 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 6.0 Operating system: Windows 10 IDE: Microsoft Visual Studio Enterprise 2022 (64-bit) - Current Version 17.0.0
This is my first issue, please let me know if I have missed something. Thanks!
Issue Analytics
- State:
- Created 2 years ago
- Reactions:3
- Comments:7 (2 by maintainers)
Top Results From Across the Web
What's New in EF Core 6.0
The creation of temporal tables using Migrations ... EF Core 6.0 will mark child entity types as owned by their parent entity by...
Read more >How to configure a View as Temporal
InvalidOperationException : Temporal query is trying to use navigation to an entity 'EntityResume' which itself doesn't map to temporal table.
Read more >Temporal Tables in Entity Framework Core
TemporalAll : Returns all rows in the historical data. This is typically many rows from the history table and/or the current table for...
Read more >How to use Historical Data with Temporal Tables in EF ...
With EF Core 6.0, you can directly integrate historical data in your code, query your data at a specific point in time, and...
Read more >Using Microsoft SQL Server Temporal Tables and ... - bytefish.de
This article discusses auditing with EntityFramework Core.
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
@iSatishYadav The problem with the temporal operators that return multiple instances of the same entity is that the referential constraints of the model are broken. There can be many instances of one entity referring to many instances of another entity. (Note this is different from many instances which are different entities.) This is essentially why SQL Server also removes all constraints from the history tables. So we have no plan to enable Include or other navigation expansions for the temporal operators that return many instances.
Contrast this with
TemporalAsOf
, which uses a specific point in time. In this case, the model constraints are preserved and all entity associations are in a valid state. This is why Include and other navigation expansions are supported with AsOf.What is the point of having temporal tables if we can’t query them? E.g. even something like
Set<MyEntity>().TemporalAll().Select(x => new { x.A, x.B.C })
doesn’t work.