Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL Server temporal tables #4693

Closed
Tracked by #24107
shivajiraot opened this issue Mar 3, 2016 · 113 comments
Closed
Tracked by #24107

SQL Server temporal tables #4693

shivajiraot opened this issue Mar 3, 2016 · 113 comments
Assignees
Labels
area-migrations area-query area-sqlserver closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Milestone

Comments

@shivajiraot
Copy link

shivajiraot commented Mar 3, 2016

Does EF Core support code first approach for Temporal tables?
Looking to find a way to use Temporal tables with EF Core code first approach. Please guide me to the article if any existing already.
Thank you.

@rowanmiller
Copy link
Contributor

For the most part, the fact that a table is temporal is opaque to the data layer - so from that point of view you should be fine to connect to an existing database with temporal tables.

Here are some limitations though, that we could address in EF:

  • If you wanted to query a past state of the table you would need to use a raw SQL query.
  • There is no way to make a table temporal in the model/migrations. So if you are using EF to create the database then you would need to hand edit the migration and use Sql(string) to make the table temporal.

@rowanmiller rowanmiller changed the title Temporal Tables Support Temporal Tables Mar 8, 2016
@rowanmiller rowanmiller added this to the Backlog milestone Mar 8, 2016
@DavidBainbridgeRIT
Copy link

I second this as important. Temporal tables are fantastic. They cut out a lot of boiler plate but I need that information in the front end just as easily as in the back end.

@R00iBaard
Copy link

Agree, I would love to be able to set this up all via EF code first.

@reberinformatik
Copy link

👍 See also #2229

@lucasmaj
Copy link

lucasmaj commented Sep 28, 2016

I was absolutely bummed out to find out that currently there is no obvious way to query past state using lambda expressions. Is there any way to sneak in "AS OF <date_time>" into a query? (in EF6)

@divega
Copy link
Contributor

divega commented Sep 28, 2016

@lucasmaj have you tried raw SQL queries as a workaround as explained above? E.g. in EF6 use SqlQuery() and FromSql() with EF Core. The latter is composable in LINQ.

@lucasmaj
Copy link

I haven't tried raw SQL. If EF6 had SqlQuery composable with LINQ it would do the trick for now. There is no way I would go with pure sql queries and abandon LINQ. Unless there could be a way for LINQ to pure sql back to entities in an automatic fashion.

Is there really no way to augment conversion of Expressions to SQL string so that some custom expression would translate to "AS OF" etc?

Thank you nonetheless.

@sfgadjo
Copy link

sfgadjo commented Mar 22, 2017

I look forward to full support of Temporal Tables in an upcoming release. In the meantime, I am trying to make use of the SysStartTime/SysEndTime columns added to the table in the model (using CodeFirst), and I find that EF is trying to write to those columns even when I have the model property annotated with DatabaseGenerated(DatabaseGeneratedOption.Computed). Is there a way to get around this issue so that I can at least use those columns added for versioning without writing SQL?

@R00iBaard
Copy link

Any update on this? It would be very nice to have this 👍

@ajcvickers
Copy link
Member

@R00iBaard This issue is in the Backlog milestone. This means that it is not going to happen for the 2.0 release. We will re-assess the backlog following the 2.0 release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

@ntimmerman
Copy link

+1, I would like to use this in the very near future for a new enterprise doc automation app that requires an audit history on every screen. Does anyone have any more information about the work around's for now?

@pray1997
Copy link

+1

We need it too!

Or is.there any workaround, maybe something like intercepting?

@ephos
Copy link

ephos commented Jul 5, 2017

+1 - I hope this gets prioritized after 2.0, this would be great to have without manual setup!

@chassq
Copy link

chassq commented Aug 22, 2017

+1

3 similar comments
@kliebenberg
Copy link

+1

@angularDevd
Copy link

+1

@cSteusloff
Copy link

+1

@squeakyD
Copy link

squeakyD commented Oct 4, 2017

If you added support for interceptors (like in EF6), then it would be possible to handle inserts and updates to temporal tables by handling the generated fields.

@smitpatel
Copy link
Member

@squeakyD - At present you can configure your model with generated columns to insert/update data in temporal table. Main and most useful functionality would be the query pipeline especially generating SQL with between predicate etc.

@binarypatrick
Copy link

+1, would love to see the ability to time slice with a lambda expression.

@sifordtj
Copy link

+1. Would also love to see the ability to query data with lambda ... something that gives me the FOR SYSTEM_TIME without have to use a raw SQL string.

@israellot
Copy link

+1

1 similar comment
@forest-devil
Copy link

+1

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Dec 15, 2017

Please vote using the 👍 reaction on the very first post, otherwise it won't affect the order in the issue list https://github.com/aspnet/EntityFrameworkCore/issues?q=is%3Aopen+is%3Aissue+label%3Atype-enhancement+sort%3Areactions-%2B1-desc

@smitpatel
Copy link
Member

Please vote using the 👍 reaction on the very first post

corrected a bit.

@jzabroski
Copy link

@smitpatel One thing to be cautious of, is to write integration tests that minimize the likelihood of the following error:

System.Data.SqlClient.SqlException: Data modification failed on system-versioned table 'WorldWideImporters.Sales.Customers' because transaction time was earlier than period start time for affected records.

@rosenbjerg
Copy link

Just came across this Adam-Langley/efcore-temporal-query.
I like the way temporality it integrated through IQueryable.AsOf(..), and there might be other cool ideas there

@jzabroski
Copy link

@maumar Additionally, after discussing with my team, supporting code-first migrations is trickier for temporal tables, under the following scenarios:

  1. Adding a new field to a table that has temporal table backed history
  2. Changing an existing column from nullable to non-nullable - how do you specify a default value for a historical table? System versioning will need to be temporarily disabled to "patch" the table.
  3. If MigrationBuilder.RenameColumn uses sp_rename, renaming a column may not be guaranteed to work under certain circumstances I documented on Azure Voice,due to bugs in sp_rename.

@smitpatel
Copy link
Member

@jzabroski - Thanks for bringing that to attention. It would certainly need to be taken care if we allow any kind of modification over history tables directly.

@jzabroski
Copy link

The way I see it, altering a column for a table that has a temporal table hacked history should have a consequent of cascading the change to the history table. In my FluentMigrator project, I don't achieve that (yet) but instead just decompose the problem into several recipes - not super elegant in that it requires careful typing, but also successful nonetheless:

            this.DisableSystemVersioning("dbo", "Foo");

            Rename.Column("Unit_Price").OnTable("Foo").InSchema("dbo")
                .To("UnitPrice");
            Rename.Column("Unit_Price").OnTable("FooHistory").InSchema("dbo")
                .To("UnitPrice");
            Delete.DefaultConstraint()
                .OnTable("Foo")
                .InSchema("dbo")
                .OnColumn("UnitPrice");
            Alter.Column("UnitPrice").OnTable("Foo").InSchema("dbo")
                .AsDecimal(28, 10)
                .NotNullable().WithDefaultValue(0);
            Alter.Column("UnitPrice").OnTable("Foo").InSchema("dbo")
                .AsDecimal(28, 10)
                .NotNullable().WithDefaultValue(0);

            this.EnableSystemVersioning("dbo", "Foo", "dbo", "FooHistory");

@jzabroski
Copy link

@smitpatel One more helpful tip based on what I've encountered adding my own temporal table support for EF6 projects. The following documentation basically implies that EFCore cannot support temporal tables on SQL Server 2016, because EFCore (the last time I checked) uses cascade delete to manage referential integrity.

See: https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-table-considerations-and-limitations?view=sql-server-2017

ON DELETE CASCADE and ON UPDATE CASCADE are not permitted on the current table. In other words, when temporal table is referencing table in the foreign key relationship (corresponding to parent_object_id in sys.foreign_keys) CASCADE options are not allowed. To work around this limitation, use application logic or after triggers to maintain consistency on delete in primary key table (corresponding to referenced_object_id in sys.foreign_keys). If primary key table is temporal and referencing table is non-temporal, there's no such limitation.

Note

This limitation applies to SQL Server 2016 only. CASCADE options are supported in SQL Database and SQL Server 2017 starting from CTP 2.0.

Our build server is still using SQL Server 2016, so this was an unexpected hiccup when I went to turn on CI for a new project.

@roji
Copy link
Member

roji commented Aug 12, 2020

@jzabroski EF Core doesn't require cascade delete - there are other options for managing referential integrity, e.g. having EF Core itself issue cascading deletes. See DeleteBehavior and the cascade delete docs (albeit a bit out of date).

@jzabroski
Copy link

@roji Unless I am mistaken, I think we are talking past each other. I must confess, I am using EF Core 3.1 in my current projects, and my understanding is that if the child entities are not loaded and I delete the parent entity, EF Core will not delete all associated child entities for me if I delete the domain root. For this use case, ON CASCADE DELETE is required. As you can see from the DeleteBehavior document you linked to, it says (emphasis mine):

Enum Name Enum Value Behavior Description
ClientCascade 4 For entities being tracked by the DbContext, dependent entities will deleted when the related principal is deleted.
If the database has been created from the model using Entity Framework Migrations or the EnsureCreated() method, then the behavior in the database is to generate an error if a foreign key constraint is violated.

To contrast this behavior with NHibernate, where you can configure the delete behavior to be something like:

BEGIN TRANSACTION
  DELETE FROM dbo.Child WHERE ParentId = @ParentId
  DELETE FROM dbo.Parent WHERE ParentId = @ParentId
COMMIT

EFCore does not support this. Anyone I know who is building real world applications with a decent amount of child record rows will want THIS behavior and not the ClientCascade behavior. The ONLY alternative that works in practice is ON CASCADE DELETE. Everything else is just theory or only viable in very small demo applications, and not at all viable for any kind of Big Data app.

@roji
Copy link
Member

roji commented Aug 14, 2020

@jzabroski you're right that EF Core doesn't currently support bulk deletes of dependents to cascade deletes - that's tracked by #18960. But it's not accurate to say that ON DELETE CASCADE is required by EF Core - many avoid it because of the SQL Server limitation on circular cascading deletes, for example.

In other words, I'm not sure this actually blocks temporal table support.

@jzabroski
Copy link

@roji It may not block it but it is a very good thing to call out in the documentation. I tend to find with ORMs that these types of configuration limitations are definitely under-specified, so helping to call them out to @smitpatel likely helps him build a better product and avoid people opening support requests for things that he can't do anything about.

@Ogglas
Copy link

Ogglas commented Sep 29, 2020

Really long wait for this. I hope this will be prioritized since it is now at the top based on label consider-for-next-release and sort on reaction 👍

image

@ajcvickers ajcvickers modified the milestones: Backlog, 6.0.0 Oct 28, 2020
@roji roji changed the title Support Temporal Tables Support temporal tables Feb 10, 2021
@maumar
Copy link
Contributor

maumar commented Jul 29, 2021

Initial support for Temporal Tables has been added here: e7c0b9d (model/metadata part) and here: 4b25a88 (query part) and will be available in the next preview (Preview 8), as well as in current nightly bits.

Usage:

Mapping entity to a temporal table can be done in the OnModelCreating, like this:

modelBuilder.Entity<MyTemporalEntity>().ToTable(tb => tb.IsTemporal());

additional configuration is also supported - history table name/schema, names for period start and period end columns

modelBuilder.Entity<MyTemporalEntity>().ToTable(tb => tb.IsTemporal(ttb =>
{
    ttb.HasPeriodStart("SystemTimeStart");
    ttb.HasPeriodEnd("SystemTimeEnd");
    ttb.WithHistoryTable("MyHistoryTable", "mySchema");
}));

Migrations are supported so existing entities can be converted to temporal.

Querying:

var myDate = new DateTime(2020, 1, 1);
context.MyTemporalEntities.TemporalAsOf(myDate).Where(e => e.Id < 10);

Supported operations: TemporalAsOf, TemporalAll, TemporalBetween, TemporalFromTo, TemporalContainedIn.

Some limitations and considerations

  • Queries that use temporal operations are always marked as NoTracking. Multiple entities with the same key could be returned from such queries and EF would not be able to resolve their identities properly otherwise.

  • Temporal operations are supported directly on DbSet, rather than IQueryable. In case of inheritance, they can't be applied on OfType operation. Instead, use:

context.Set<MyDerivedEntity>().TemporalAsOf(...); 
  • Navigation expansion is only supported for AsOf operation, since it's the only temporal operation that guarantees consistency of the result graph. For other temporal operations navigations must be created manually using Join.

  • When expanding navigation, the target entity must also be mapped to temporal table. Temporal operation gets propagated from source to the target. Navigating from temporal to non-temporal entity is not supported.

context.Customers.TemporalAsOf(new DateTime(2020, 1, 1)).Select(c => c.Orders)

will return customers and their orders as of Jan 1st 2020. Temporal operation gets applied to customers AND orders automatically.

Note:
This thread is already very long, for any feedback, questions, bug reports etc. please go here as a starting point: dotnet/EntityFramework.Docs#3353

@maumar maumar closed this as completed Jul 29, 2021
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jul 30, 2021
@ajcvickers ajcvickers modified the milestones: 6.0.0, 6.0.0-rc1 Aug 12, 2021
@ajcvickers ajcvickers changed the title Support temporal tables SQL Server temporal tables Sep 16, 2021
@ajcvickers ajcvickers modified the milestones: 6.0.0-rc1, 6.0.0 Nov 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-migrations area-query area-sqlserver closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Projects
None yet
Development

No branches or pull requests