Skip to content

Commit 204e115

Browse files
authored
Document temporal period properties mapped to CLR properties (#5336)
Documents dotnet/efcore#38110
1 parent 848005a commit 204e115

2 files changed

Lines changed: 106 additions & 3 deletions

File tree

entity-framework/core/providers/sql-server/temporal-tables.md

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ EXEC(N'CREATE TABLE [Employees] (
6464
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].[EmployeeHistory]))');
6565
```
6666

67-
Notice that SQL Server creates two hidden `datetime2` columns called `PeriodEnd` and `PeriodStart`. These "period columns" represent the time range during which the data in the row existed. These columns are mapped to [shadow properties](xref:core/modeling/shadow-properties) in the EF Core model, allowing them to be used in queries as shown later.
67+
Notice that SQL Server creates two hidden `datetime2` columns called `PeriodEnd` and `PeriodStart`. These "period columns" represent the time range during which the data in the row existed. By default, these columns are mapped to [shadow properties](xref:core/modeling/shadow-properties) in the EF Core model, allowing them to be used in queries as shown later. Starting with EF Core 11, period columns can also be [mapped to CLR properties](#mapping-period-columns-to-clr-properties) on your entity type.
6868

6969
> [!IMPORTANT]
7070
> The times in these columns are always UTC time generated by SQL Server. UTC times are used for all operations involving temporal tables, such as in the queries shown below.
@@ -148,7 +148,7 @@ context.SaveChanges();
148148
-->
149149
[!code-csharp[NormalQuery](../../../../samples/core/Miscellaneous/NewInEFCore6/TemporalTablesSample.cs?name=NormalQuery)]
150150

151-
Also, after a normal [tracking query](xref:core/querying/tracking#no-tracking-queries), the values from the period columns of the current data can be [accessed from the tracked entities](xref:core/change-tracking/entity-entries). For example:
151+
Also, after a normal [tracking query](xref:core/querying/tracking#no-tracking-queries), the values from the period columns of the current data can be [accessed from the tracked entities](xref:core/change-tracking/entity-entries). If the period columns are mapped to CLR properties, you can access them directly on the entity; otherwise, use `EF.Property` to access them as shadow properties. For example:
152152

153153
<!--
154154
var employees = context.Employees.ToList();
@@ -212,7 +212,7 @@ foreach (var pointInTime in history)
212212
-->
213213
[!code-csharp[TemporalAll](../../../../samples/core/Miscellaneous/NewInEFCore6/TemporalTablesSample.cs?name=TemporalAll)]
214214

215-
Notice how the [EF.Property method](xref:core/modeling/shadow-properties#accessing-shadow-properties) can be used to access values from the period columns. This is used in the `OrderBy` clause to sort the data, and then in a projection to include these values in the returned data.
215+
Notice how the [EF.Property method](xref:core/modeling/shadow-properties#accessing-shadow-properties) can be used to access values from the period columns. This is used in the `OrderBy` clause to sort the data, and then in a projection to include these values in the returned data. If the period columns are [mapped to CLR properties](#mapping-period-columns-to-clr-properties), you can reference them directly in the query instead of using `EF.Property`.
216216

217217
This query brings back the following data:
218218

@@ -280,3 +280,62 @@ Historical data for Rainbow Dash:
280280
Employee Rainbow Dash was 'Wonderbolt' from 8/26/2021 4:43:29 PM to 8/26/2021 4:44:59 PM
281281
Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:44:59 PM to 12/31/9999 11:59:59 PM
282282
```
283+
284+
## Mapping period columns to CLR properties
285+
286+
> [!NOTE]
287+
> This feature is being introduced in EF Core 11, which is currently in preview.
288+
289+
By default, the period columns in a temporal table are mapped to [shadow properties](xref:core/modeling/shadow-properties) in the EF Core model, meaning they don't need to exist on your .NET entity type. Starting with EF Core 11, you can instead map period columns to regular CLR properties on your entity type, which allows you to access their values directly.
290+
291+
To do this, add `DateTime` properties for the period start and end to your entity type:
292+
293+
```csharp
294+
public class Employee
295+
{
296+
public Guid EmployeeId { get; set; }
297+
public string Name { get; set; }
298+
public string Position { get; set; }
299+
public string Department { get; set; }
300+
public string Address { get; set; }
301+
public decimal AnnualSalary { get; set; }
302+
public DateTime PeriodStart { get; set; }
303+
public DateTime PeriodEnd { get; set; }
304+
}
305+
```
306+
307+
Then configure the temporal table to use these properties via a lambda expression:
308+
309+
```csharp
310+
modelBuilder
311+
.Entity<Employee>()
312+
.ToTable(
313+
"Employees",
314+
b => b.IsTemporal(
315+
b =>
316+
{
317+
b.HasPeriodStart(e => e.PeriodStart);
318+
b.HasPeriodEnd(e => e.PeriodEnd);
319+
}));
320+
```
321+
322+
> [!NOTE]
323+
> Period properties are automatically configured with `ValueGenerated.OnAddOrUpdate`, so their values are always generated by SQL Server. You don't need to — and should not — set their values when inserting or updating entities.
324+
325+
When period columns are mapped to CLR properties, you can access their values directly on the entity instead of using `EF.Property`:
326+
327+
```csharp
328+
var history = context
329+
.Employees
330+
.TemporalAll()
331+
.Where(e => e.Name == "Rainbow Dash")
332+
.OrderBy(e => e.PeriodStart)
333+
.Select(
334+
e => new
335+
{
336+
Employee = e,
337+
e.PeriodStart,
338+
e.PeriodEnd
339+
})
340+
.ToList();
341+
```

entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,50 @@ WHERE JSON_CONTAINS([b].[JsonData], 8, N'$.Rating') = 1
394394

395395
For the full `JSON_CONTAINS` SQL Server documentation, see [`JSON_CONTAINS`](/sql/t-sql/functions/json-contains-transact-sql).
396396

397+
<a name="sqlserver-temporal-clr-properties"></a>
398+
399+
### Temporal period properties mapped to CLR properties
400+
401+
Previously, the period properties (`PeriodStart`/`PeriodEnd`) on temporal tables were required to be [shadow properties](xref:core/modeling/shadow-properties), meaning they existed only in the EF model and not as CLR properties on your .NET entity types. Starting with EF Core 11, period properties can now be mapped to regular CLR properties on the entity type, making it easier to access their values directly without using `EF.Property`.
402+
403+
To map period columns to CLR properties, add `DateTime` properties to your entity type and configure them with the lambda-based `HasPeriodStart`/`HasPeriodEnd` overloads:
404+
405+
```csharp
406+
public class Employee
407+
{
408+
public Guid EmployeeId { get; set; }
409+
public string Name { get; set; }
410+
public string Position { get; set; }
411+
public DateTime PeriodStart { get; set; }
412+
public DateTime PeriodEnd { get; set; }
413+
}
414+
415+
modelBuilder
416+
.Entity<Employee>()
417+
.ToTable(
418+
"Employees",
419+
b => b.IsTemporal(
420+
b =>
421+
{
422+
b.HasPeriodStart(e => e.PeriodStart);
423+
b.HasPeriodEnd(e => e.PeriodEnd);
424+
}));
425+
```
426+
427+
Once period properties are mapped to CLR properties, you can reference them directly in LINQ queries — for example, in `OrderBy`, `Select`, or `Where` clauses — without needing `EF.Property`:
428+
429+
```csharp
430+
var history = context.Employees
431+
.TemporalAll()
432+
.OrderBy(e => e.PeriodStart)
433+
.Select(e => new { e.Name, e.PeriodStart, e.PeriodEnd })
434+
.ToList();
435+
```
436+
437+
Period properties remain configured with `ValueGenerated.OnAddOrUpdate`, so their values are always generated by SQL Server and excluded from INSERT and UPDATE statements.
438+
439+
For more information, see the [full documentation on temporal tables](xref:core/providers/sql-server/temporal-tables#mapping-period-columns-to-clr-properties).
440+
397441
## Cosmos DB
398442

399443
<a name="cosmos-complex-types"></a>

0 commit comments

Comments
 (0)