Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Tabele czasowe programu SQL Server automatycznie śledzą wszystkie dane przechowywane w tabeli nawet po zaktualizowaniu lub usunięciu tych danych. Jest to osiągane przez utworzenie równoległej "tabeli historii", w której przechowywane są dane historyczne ze znacznikami czasu za każdym razem, gdy zostanie wprowadzona zmiana w tabeli głównej. Umożliwia to wykonywanie zapytań dotyczących danych historycznych, takich jak audyt, lub odtworzenie, na przykład po przypadkowej mutacji lub usunięciu.
Program EF Core obsługuje następujące funkcje:
- Tworzenie tabel czasowych przy użyciu migracji platformy EF Core
- Przekształcanie istniejących tabel w tabele czasowe, ponownie przy użyciu funkcji Migracje
- Wykonywanie zapytań dotyczących danych historycznych
- Przywracanie danych z jakiegoś punktu w przeszłości
Konfigurowanie tabeli czasowej
Konstruktor modelu może służyć do konfigurowania tabeli jako tabeli czasowej. Przykład:
modelBuilder
.Entity<Employee>()
.ToTable("Employees", b => b.IsTemporal());
Wskazówka
Pokazany tutaj kod pochodzi z TemporalTablesSample.cs.
W przypadku tworzenia bazy danych przy użyciu programu EF Core nowa tabela jest skonfigurowana jako tabela czasowa z domyślnymi wartościami programu SQL Server dla sygnatur czasowych i tabeli historii. Rozważmy na przykład Employee typ jednostki:
public class Employee
{
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; }
}
Utworzona tabela czasowa wygląda następująco:
DECLARE @historyTableSchema sysname = SCHEMA_NAME()
EXEC(N'CREATE TABLE [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 GENERATED ALWAYS AS ROW END NOT NULL,
[PeriodStart] datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
CONSTRAINT [PK_Employees] PRIMARY KEY ([EmployeeId]),
PERIOD FOR SYSTEM_TIME([PeriodStart], [PeriodEnd])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].[EmployeeHistory]))');
Zwróć uwagę, że program SQL Server tworzy dwie ukryte datetime2 kolumny o nazwie PeriodEnd i PeriodStart. Te "kolumny okresu" reprezentują zakres czasu, w którym istniały dane w wierszu. Domyślnie te kolumny są mapowane na właściwości w tle w modelu EF Core, co pozwala na ich stosowanie w zapytaniach, jak pokazano później. Począwszy od programu EF Core 11, kolumny okresów można również mapować na właściwości CLR w typie jednostki.
Ważne
Czasy w tych kolumnach są zawsze czasem UTC generowanym przez program SQL Server. Czasy UTC są używane dla wszystkich operacji obejmujących tabele czasowe, takie jak w zapytaniach przedstawionych poniżej.
Zauważ również, że skojarzona tabela historii o nazwie EmployeeHistory jest tworzona automatycznie. Nazwy kolumn okresów i tabeli historii można zmienić przy użyciu dodatkowej konfiguracji konstruktora modelu. Przykład:
modelBuilder
.Entity<Employee>()
.ToTable(
"Employees",
b => b.IsTemporal(
b =>
{
b.HasPeriodStart("ValidFrom");
b.HasPeriodEnd("ValidTo");
b.UseHistoryTable("EmployeeHistoricalData");
}));
Jest to odzwierciedlone w tabeli utworzonej przez program SQL Server:
DECLARE @historyTableSchema sysname = SCHEMA_NAME()
EXEC(N'CREATE TABLE [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,
[ValidFrom] datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
[ValidTo] datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
CONSTRAINT [PK_Employees] PRIMARY KEY ([EmployeeId]),
PERIOD FOR SYSTEM_TIME([ValidFrom], [ValidTo])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].[EmployeeHistoricalData]))');
Korzystanie z tabel czasowych
W większości przypadków tabele czasowe są używane tak samo jak każda inna tabela. Oznacza to, że kolumny okresu i dane historyczne są obsługiwane w sposób przejrzysty przez SQL Server, tak aby aplikacja mogła je zignorować. Na przykład nowe jednostki można zapisywać w bazie danych w normalny sposób:
context.AddRange(
new Employee
{
Name = "Pinky Pie",
Address = "Sugarcube Corner, Ponyville, Equestria",
Department = "DevDiv",
Position = "Party Organizer",
AnnualSalary = 100.0m
},
new Employee
{
Name = "Rainbow Dash",
Address = "Cloudominium, Ponyville, Equestria",
Department = "DevDiv",
Position = "Ponyville weather patrol",
AnnualSalary = 900.0m
},
new Employee
{
Name = "Fluttershy",
Address = "Everfree Forest, Equestria",
Department = "DevDiv",
Position = "Animal caretaker",
AnnualSalary = 30.0m
});
await context.SaveChangesAsync();
Te dane mogą być następnie odpytywane, aktualizowane i usuwane w normalny sposób. Przykład:
var employee = await context.Employees.SingleAsync(e => e.Name == "Rainbow Dash");
context.Remove(employee);
await context.SaveChangesAsync();
Ponadto po normalnym zapytaniu śledzenia można uzyskać dostęp do wartości z kolumn okresu bieżących danych z śledzonych jednostek. Jeśli kolumny okresu są mapowane na właściwości CLR, możesz uzyskać do nich dostęp bezpośrednio w jednostce; w przeciwnym razie użyj polecenia EF.Property, aby uzyskać do nich dostęp jako właściwości cieniowe. Przykład:
var employees = await context.Employees.ToListAsync();
foreach (var employee in employees)
{
var employeeEntry = context.Entry(employee);
var validFrom = employeeEntry.Property<DateTime>("ValidFrom").CurrentValue;
var validTo = employeeEntry.Property<DateTime>("ValidTo").CurrentValue;
Console.WriteLine($" Employee {employee.Name} valid from {validFrom} to {validTo}");
}
To drukuje:
Starting data:
Employee Pinky Pie valid from 8/26/2021 4:38:58 PM to 12/31/9999 11:59:59 PM
Employee Rainbow Dash valid from 8/26/2021 4:38:58 PM to 12/31/9999 11:59:59 PM
Employee Fluttershy valid from 8/26/2021 4:38:58 PM to 12/31/9999 11:59:59 PM
Zwróć uwagę, że kolumna ValidTo (domyślnie nazywana PeriodEnd) zawiera wartość maksymalną datetime2 . Tak jest zawsze w przypadku bieżących wierszy w tabeli. Kolumna ValidFrom (domyślnie nazywana PeriodStart) zawiera czas w UTC, w którym wiersz został wstawiony.
Wykonywanie zapytań dotyczących danych historycznych
Program EF Core obsługuje zapytania obejmujące dane historyczne za pośrednictwem kilku wyspecjalizowanych operatorów zapytań:
-
TemporalAsOf: zwraca wiersze, które były aktywne (bieżące) o danej godzinie UTC. Jest to pojedynczy wiersz z bieżącej tabeli lub tabeli historii dla danego klucza podstawowego. -
TemporalAll: zwraca wszystkie wiersze w danych historycznych. Zazwyczaj jest to wiele wierszy z tabeli historycznej i/lub z tabeli bieżącej dla danego klucza podstawowego. -
TemporalFromTo: zwraca wszystkie wiersze, które były aktywne między dwoma podanymi godzinami UTC. Może chodzić o wiele wierszy z tabeli historii i/lub bieżącej tabeli w odniesieniu do danego klucza podstawowego. -
TemporalBetween: To samo coTemporalFromTo, z tą różnicą, że uwzględniane są wiersze, które stały się aktywne na górnej granicy. -
TemporalContainedIn: zwraca wszystkie wiersze, które zaczęły być aktywne i kończą się aktywne między dwoma podanymi godzinami UTC. Może chodzić o wiele wierszy z tabeli historii i/lub bieżącej tabeli w odniesieniu do danego klucza podstawowego.
Uwaga / Notatka
Zapoznaj się z dokumentacją tabel czasowych programu SQL Server , aby uzyskać więcej informacji na temat dokładnie wierszy uwzględnionych dla każdego z tych operatorów.
Na przykład po wprowadzeniu niektórych aktualizacji i usunięcia danych możemy uruchomić zapytanie przy użyciu polecenia TemporalAll , aby wyświetlić dane historyczne:
var history = await context
.Employees
.TemporalAll()
.Where(e => e.Name == "Rainbow Dash")
.OrderBy(e => EF.Property<DateTime>(e, "ValidFrom"))
.Select(
e => new
{
Employee = e,
ValidFrom = EF.Property<DateTime>(e, "ValidFrom"),
ValidTo = EF.Property<DateTime>(e, "ValidTo")
})
.ToListAsync();
foreach (var pointInTime in history)
{
Console.WriteLine(
$" Employee {pointInTime.Employee.Name} was '{pointInTime.Employee.Position}' from {pointInTime.ValidFrom} to {pointInTime.ValidTo}");
}
Zauważ, jak EF.Property metoda może być używana do uzyskiwania dostępu do wartości z kolumn dotyczących okresów. Jest on używany w klauzuli OrderBy do sortowania danych, a następnie w projekcji w celu uwzględnienia tych wartości w zwracanych danych. Jeśli kolumny okresowe są mapowane na właściwości CLR, możesz odwoływać się do nich bezpośrednio w zapytaniu zamiast korzystania z EF.Property.
To zapytanie przywraca następujące dane:
Historical data for Rainbow Dash:
Employee Rainbow Dash was 'Ponyville weather patrol' from 8/26/2021 4:38:58 PM to 8/26/2021 4:40:29 PM
Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:40:29 PM to 8/26/2021 4:41:59 PM
Employee Rainbow Dash was 'Wonderbolt Reservist' from 8/26/2021 4:41:59 PM to 8/26/2021 4:43:29 PM
Employee Rainbow Dash was 'Wonderbolt' from 8/26/2021 4:43:29 PM to 8/26/2021 4:44:59 PM
Zwróć uwagę, że ostatni wiersz przestał być aktywny o godzinie 26.08.2021 4:44:59. Wynika to z faktu, że wiersz dla Rainbow Dash został usunięty z tabeli głównej wówczas. Później zobaczymy, jak można przywrócić te dane.
Podobne zapytania można zapisywać przy użyciu metody TemporalFromTo, TemporalBetweenlub TemporalContainedIn. Przykład:
var history = await context
.Employees
.TemporalBetween(timeStamp2, timeStamp3)
.Where(e => e.Name == "Rainbow Dash")
.OrderBy(e => EF.Property<DateTime>(e, "ValidFrom"))
.Select(
e => new
{
Employee = e,
ValidFrom = EF.Property<DateTime>(e, "ValidFrom"),
ValidTo = EF.Property<DateTime>(e, "ValidTo")
})
.ToListAsync();
To zapytanie zwraca następujące wiersze:
Historical data for Rainbow Dash between 8/26/2021 4:41:14 PM and 8/26/2021 4:42:44 PM:
Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:40:29 PM to 8/26/2021 4:41:59 PM
Employee Rainbow Dash was 'Wonderbolt Reservist' from 8/26/2021 4:41:59 PM to 8/26/2021 4:43:29 PM
Przywracanie danych historycznych
Jak wspomniano powyżej, Rainbow Dash został usunięty z Employees tabeli. Był to wyraźnie błąd, więc wróćmy do punktu w czasie i przywróćmy brakujący wiersz z tego czasu.
var employee = await context
.Employees
.TemporalAsOf(timeStamp2)
.SingleAsync(e => e.Name == "Rainbow Dash");
context.Add(employee);
await context.SaveChangesAsync();
To zapytanie zwraca pojedynczy wiersz dla Rainbow Dash, tak jak wyglądała o danej godzinie UTC. Wszystkie zapytania wykorzystujące operatory czasowe są domyślnie bez śledzenia, więc zwracana encja nie jest śledzona. Ma to sens, ponieważ obecnie nie istnieje w tabeli głównej. Aby ponownie wstawić jednostkę do tabeli głównej, po prostu oznaczymy ją jako Added , a następnie wywołamy metodę SaveChanges.
Po ponownym wstawieniu wiersza Rainbow Dash, zapytywanie danych historycznych pokazuje, że wiersz został przywrócony, tak, jak wyglądał o danej godzinie UTC.
Historical data for Rainbow Dash:
Employee Rainbow Dash was 'Ponyville weather patrol' from 8/26/2021 4:38:58 PM to 8/26/2021 4:40:29 PM
Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:40:29 PM to 8/26/2021 4:41:59 PM
Employee Rainbow Dash was 'Wonderbolt Reservist' from 8/26/2021 4:41:59 PM to 8/26/2021 4:43:29 PM
Employee Rainbow Dash was 'Wonderbolt' from 8/26/2021 4:43:29 PM to 8/26/2021 4:44:59 PM
Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:44:59 PM to 12/31/9999 11:59:59 PM
Mapowanie kolumn okresu na właściwości CLR
Uwaga / Notatka
Ta funkcja jest wprowadzana w programie EF Core 11, który jest obecnie w wersji zapoznawczej.
Domyślnie kolumny okresów w tabeli czasowej są mapowane na właściwości shadow w modelu platformy EF Core, co oznacza, że nie muszą istnieć w typie jednostki .NET. Począwszy od programu EF Core 11, można zamiast tego mapować kolumny okresów na zwykłe właściwości CLR w typie jednostki, co umożliwia bezpośredni dostęp do ich wartości.
Aby to zrobić, dodaj DateTime właściwości dla początku i końca okresu do typu jednostki:
public class Employee
{
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 DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }
}
Następnie skonfiguruj tabelę czasową tak, aby korzystała z tych właściwości za pomocą wyrażenia lambda:
modelBuilder
.Entity<Employee>()
.ToTable(
"Employees",
b => b.IsTemporal(
b =>
{
b.HasPeriodStart(e => e.PeriodStart);
b.HasPeriodEnd(e => e.PeriodEnd);
}));
Uwaga / Notatka
Właściwości okresu są automatycznie konfigurowane przy użyciu ValueGenerated.OnAddOrUpdate, więc ich wartości są zawsze generowane przez SQL Server. Nie musisz — i nie należy — ustawiać ich wartości podczas wstawiania lub aktualizowania jednostek.
Gdy kolumny czasowe są mapowane na właściwości CLR, można uzyskać dostęp do ich wartości bezpośrednio w encjach zamiast używać EF.Property:
var history = context
.Employees
.TemporalAll()
.Where(e => e.Name == "Rainbow Dash")
.OrderBy(e => e.PeriodStart)
.Select(
e => new
{
Employee = e,
e.PeriodStart,
e.PeriodEnd
})
.ToList();