0Pricing
C# Academy · Lección

Filtros de consulta globales y entidades propias

Aplique aislamiento por inquilino y borrado lógico con filtros de consulta globales, y asigne objetos de valor mediante tipos de entidad propios.

Filtros de consulta globales y entidades propias es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.

¿Qué son los filtros de consulta globales?

Los filtros de consulta globales son predicados WHERE que se aplican automáticamente a cada consulta de LINQ para un tipo de entidad determinado. Son perfectos para el borrado lógico, la multitenencia y la seguridad a nivel de fila, sin repetir la condición en todas partes.

Borrado lógico con un filtro global

Añada una marca IsDeleted y configure un filtro global para que los registros eliminados nunca se devuelvan, a menos que ignore explícitamente el filtro.

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

Multitenencia con un filtro global

Utilice un filtro que lea el inquilino actual desde un servicio con ámbito. Cada consulta se limita automáticamente a los datos del inquilino actual.

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

Omisión de filtros globales

A veces es necesario omitir el filtro, por ejemplo, en consultas de administración para todos los inquilinos o al restaurar un registro eliminado lógicamente. Utilice IgnoreQueryFilters() únicamente para esa consulta.

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

Filtros en propiedades de navegación

Los filtros globales también participan en las combinaciones JOIN. Si Posts tiene un filtro, los Posts relacionados cargados mediante Include también se filtran automáticamente.

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

Tipos de entidad propiedad: concepto

Los tipos de entidad propiedad son clases que pertenecen exclusivamente a una única entidad propietaria. No tienen clave principal y se almacenan en la misma tabla de forma predeterminada. Son perfectos para representar objetos de valor en 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; } = "";
}

Configuración de entidades propiedad

Utilice OwnsOne para configurar una única entidad propiedad. EF Core antepone el nombre de la propiedad de navegación a las columnas de forma predeterminada.

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

Entidades propiedad en una tabla independiente

Utilice ToTable() dentro de OwnsOne para almacenar las entidades propiedad en su propia tabla; resulta útil cuando los datos son grandes u opcionales.

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 para colecciones de objetos de valor

OwnsMany configura una colección de entidades propiedad, por ejemplo, una lista de direcciones de correo electrónico o números de teléfono almacenada en una tabla secundaria.

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"));

Jerarquía por tabla frente a entidades propiedad

Los filtros globales también funcionan bien con la herencia TPH (Table-Per-Hierarchy), en la que una columna discriminadora distingue los subtipos. Aplique un filtro basado en el discriminador para las consultas específicas de cada tipo.

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

En la práctica: aislamiento de inquilinos en SaaS

Una configuración completa de multitenencia en SaaS combina filtros de consulta globales con un contexto de inquilino con ámbito, lo que garantiza que cada consulta se limite automáticamente sin código específico para cada consulta.

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

Comprobación rápida

¿Qué método debe llamar para omitir temporalmente un filtro de consulta global en una consulta específica?

Resumen: filtros globales y entidades propiedad

Conclusiones clave:

  • Los filtros de consulta globales añaden automáticamente predicados WHERE a cada consulta de una entidad
  • Son ideales para el borrado lógico, la multitenencia y la seguridad a nivel de fila
  • Se omiten con IgnoreQueryFilters() para operaciones de administración o restauración
  • Las entidades propiedad modelan objetos de valor: no tienen clave primaria y se almacenan en la tabla del propietario
  • OwnsOne / OwnsMany configuran relaciones de propiedad; utilice ToTable() para separarlas

Preguntas frecuentes

¿La lección «Filtros de consulta globales y entidades propias» es gratis?

Sí — el texto completo de «Filtros de consulta globales y entidades propias» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Filtros de consulta globales y entidades propias»?

Aplique aislamiento por inquilino y borrado lógico con filtros de consulta globales, y asigne objetos de valor mediante tipos de entidad propios. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C# Academy?

No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Filtros de consulta globales y entidades propias»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C# Academy?

Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Carga eager, lazy y explícita
  2. SQL sin formato, procedimientos almacenados e interpolación
  3. Consultas compiladas y rendimiento
  4. Filtros de consulta globales y entidades propias
← Volver a C# Academy