C# Academy · Lektion

Globala frågefilter och ägda entiteter

Tillämpa klientisolering och mjuk borttagning med globala frågefilter och mappa värdeobjekt med ägda entitetstyper.

Lektion 4 av 413 steg

Globala frågefilter och ägda entiteter är en gratis lektion i C# Academy på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C# Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C# Academy innehåller totalt 4 lektioner.

Vad är globala frågefilter?

Globala frågefilter är WHERE-predikat som automatiskt tillämpas på varje LINQ-fråga för en viss entitetstyp. De passar perfekt för soft delete, multi-tenancy och row-level security – utan att villkoret behöver upprepas överallt.

Soft delete med globalt filter

Lägg till en IsDeleted-flagga och konfigurera ett globalt filter så att borttagna poster aldrig returneras om du inte uttryckligen ignorerar filtret.

// 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();

Multi-tenancy med globalt filter

Använd ett filter som läser den aktuella tenanten från en scoped-tjänst. Varje fråga begränsas automatiskt till den aktuella tenantens data.

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

Ignorera globala filter

Ibland behöver du kringgå filtret – till exempel i administratörsfrågor för alla tenants eller när du återställer en soft-deleted post. Använd IgnoreQueryFilters() endast för den aktuella frågan.

// 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();
}

Filter på navigeringsegenskaper

Globala filter påverkar även JOINs. Om Posts har ett filter filtreras relaterade Posts som läses in via Include också automatiskt.

// 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

Ägda entitetstyper: grundidé

Ägda entitetstyper är klasser som uteslutande tillhör en enda ägarentitet. De har ingen primärnyckel och lagras i samma tabell (som standard). De passar perfekt för värdeobjekt i 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; } = "";
}

Konfigurera ägda entiteter

Använd OwnsOne för att konfigurera en enskild ägd entitet. EF Core lägger som standard till navigeringsegenskapens namn som prefix på kolumnerna.

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, ...

Ägda entiteter i en separat tabell

Använd ToTable() inuti OwnsOne för att lagra ägda entiteter i en egen tabell – användbart när data uppgår till stora mängder eller är valfria.

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 för samlingar av värdeobjekt

OwnsMany konfigurerar en samling ägda entiteter – till exempel en lista med e-postadresser eller telefonnummer som lagras i en underordnad tabell.

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 jämfört med Owned

Globala filter fungerar också bra med TPH-arv (Table-Per-Hierarchy), där en diskriminatorkolumn skiljer undertyper åt. Tillämpa ett filter baserat på diskriminatorn för typspecifika frågor.

// 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'

Isolering av SaaS-tenants i praktiken

En fullständig SaaS-konfiguration för multi-tenancy kombinerar globala frågefilter med en tenantkontext med scoped-livslängd, så att varje fråga automatiskt begränsas utan kod per fråga.

// 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'

Snabbkontroll

Vilken metod anropar du för att tillfälligt kringgå ett globalt frågefilter för en specifik fråga?

Sammanfattning: globala filter och ägda entiteter

Viktiga punkter:

  • Globala frågefilter lägger automatiskt till WHERE-predikat i varje fråga för en entitet
  • Idealiska för soft delete, multi-tenancy och row-level security
  • Kringgå dem med IgnoreQueryFilters() vid administratörs- eller återställningsåtgärder
  • Ägda entiteter modellerar värdeobjekt – ingen primärnyckel, lagras i ägarens tabell
  • OwnsOne / OwnsMany konfigurerar ägda relationer; använd ToTable() för att dela upp dem
Gratis att börja

Lär dig C# med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
93
Lektioner
346

Vanliga frågor

Är lektionen ”Globala frågefilter och ägda entiteter” gratis?

Ja – hela texten till ”Globala frågefilter och ägda entiteter” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C# Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C# Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Globala frågefilter och ägda entiteter”?

Tillämpa klientisolering och mjuk borttagning med globala frågefilter och mappa värdeobjekt med ägda entitetstyper. Ni övar på C# Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig C# Academy?

Du behöver inga förkunskaper. Utbildningen i C# Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Globala frågefilter och ägda entiteter”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här C# Academy-lektionen?

Ja. Varje C# Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Eager, lazy och explicit inläsning
  2. Rå SQL, lagrade procedurer och interpolering
  3. Kompilerade frågor och prestanda
  4. Globala frågefilter och ägda entiteter
← Tillbaka till C# Academy