전역 쿼리 필터와 소유 엔터티
전역 쿼리 필터로 테넌트 격리와 소프트 삭제를 적용하고 소유 엔터티 형식으로 값 객체를 매핑합니다.
전역 쿼리 필터와 소유 엔터티은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
전역 쿼리 필터란 무엇입니까?
전역 쿼리 필터는 특정 엔터티 형식에 대한 모든 LINQ 쿼리에 자동으로 적용되는 WHERE 조건식입니다. 어디에서나 조건을 반복하지 않고도 소프트 삭제, 멀티 테넌시, 행 수준 보안을 구현하는 데 적합합니다.
전역 필터를 사용한 소프트 삭제
IsDeleted 플래그를 추가하고 전역 필터를 구성하면, 필터를 명시적으로 무시하지 않는 한 삭제된 레코드가 반환되지 않습니다.
// 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();전역 필터를 사용한 멀티 테넌시
범위가 지정된 서비스에서 현재 테넌트를 읽는 필터를 사용하십시오. 모든 쿼리는 자동으로 현재 테넌트의 데이터 범위에 한정됩니다.
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전역 필터 무시
관리자가 모든 테넌트를 조회하거나 소프트 삭제된 레코드를 복원하는 경우처럼 필터를 우회해야 할 때가 있습니다. 해당 쿼리에만 IgnoreQueryFilters()를 사용하십시오.
// 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();
}탐색 속성의 필터
전역 필터는 JOIN에도 적용됩니다. 게시물 엔터티에 필터가 있으면 Include를 통해 로드한 관련 게시물에도 자동으로 필터가 적용됩니다.
// 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소유 엔터티 형식: 개념
소유 엔터티 형식은 하나의 소유 엔터티에만 독점적으로 속하는 클래스입니다. 기본 키가 없으며 기본적으로 같은 테이블에 저장됩니다. 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; } = "";
}소유 엔터티 구성
OwnsOne을 사용하여 단일 소유 엔터티를 구성하십시오. EF Core는 기본적으로 탐색 속성 이름을 열 이름 앞에 붙입니다.
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, ...별도 테이블의 소유 엔터티
OwnsOne 내부에서 ToTable()을 사용하면 소유 엔터티를 자체 테이블에 저장할 수 있습니다. 데이터가 크거나 선택 사항일 때 유용합니다.
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
OwnsMany는 소유 엔터티 컬렉션을 구성합니다. 예를 들어 자식 테이블에 저장된 이메일 주소 목록이나 전화번호 목록에 사용할 수 있습니다.
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"));계층별 테이블과 소유 엔터티 비교
전역 필터는 TPH(계층별 테이블) 상속과도 잘 작동하며, 이 상속에서는 판별자 열이 하위 형식을 구분합니다. 형식별 쿼리에는 판별자를 기준으로 필터를 적용하십시오.
// 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'실전: 서비스형 소프트웨어의 테넌트 격리
완전한 서비스형 소프트웨어 멀티 테넌시 설정은 전역 쿼리 필터와 범위가 지정된 테넌트 컨텍스트를 결합합니다. 따라서 쿼리별 코드를 작성하지 않아도 모든 쿼리가 자동으로 해당 테넌트의 범위에 한정됩니다.
// 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'빠른 확인
특정 쿼리에서 전역 쿼리 필터를 일시적으로 우회하려면 어떤 메서드를 호출해야 합니까?
복습: 전역 필터와 소유 엔터티
핵심 요점:
- 전역 쿼리 필터는 엔터티에 대한 모든 쿼리에 WHERE 조건식을 자동으로 추가
- 소프트 삭제, 멀티 테넌시, 행 수준 보안에 적합
- 관리 작업이나 복원 작업에서는
IgnoreQueryFilters()로 우회 - 소유 엔터티는 값 개체를 모델링 — PK가 없고 소유자의 테이블에 저장
OwnsOne/OwnsMany로 소유 관계를 구성하고,ToTable()을 사용하여 테이블을 분리
자주 묻는 질문
“전역 쿼리 필터와 소유 엔터티” 강의는 무료인가요?
네 — “전역 쿼리 필터와 소유 엔터티” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“전역 쿼리 필터와 소유 엔터티”에서 뭘 배우나요?
전역 쿼리 필터로 테넌트 격리와 소프트 삭제를 적용하고 소유 엔터티 형식으로 값 객체를 매핑합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“전역 쿼리 필터와 소유 엔터티” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 즉시, 지연, 명시적 로딩
- 원시 SQL, 저장 프로시저와 보간
- 컴파일된 쿼리와 성능
- 전역 쿼리 필터와 소유 엔터티