ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C# Dictionary와 LINQ로 구현하는 고객 유형별 상품 타입 필터링 패턴
    닷넷/C# 2025. 6. 30. 11:56
    반응형

    1. 개요

    고객 유형(CustomerType)에 따라 시스템에서 제공해야 하는 ItemType 목록을 동적으로 필터링하는 로직을 구현한 경험을 공유한다.

    • 목표: VIP, Regular 등 고객 유형별로 기본 제공되는 상품 타입을 정의하고, 특정 권한(Permission) 값에 따라 필터링 규칙을 적용
    • 장점: 타입 안전성 보장, 유지보수성 향상, 코드 가독성 및 확장성 확보

    2. 요구사항

    1. 고객 유형별 기본 상품 타입
      • Regular: A, B, C, D
      • VIP: A, B, E, F, G, H, I, J
    2. Permission이 “20”인 경우
      • 위 허용 목록에 포함된 ItemType만 노출
    3. 그 외 Permission
      • 전체 ItemType 노출
    4. 향후 고객 유형 추가허용 항목 변경이 용이해야 함.

    3. 설계 아이디어

    • 고객 유형별 허용 ItemType을 Dictionary<CustomerType, HashSet<ItemType>>로 중앙집중 관리
    • 데이터베이스에서 가져온 Code 객체(CodeID: string, Permission: string)를 LINQ로 간단히 필터링
    • Enum.TryParse + HashSet.Contains 조합으로 엄격한 타입 검증 및 빠른 조회
    • 조건문(Permission != "20" || …) 하나로 “그 외” 케이스도 자연스럽게 처리

    4. 주요 코드 예시

    /// <summary>
    /// 고객 타입별 기본 상품 타입 목록을 정의합니다.
    /// </summary>
    private readonly Dictionary<CustomerType, HashSet<ItemType>> _allowedItemTypes;
    
    public MyServiceConstructor()
    {
        _allowedItemTypes = new()
        {
            [CustomerType.Regular] = new HashSet<ItemType>
            {
                ItemType.A, ItemType.B, ItemType.C, ItemType.D
            },
            [CustomerType.Vip] = new HashSet<ItemType>
            {
                ItemType.A, ItemType.B, ItemType.E, ItemType.F,
                ItemType.G, ItemType.H, ItemType.I, ItemType.J
            }
        };
    }
    
    public IEnumerable<ItemType> GetItemTypeList(CustomerType customerType)
    {
        // 1) DB에서 전체 코드 리스트 조회
        IEnumerable<Code> codes = _codeService.Value
            .SelectCode(SystemConstants.ModuleTypeA, SystemConstants.CodeTypeA);
    
        // 2) 고객 유형에 매핑된 허용 목록이 있으면 필터링 적용
        if (_allowedItemTypes.TryGetValue(customerType, out var allowedEnums))
        {
            codes = codes.Where(x =>
                // Permission이 "20"이 아니면 모두 포함
                x.Permission != "20"
                // Permission이 "20"이면, 파싱 성공 & 허용 목록에 포함된 항목만 포함
                || (Enum.TryParse<ItemType>(x.CodeID, ignoreCase: true, out var parsed)
                    && allowedEnums.Contains(parsed))
            );
        }
    
        // 3) Code → ItemType 변환
        var result = codes
            .Select(code => (ItemType)Enum.Parse(typeof(ItemType), code.CodeID, ignoreCase: true))
            .ToList();
    
        return result;
    }

    5. 코드 설명

    1. 맵 초기화
      • 생성자에서 Dictionary<CustomerType, HashSet<ItemType>>를 초기화
      • 신규 고객 유형 추가 시 이곳만 수정하면 됨
    2. 필터 로직
      • TryGetValue로 고객 유형에 대한 허용 목록을 가져옴
      • LINQ Where 내부에서 한 줄로 두 가지 케이스를 처리
        • x.Permission != "20": 모든 코드 통과
        • x.Permission == "20": Enum.TryParse로 CodeID를 ItemType으로 변환 후, 허용 목록 검사
    3. 최종 반환
      • 필터링된 Code 리스트를 ItemType 리스트로 변환

    6. 장단점 및 확장 포인트

    구분장점유의/확장 포인트
    유지보수성 중앙집중형 맵으로 허용 항목 관리 → 변경 시 한 곳만 수정 가능 맵이 커지면 초기화 코드가 길어질 수 있음
    성능 HashSet → O(1) 포함 검사, Enum.TryParse 오버헤드 최소화 아주 큰 규모(수만 건 이상)라면 DB 레벨 필터링 고려
    타입 안정성 string → enum 파싱 후 검사 → 잘못된 값 걸러짐 Enum.Parse 실패 시 예외 방지 로직 추가 고민 가능
    확장성 신규 CustomerType 추가/허용 목록 변경 손쉽게 대응 고객별 커스텀 로직이 복잡해지면 전략 패턴 도입 고려
     

    7. 결론

    이 방식은 간단한 사전(Dictionary) + LINQ 조합만으로도 복잡한 비즈니스 로직을 깔끔하게 표현할 수 있었다.

    • 소규모 프로젝트: 코드 한 줄, 맵 수정만으로 요구사항 반영 가능
    • 대규모 시스템: 이후 DB 매핑 테이블이나 Specification 패턴 등으로 리팩토링해도 기반 아이디어는 동일
    반응형

    댓글

Designed by Tistory.