0Pricing
C# Academy · Lekcja

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 TenantId

Pomijanie 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 JOIN

Typy 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 CustomerId

OwnsMany 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 / OwnsMany konfigurują 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

  1. Ładowanie eager, lazy i explicit
  2. Surowy SQL, procedury składowane i interpolacja
  3. Zapytania kompilowane i wydajność
  4. Globalne filtry zapytań i encje owned
← Powrót do C# Academy