Skip to content

Commit 5a1ac46

Browse files
temporal tables #78
1 parent 6b6c3c1 commit 5a1ac46

File tree

7 files changed

+272
-0
lines changed

7 files changed

+272
-0
lines changed
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace TemporalTableSample;
4+
5+
public class Book
6+
{
7+
public Book(string title, string? publisher = default, int bookId = default)
8+
{
9+
Title = title;
10+
Publisher = publisher;
11+
BookId = bookId;
12+
}
13+
[StringLength(50)]
14+
public string Title { get; set; }
15+
[StringLength(30)]
16+
public string? Publisher { get; set; }
17+
public int BookId { get; set; }
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace TemporalTableSample;
4+
5+
public class BooksContext : DbContext
6+
{
7+
public BooksContext(DbContextOptions<BooksContext> options)
8+
: base(options) { }
9+
10+
protected override void OnModelCreating(ModelBuilder modelBuilder)
11+
{
12+
modelBuilder.Entity<Book>()
13+
.ToTable("Books", b => b.IsTemporal()); // creates PeriodStart, PeriodEnd columns, use overload to customize the columns
14+
}
15+
16+
public DbSet<Book> Books => Set<Book>();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Configuration;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Hosting;
5+
using TemporalTableSample;
6+
7+
using var host = Host.CreateDefaultBuilder(args)
8+
.ConfigureServices((context, services) =>
9+
{
10+
var connectionString = context.Configuration.GetConnectionString("BooksConnection");
11+
services.AddDbContext<BooksContext>(options =>
12+
{
13+
options.UseSqlServer(connectionString);
14+
});
15+
services.AddScoped<Runner>();
16+
})
17+
.Build();
18+
19+
using var scope = host.Services.CreateScope();
20+
var runner = scope.ServiceProvider.GetRequiredService<Runner>();
21+
22+
await runner.CreateTheDatabaseAsync();
23+
await runner.AddBookAsync("Professional C# and .NET", "Wrox Press");
24+
await runner.AddBooksAsync();
25+
await runner.ReadBooksAsync();
26+
await runner.QueryBooksAsync();
27+
await runner.UpdateBookAsync();
28+
await runner.TemporalPointInTimeQueryAsync();
29+
await runner.TemporalAllQueryAsync();
30+
await runner.DeleteBooksAsync();
31+
await runner.DeleteDatabaseAsync();
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace TemporalTableSample;
4+
5+
public class Runner
6+
{
7+
private readonly BooksContext _booksContext;
8+
public Runner(BooksContext booksContext)
9+
{
10+
_booksContext = booksContext;
11+
}
12+
13+
public async Task CreateTheDatabaseAsync()
14+
{
15+
bool created = await _booksContext.Database.EnsureCreatedAsync();
16+
string creationInfo = created ? "created" : "exists";
17+
Console.WriteLine($"database {creationInfo}");
18+
}
19+
20+
public async Task DeleteDatabaseAsync()
21+
{
22+
Console.Write("Delete the database? (y|n) ");
23+
string? input = Console.ReadLine();
24+
if (input?.ToLower() == "y")
25+
{
26+
bool deleted = await _booksContext.Database.EnsureDeletedAsync();
27+
string deletionInfo = deleted ? "deleted" : "not deleted";
28+
Console.WriteLine($"database {deletionInfo}");
29+
}
30+
}
31+
32+
public async Task AddBookAsync(string title, string publisher)
33+
{
34+
Book book = new(title, publisher);
35+
await _booksContext.Books.AddAsync(book);
36+
int records = await _booksContext.SaveChangesAsync();
37+
Console.WriteLine($"{records} record added with id {book.BookId}");
38+
39+
Console.WriteLine();
40+
}
41+
42+
public async Task AddBooksAsync()
43+
{
44+
Book b1 = new("Professional C# 7 and .NET Core 2", "Wrox Press");
45+
Book b2 = new("Professional C# 6 and .NET Core 1.0", "Wrox Press");
46+
Book b3 = new("Professional C# 5 and .NET 4.5.1", "Wrox Press");
47+
Book b4 = new("Essential Algorithms", "Wiley");
48+
await _booksContext.Books.AddRangeAsync(b1, b2, b3, b4);
49+
int records = await _booksContext.SaveChangesAsync();
50+
Console.WriteLine($"{records} records added");
51+
52+
Console.WriteLine();
53+
}
54+
55+
public async Task ReadBooksAsync(CancellationToken token = default)
56+
{
57+
#if DEBUG
58+
string query = _booksContext.Books.ToQueryString();
59+
Console.WriteLine(query);
60+
#endif
61+
List<Book> books = await _booksContext.Books.ToListAsync(token);
62+
foreach (var b in books)
63+
{
64+
Console.WriteLine($"{b.Title} {b.Publisher}");
65+
}
66+
67+
Console.WriteLine();
68+
}
69+
70+
public async Task QueryBooksAsync(CancellationToken token = default)
71+
{
72+
string query = _booksContext.Books.Where(b => b.Publisher == "Wrox Press").ToQueryString();
73+
Console.WriteLine(query);
74+
await _booksContext.Books
75+
.Where(b => b.Publisher == "Wrox Press")
76+
.ForEachAsync(b =>
77+
{
78+
Console.WriteLine($"{b.Title} {b.Publisher}");
79+
}, token);
80+
81+
Console.WriteLine();
82+
}
83+
84+
public async Task UpdateBookAsync()
85+
{
86+
Book? book = await _booksContext.Books.FindAsync(1);
87+
Console.WriteLine("Just a short delay before updating...");
88+
await Task.Delay(TimeSpan.FromSeconds(5));
89+
90+
if (book != null)
91+
{
92+
book.Title = "Professional C# and .NET - 2021 Edition";
93+
int records = await _booksContext.SaveChangesAsync();
94+
Console.WriteLine($"{records} record updated");
95+
}
96+
Console.WriteLine();
97+
}
98+
99+
100+
public async Task TemporalPointInTimeQueryAsync()
101+
{
102+
Book? book = await _booksContext.Books.FindAsync(1);
103+
if (book is null) return;
104+
// read shadow property for time
105+
if (_booksContext.Entry(book).CurrentValues["PeriodStart"] is DateTime periodStart)
106+
{
107+
DateTime previousTime = periodStart.AddSeconds(-4);
108+
var previousBook = await _booksContext.Books
109+
.TemporalAsOf(previousTime)
110+
.TagWith("temporalasof")
111+
.SingleOrDefaultAsync(b => b.BookId == book.BookId);
112+
113+
Console.WriteLine($"actual: {book.BookId}: {book.Title}, {book.Publisher}");
114+
if (previousBook is not null)
115+
{
116+
Console.WriteLine($"earlier: {previousBook.BookId}: {previousBook.Title}, " +
117+
$"{previousBook.Publisher}");
118+
}
119+
}
120+
}
121+
122+
public async Task TemporalAllQueryAsync()
123+
{
124+
await _booksContext.Books
125+
.TemporalAll()
126+
.TagWith("temporalall")
127+
.ForEachAsync(b =>
128+
{
129+
var entry = _booksContext.Entry(b);
130+
Console.WriteLine($"{b.Title} {entry.State}");
131+
});
132+
}
133+
134+
public async Task DeleteBooksAsync()
135+
{
136+
List<Book> books = await _booksContext.Books.ToListAsync();
137+
_booksContext.Books.RemoveRange(books);
138+
int records = await _booksContext.SaveChangesAsync();
139+
Console.WriteLine($"{records} records deleted");
140+
141+
Console.WriteLine();
142+
}
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
12+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
13+
<PrivateAssets>all</PrivateAssets>
14+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
15+
</PackageReference>
16+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1" />
17+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<None Update="appsettings.json">
22+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23+
</None>
24+
</ItemGroup>
25+
26+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.32120.378
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemporalTableSample", "TemporalTableSample.csproj", "{7BF2EE89-0ABB-4B1B-BCDC-42BD098D2F86}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{7BF2EE89-0ABB-4B1B-BCDC-42BD098D2F86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{7BF2EE89-0ABB-4B1B-BCDC-42BD098D2F86}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{7BF2EE89-0ABB-4B1B-BCDC-42BD098D2F86}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{7BF2EE89-0ABB-4B1B-BCDC-42BD098D2F86}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {9FFBC304-9E4F-45E1-A72A-F05C239E346C}
24+
EndGlobalSection
25+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Logging": {
3+
"Console": {
4+
"LogLevel": {
5+
"Microsoft.EntityFramework": "Debug"
6+
}
7+
}
8+
},
9+
"ConnectionStrings": {
10+
"BooksConnection": "server=(localdb)\\mssqllocaldb;database=ProCSharpBooksTemporalTable;trusted_connection=true"
11+
}
12+
}

0 commit comments

Comments
 (0)