Globala frågefilter och ägda entiteter
Tillämpa klientisolering och mjuk borttagning med globala frågefilter och mappa värdeobjekt med ägda entitetstyper.
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 TenantIdIgnorera 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 CustomerIdOwnsMany 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/OwnsManykonfigurerar ägda relationer; användToTable()för att dela upp dem
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
- Eager, lazy och explicit inläsning
- Rå SQL, lagrade procedurer och interpolering
- Kompilerade frågor och prestanda
- Globala frågefilter och ägda entiteter