UZMAN
Temporal Tables — Zamansal Tablolar
SQL Server'ın built-in özelliği — her satırın tüm geçmiş versiyonlarını otomatik saklar. "Bu müşterinin adresi 3 ay önce neydi?" gibi sorulara cevap verir.
Veritabanı sağlayıcısı
Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.
"Bu müşterinin adresi 3 ay önce neydi?" gibi sorulara doğrudan cevap verebilirsiniz.
Yapılandırma
// Entity
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string Email { get; set; } = null!;
public string Address { get; set; } = null!;
// EF Core bunları otomatik ekler (opsiyonel — görmek istersen tanımla)
public DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }
}
// Configuration
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.ToTable("Customers", t => t.IsTemporal(temporal =>
{
temporal.HasPeriodStart("PeriodStart");
temporal.HasPeriodEnd("PeriodEnd");
temporal.UseHistoryTable("CustomersHistory", "audit");
}));
}
}
SQL Çıktısı
CREATE TABLE [dbo].[Customers] (
[Id] INT IDENTITY(1,1) NOT NULL,
[Name] NVARCHAR(MAX) NOT NULL,
[Email] NVARCHAR(MAX) NOT NULL,
[Address] NVARCHAR(MAX) NOT NULL,
[PeriodStart] DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
[PeriodEnd] DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME ([PeriodStart], [PeriodEnd]),
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [audit].[CustomersHistory]));
-- PostgreSQL'de SQL Server'ın SYSTEM_VERSIONING özelliği YOKTUR.
-- Alternatif: Trigger-based history table
CREATE TABLE customers (
id INTEGER GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL,
email TEXT NOT NULL,
address TEXT NOT NULL,
CONSTRAINT pk_customers PRIMARY KEY (id)
);
CREATE TABLE customers_history (
history_id SERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL,
name TEXT,
email TEXT,
address TEXT,
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_to TIMESTAMPTZ NOT NULL DEFAULT 'infinity',
operation TEXT NOT NULL -- 'INSERT','UPDATE','DELETE'
);
-- Trigger fonksiyonu ile otomatik history tracking:
CREATE OR REPLACE FUNCTION track_customer_changes()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
UPDATE customers_history SET valid_to = NOW()
WHERE customer_id = OLD.id AND valid_to = 'infinity';
INSERT INTO customers_history (customer_id, name, email, address, operation)
VALUES (NEW.id, NEW.name, NEW.email, NEW.address, 'UPDATE');
ELSIF TG_OP = 'INSERT' THEN
INSERT INTO customers_history (customer_id, name, email, address, operation)
VALUES (NEW.id, NEW.name, NEW.email, NEW.address, 'INSERT');
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_customer_history
AFTER INSERT OR UPDATE OR DELETE ON customers
FOR EACH ROW EXECUTE FUNCTION track_customer_changes();
Zaman Yolculuğu Sorguları
// Belirli bir andaki hali
var customerInPast = await context.Customers
.TemporalAsOf(new DateTime(2024, 1, 15, 0, 0, 0, DateTimeKind.Utc))
.FirstOrDefaultAsync(c => c.Id == 42);
// Tüm değişiklik geçmişi
var history = await context.Customers
.TemporalAll()
.Where(c => c.Id == 42)
.OrderBy(c => EF.Property<DateTime>(c, "PeriodStart"))
.Select(c => new
{
c.Name,
c.Address,
ValidFrom = EF.Property<DateTime>(c, "PeriodStart"),
ValidTo = EF.Property<DateTime>(c, "PeriodEnd")
})
.ToListAsync();
// Belirli tarih aralığındaki versiyonlar
var changes = await context.Customers
.TemporalBetween(startDate, endDate)
.Where(c => c.Id == 42)
.ToListAsync();
// İki tarih arasında aktif olan kayıtlar
var overlapping = await context.Customers
.TemporalFromTo(startDate, endDate)
.ToListAsync();
Oluşan SQL (TemporalAll)
SELECT [c].[Id], [c].[Name], [c].[Address], [c].[PeriodStart], [c].[PeriodEnd]
FROM [dbo].[Customers] FOR SYSTEM_TIME ALL AS [c]
WHERE [c].[Id] = 42
ORDER BY [c].[PeriodStart]
PostgreSQL'de
FOR SYSTEM_TIMEyoktur. Aşağıdaki trigger-based history tablosu ile benzer sonuç elde edilir:
SELECT c.id, c.name, c.address, c.valid_from, c.valid_to
FROM customers_history AS c
WHERE c.id = 42
ORDER BY c.valid_from;
Örnek Geçmiş Verisi
| Id | Name | Address | PeriodStart | PeriodEnd |
|---|---|---|---|---|
| 42 | Ahmet Yılmaz | Kadıköy, İstanbul | 2024-01-01 | 2024-03-15 |
| 42 | Ahmet Yılmaz | Beşiktaş, İstanbul | 2024-03-15 | 2024-06-20 |
| 42 | Ahmet Yılmaz | Sarıyer, İstanbul | 2024-06-20 | 9999-12-31 |
PostgreSQL'de Temporal Tables:
- PostgreSQL'de SQL Server'ın
SYSTEM_VERSIONINGözelliği yokturIsTemporal()API'si PostgreSQL'de çalışmaz — bu tamamen SQL Server'a özgü- Alternatif çözümler:
// PostgreSQL Alternatif 1: Trigger-based history table
// History tablosu ayrı entity olarak tanımlanır:
public class CustomerHistory
{
public int HistoryId { get; set; }
public int CustomerId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string Operation { get; set; } // INSERT, UPDATE, DELETE
}
-- PostgreSQL: Trigger ile otomatik history
CREATE TABLE customers_history (
history_id SERIAL PRIMARY KEY,
customer_id INT NOT NULL,
name TEXT,
address TEXT,
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_to TIMESTAMPTZ NOT NULL DEFAULT 'infinity',
operation TEXT NOT NULL -- 'INSERT','UPDATE','DELETE'
);
-- Trigger fonksiyonu:
CREATE OR REPLACE FUNCTION track_customer_changes()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
-- Eski kaydı kapat
UPDATE customers_history
SET valid_to = NOW()
WHERE customer_id = OLD.id AND valid_to = 'infinity';
-- Yeni kayıt ekle
INSERT INTO customers_history (customer_id, name, address, operation)
VALUES (NEW.id, NEW.name, NEW.address, 'UPDATE');
ELSIF TG_OP = 'INSERT' THEN
INSERT INTO customers_history (customer_id, name, address, operation)
VALUES (NEW.id, NEW.name, NEW.address, 'INSERT');
ELSIF TG_OP = 'DELETE' THEN
UPDATE customers_history
SET valid_to = NOW()
WHERE customer_id = OLD.id AND valid_to = 'infinity';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_customer_history
AFTER INSERT OR UPDATE OR DELETE ON customers
FOR EACH ROW EXECUTE FUNCTION track_customer_changes();
-- Sorgulama (SQL Server'ın FOR SYSTEM_TIME ALL karşılığı):
SELECT * FROM customers_history
WHERE customer_id = 42
ORDER BY valid_from;
PostgreSQL alternatifleri özet:
Yöntem Avantaj Dezavantaj Trigger + history table Tam kontrol, özelleştirilebilir Manuel kurulum pgAuditextensionDML/DDL seviyesinde log Sütun bazlı geçmiş yok EF Interceptor (Bölüm 30) Provider bağımsız Uygulama seviyesinde temporal_tablesextensionSQL Server benzeri syntax Community, resmi değil