Globale Abfragefilter und Owned Entities
Setzen Sie Mandantentrennung und Soft Delete mit globalen Abfragefiltern um und bilden Sie Value Objects mithilfe von Owned Entity Types ab.
Globale Abfragefilter und Owned Entities ist eine kostenlose C# Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des C# Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Was sind globale Abfragefilter?
Globale Abfragefilter sind WHERE-Prädikate, die automatisch auf jede LINQ-Abfrage für einen bestimmten Entitätstyp angewendet werden. Sie eignen sich ideal für Soft Delete, Mandantenfähigkeit und Sicherheit auf Zeilenebene – ohne die Bedingung überall zu wiederholen.
Soft Delete mit einem globalen Filter
Fügen Sie ein IsDeleted-Flag hinzu und konfigurieren Sie einen globalen Filter, damit gelöschte Datensätze niemals zurückgegeben werden, es sei denn, Sie ignorieren den Filter ausdrücklich.
// 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();Mandantenfähigkeit mit einem globalen Filter
Verwenden Sie einen Filter, der den aktuellen Mandanten aus einem Scoped Service liest. Jede Abfrage wird automatisch auf die Daten des aktuellen Mandanten beschränkt.
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 TenantIdGlobale Filter ignorieren
Manchmal müssen Sie den Filter umgehen – etwa bei Administratorabfragen für alle Mandanten oder beim Wiederherstellen eines per Soft Delete gelöschten Datensatzes. Verwenden Sie dafür IgnoreQueryFilters(), und zwar nur für diese Abfrage.
// 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 für Navigationseigenschaften
Globale Filter wirken sich auch auf JOINs aus. Wenn Posts über einen Filter verfügen, werden verknüpfte Posts, die über Include geladen werden, ebenfalls automatisch gefiltert.
// 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 JOINOwned Entity Types: Konzept
Owned Entity Types sind Klassen, die ausschließlich zu einer einzelnen übergeordneten Entität gehören. Sie besitzen keinen Primärschlüssel und werden standardmäßig in derselben Tabelle gespeichert. Sie eignen sich ideal für Value Objects im 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; } = "";
}Owned Entities konfigurieren
Verwenden Sie OwnsOne, um eine einzelne Owned Entity zu konfigurieren. EF Core versieht Spalten standardmäßig mit dem Präfix des Namens der Navigation Property.
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, ...Owned Entities in einer separaten Tabelle
Verwenden Sie ToTable() innerhalb von OwnsOne, um Owned Entities in einer eigenen Tabelle zu speichern – nützlich, wenn die Daten umfangreich oder optional sind.
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 Sammlungen von Value Objects
OwnsMany konfiguriert eine Sammlung von Owned Entities – beispielsweise eine Liste von E-Mail-Adressen oder Telefonnummern, die in einer untergeordneten Tabelle gespeichert wird.
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 im Vergleich zu Owned Entities
Globale Filter funktionieren auch gut mit TPH-Vererbung (Table-Per-Hierarchy), bei der eine Diskriminatorspalte die Untertypen unterscheidet. Wenden Sie für typspezifische Abfragen einen Filter auf Grundlage des Diskriminators an.
// 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'Praxisbeispiel: Mandantenisolierung in SaaS
Eine vollständige SaaS-Lösung für die Mandantenfähigkeit kombiniert globale Abfragefilter mit einem Scoped Tenant Context. Dadurch wird jede Abfrage automatisch auf den jeweiligen Mandanten beschränkt, ohne Code für einzelne Abfragen zu benötigen.
// 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'Kurzer Check
Welche Methode rufen Sie auf, um einen globalen Abfragefilter für eine bestimmte Abfrage vorübergehend zu umgehen?
Zusammenfassung: Globale Filter und Owned Entities
Wichtigste Erkenntnisse:
- Globale Abfragefilter fügen jeder Abfrage für eine Entität automatisch WHERE-Prädikate hinzu
- Ideal für Soft Delete, Mandantenfähigkeit und Sicherheit auf Zeilenebene
- Mit
IgnoreQueryFilters()für Administrator- oder Wiederherstellungsvorgänge umgehen - Owned Entities modellieren Value Objects – kein Primärschlüssel, Speicherung in der Tabelle des Besitzers
OwnsOne/OwnsManykonfigurieren Owned-Beziehungen; mitToTable()auf mehrere Tabellen aufteilen
Häufig gestellte Fragen
Ist die Lektion „Globale Abfragefilter und Owned Entities“ kostenlos?
Ja — der vollständige Text von „Globale Abfragefilter und Owned Entities“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des C# Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Globale Abfragefilter und Owned Entities“?
Setzen Sie Mandantentrennung und Soft Delete mit globalen Abfragefiltern um und bilden Sie Value Objects mithilfe von Owned Entity Types ab. Du übst C# Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um C# Academy zu starten?
Keine Vorkenntnisse erforderlich. C# Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Globale Abfragefilter und Owned Entities“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser C# Academy-Lektion Code schreiben und ausführen?
Ja. Jede C# Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Eager-, Lazy- und Explicit-Loading
- Rohes SQL, Stored Procedures und Interpolation
- Kompilierte Abfragen und Performance
- Globale Abfragefilter und Owned Entities