Globalne filtry zapytań i encje owned
Stosuj izolację tenantów i usuwanie logiczne za pomocą globalnych filtrów zapytań oraz mapuj obiekty wartości z użyciem typów encji owned.
Globalne filtry zapytań i encje owned to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Czym są globalne filtry zapytań
Globalne filtry zapytań to predykaty WHERE automatycznie stosowane do każdego zapytania LINQ dotyczącego danego typu encji. Doskonale nadają się do usuwania logicznego, wielodostępności i zabezpieczeń na poziomie wierszy — bez powtarzania warunku w każdym miejscu.
Usuwanie logiczne z globalnym filtrem
Należy dodać flagę IsDeleted i skonfigurować filtr globalny, aby usunięte rekordy nigdy nie były zwracane, chyba że filtr zostanie jawnie pominięty.
// Entity with soft-delete flag
public class Post
{
public int Id { get; set; }
public string Title { get; set; } = "";
public bool IsDeleted { get; set; }
}
// Configure in OnModelCreating
modelBuilder.Entity<Post>()
.HasQueryFilter(p => !p.IsDeleted);
// All queries now implicitly add WHERE IsDeleted = 0:
var posts = await _db.Posts.ToListAsync();Wielodostępność z globalnym filtrem
Należy użyć filtra odczytującego bieżącego tenanta z usługi o zasięgu żądania. Każde zapytanie będzie automatycznie ograniczane do danych bieżącego tenanta.
public class AppDbContext : DbContext
{
private readonly ICurrentUserService _user;
public AppDbContext(DbContextOptions<AppDbContext> opt, ICurrentUserService user)
: base(opt) => _user = user;
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Order>().HasQueryFilter(
o => o.TenantId == _user.TenantId);
}
}
// Every Order query automatically filters by TenantIdPomijanie globalnych filtrów
Czasami trzeba pominąć filtr, na przykład w zapytaniach administratora dotyczących wszystkich tenantów lub podczas przywracania rekordu usuniętego logicznie. Należy użyć IgnoreQueryFilters() tylko dla tego zapytania.
// Admin: see ALL posts including deleted
var allPosts = await _db.Posts
.IgnoreQueryFilters()
.ToListAsync();
// Restore a specific deleted post
var deleted = await _db.Posts
.IgnoreQueryFilters()
.FirstOrDefaultAsync(p => p.Id == 42);
if (deleted is not null)
{
deleted.IsDeleted = false;
await _db.SaveChangesAsync();
}Filtry dotyczące właściwości nawigacyjnych
Globalne filtry uczestniczą również w operacjach JOIN. Jeśli Posts ma filtr, powiązane obiekty Posts ładowane za pomocą Include również zostaną automatycznie odfiltrowane.
// Blog with soft-delete filter on Posts
var blog = await _db.Blogs
.Include(b => b.Posts) // only non-deleted posts are included
.FirstOrDefaultAsync(b => b.Id == 1);
// blog.Posts contains only active posts — the filter applied in the JOINTypy encji należących: koncepcja
Typy encji należących to klasy należące wyłącznie do jednej encji właściciela. Nie mają klucza głównego i domyślnie są przechowywane w tej samej tabeli. Doskonale nadają się do modelowania obiektów wartości w DDD.
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public Address BillingAddress { get; set; } = new();
public Address ShippingAddress { get; set; } = new();
}
public class Address
{
public string Street { get; set; } = "";
public string City { get; set; } = "";
public string ZipCode { get; set; } = "";
public string Country { get; set; } = "";
}Konfigurowanie encji należących
Należy użyć OwnsOne, aby skonfigurować pojedynczą encję należącą. EF Core domyślnie poprzedza nazwy kolumn nazwą właściwości nawigacyjnej.
modelBuilder.Entity<Customer>(entity =>
{
entity.OwnsOne(c => c.BillingAddress, addr =>
{
addr.Property(a => a.Street).HasMaxLength(300);
addr.Property(a => a.Country).HasMaxLength(2);
});
entity.OwnsOne(c => c.ShippingAddress);
});
// Table: Customers with columns:
// BillingAddress_Street, BillingAddress_City, ...
// ShippingAddress_Street, ShippingAddress_City, ...Encje należące w osobnej tabeli
Należy użyć ToTable() wewnątrz OwnsOne, aby przechowywać encje należące w ich własnej tabeli — jest to przydatne, gdy dane są duże lub opcjonalne.
modelBuilder.Entity<Customer>().OwnsOne(
c => c.BillingAddress,
addr => addr.ToTable("CustomerBillingAddresses"));
// Now BillingAddress columns are in a separate table
// with a FK back to CustomerIdOwnsMany dla kolekcji obiektów wartości
OwnsMany konfiguruje kolekcję encji należących, na przykład listę adresów e-mail lub numerów telefonów przechowywanych w tabeli podrzędnej.
public class Customer
{
public int Id { get; set; }
public IList<PhoneNumber> PhoneNumbers { get; set; } = new List<PhoneNumber>();
}
public class PhoneNumber
{
public string Number { get; set; } = "";
public string Type { get; set; } = "";
}
modelBuilder.Entity<Customer>()
.OwnsMany(c => c.PhoneNumbers,
phone => phone.ToTable("CustomerPhones"));Table-Per-Hierarchy a encje należące
Globalne filtry dobrze współpracują również z dziedziczeniem TPH (Table-Per-Hierarchy), w którym kolumna dyskryminatora rozróżnia podtypy. Należy zastosować filtr oparty na dyskryminatorze dla zapytań dotyczących określonego typu.
// Base entity
public abstract class Payment { public int Id { get; set; } }
public class CardPayment : Payment { public string CardLast4 { get; set; } = ""; }
public class BankPayment : Payment { public string IBAN { get; set; } = ""; }
// All Payment subtypes in one table with a Discriminator column
// No extra config needed — EF Core handles TPH by default
var cards = await _db.Set<CardPayment>().ToListAsync();
// SQL: SELECT * FROM Payments WHERE Discriminator = 'CardPayment'Praktyka: izolacja tenantów w SaaS
Kompletna konfiguracja wielodostępności SaaS łączy globalne filtry zapytań z kontekstem tenanta o zasięgu żądania, dzięki czemu każde zapytanie jest automatycznie ograniczane bez kodu wykonywanego dla poszczególnych zapytań.
// Scoped tenant service
public class TenantContext
{
public Guid TenantId { get; set; }
}
// DbContext uses it
modelBuilder.Entity<Invoice>().HasQueryFilter(
inv => inv.TenantId == _tenantCtx.TenantId);
modelBuilder.Entity<Customer>().HasQueryFilter(
c => c.TenantId == _tenantCtx.TenantId);
// Queries are automatically scoped:
var invoices = await _db.Invoices.ToListAsync();
// WHERE TenantId = 'current-tenant-guid'Szybkie sprawdzenie
Jaką metodę należy wywołać, aby tymczasowo pominąć globalny filtr zapytań dla konkretnego zapytania?
Podsumowanie: filtry globalne i encje należące
Najważniejsze informacje:
- Globalne filtry zapytań automatycznie dodają predykaty WHERE do każdego zapytania dotyczącego encji
- Idealnie nadają się do usuwania logicznego, wielodostępności i zabezpieczeń na poziomie wierszy
- Można je pominąć za pomocą
IgnoreQueryFilters()podczas operacji administracyjnych lub przywracania danych - Encje należące służą do modelowania obiektów wartości — nie mają klucza głównego i są przechowywane w tabeli właściciela
OwnsOne/OwnsManykonfigurują relacje encji należących; za pomocąToTable()można rozdzielić je do osobnej tabeli
Często zadawane pytania
Czy lekcja „Globalne filtry zapytań i encje owned” jest bezpłatna?
Tak — pełny tekst „Globalne filtry zapytań i encje owned” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Globalne filtry zapytań i encje owned”?
Stosuj izolację tenantów i usuwanie logiczne za pomocą globalnych filtrów zapytań oraz mapuj obiekty wartości z użyciem typów encji owned. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Globalne filtry zapytań i encje owned”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Ładowanie eager, lazy i explicit
- Surowy SQL, procedury składowane i interpolacja
- Zapytania kompilowane i wydajność
- Globalne filtry zapytań i encje owned