question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TemporalAll() in Temporal Tables with child entity gives System.InvalidOperationException when used with Include()

See original GitHub issue

When 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

  1. Creates a sample repo here https://github.com/iSatishYadav/TemporalTableWithNavigation, otherwise use following steps:
  2. Create a new Console App with .NET 6.
  3. 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
  1. Reverse Engineer the database to create TemporaltablewithnavigationContext with 2 Entities Employees and Statuses.
// <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:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

6reactions
ajcvickerscommented, Nov 16, 2021

@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.

4reactions
Mithrascommented, Jan 21, 2022

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.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found