
지네릭 레포지토리 패턴은 동적으로 타입을 지정할 수 있게 해 주는 패턴으로 동일한 기능을 하는 메서드를 중복 생성할 필요를 줄여 주는 장점이 있습니다. 하지만 지네릭 레포지토리 패턴을 사용할 경우 엔티티프레임 워크에서 제공하는 기능을 바로 사용할 수 없기 때문에 연관된 테이블의 데이터를 가져오기 위해 추가 작업이 필요한데, 다양한 패턴 중 스페시피케이션이라는 패턴을 사용하여 연관된 데이터를 가져오는 방법을 살펴 보겠습니다.
목차
프로젝트 설정
애플리케이션 생성
.NET web-API 생성하기
개발도구 설치하기 .NET을 이용하여 웹애플리케이션을 만들기 위해서는 .NET에서 제공하는 개발도구가 필요합니다. 개발도구 다운로드는 아래 링크에서 가능합니다. .NET | Free. Cross-platform. Open Sour
jin-co.tistory.com
데이터베이스 관계추가하기
데이터베이스 관계 추가하기
엔티티프레임워크를 사용하여 테이블간에 관계를 추가 해 보겠습니다. 애플리케이션 생성 먼저 .NET 애플리케이션을 생성합니다. 생성하기 .NET web-API 생성하기 개발도구 설치하기 .NET을 이용하
jin-co.tistory.com
해당 보기에서는 아래처럼 엔티티 세 개를 생성하였습니다.



▶ 베이스 엔티티 추가
베이스 엔티티는 각 개체에 공통 속성을 지정하고 지네릭 인터페이스에서 타입제한에 사용됩니다.
엔티티 폴더에 베이스 엔티티 클래스 생성

각 속성에 공통으로 적용되는 속성을 베이스 엔티티에 지정


각 엔티티에 베이스 엔티티를 부모로 추가하고 베이스 엔티티에 지정된 공통 속성제거

▶ 테스트용 데이터 파일
레포지토리 패턴 구성하기
서버 아키텍처 - 레포지토리 (인터페이스 사용)
인터페이스는 필요한 사항을 규정하는 일종의 계약서로 레포지토리 패턴에 인터페이스를 추가해 보겠습니다. 프로젝트 구성하기 프로젝트 생성 및 기본구조 구성 서버 아키텍처 - 프로젝트 분
jin-co.tistory.com
지네릭 레포지토리 패턴 구성하기
서버 아키텍처 - 지네릭 레포지토리
레포지토리는 정적 타입을 가지는 데 이는 엔티티가 증가할 때마다 레포지토리를 추가로 생성해야 하는 불편함을 줍니다. 지네릭은 동적으로 타입을 지정하는 기능으로 지네릭 레포지토리를
jin-co.tistory.com
▶ 지네릭 인터페이스 타입제한 수정
코어 폴더에 지정한 인터페이스폴더로 이동하여, 지네릭 인터페이스에 타입제한 조건을 베이스 엔티티로 교체

▶ 지네릭 레포지토리 타입제한 수정
인프라스트럭처 폴더로 이동하여, 지네릭 레포지토리에 타입제한 조건을 베이스 엔티티로 교체

구현하기
지네릭 인터페이스에 메서드 추가하기
인터페이스에 각 리스트를 불러오는 메서드를 <T>을 활용하는 하나의 메서드로 교체합니다

지네릭 인터페이스 구현
레포티토리로 이동하여 추가된 메서드를 구현하고

메서드에 필요한 코드작성. 이때, 각 엔티티 타입변경은 아래 코드를 활용해서 합니다
.Set<T>()

서비스 설정
Program.cs 파일에 아래와 같이 서비스를 등록합니다
builder.Services.AddScoped(typeof(IGenericRepo<>), typeof(GenericRepo<>));

컨트롤러에 메서드 추가
추가되는 엔드포인트의 경로는 아래처럼 괄호 안에 지정합니다
[HttpGet("targets")]
컨트롤러로 이동하여 컨스트럭터를 업데이트합니다. 컨트롤러에서 호출 시 타입구분이 필요하므로 지네릭 레포지토리에 사용된 모든 개체를 인젝션 합니다.
private readonly IGenericRepo<Item> _itemRepo;
private readonly IGenericRepo<ItemTarget> _itemTargetRepo;
private readonly IGenericRepo<ItemType> _itemTypeRepo;
public StoreController(
IGenericRepo<Item> itemRepo,
IGenericRepo<ItemTarget> itemTargetRepo,
IGenericRepo<ItemType> itemTypeRepo)
{
this._itemTypeRepo = itemTypeRepo;
this._itemTargetRepo = itemTargetRepo;
this._itemRepo = itemRepo;
}
각 엔티티 호출 메서드를 수정합니다



실행
작업이 완료되면 API 폴더로 이동하여
cd /API
아래 명령어로 앱을 실행합니다.
dotnet watch
스웨거를 사용하는 경우 아래처럼 바로 추가된 엔트포인트 확인이 가능합니다.

각 엔트포인트를 열고 실행하면 반환되는 정보확인이 가능합니다.



주 엔티티에 포함된 보조 엔티티 값 불러오기 (스페시피케이션 패턴)
주 엔티티에서 참조하는 정보들이 널값으로 뜨는데 해당 데이터를 포함하려면 주 엔티티에서 데이터를 요청할 때 포함된 참조되는 엔티티도 추가해 주어야 합니다.
다만, 지네릭 패턴은 인클루드 구문을 지원하지 않기 때문에 다른 방법을 사용해야 하는데요. 지네릭 패턴을 명확하게 해 주고 옵션 추가가 가능한 스페시피케이션 패턴을 통해 구현해 보겠습니다.

일반 동기방식의 리스트의 경우 옵션 추가가 불가능하므로 먼저, 비동기 방식으로 메서드를 교체합니다
비동기 방식 구현하기
관계형 데이터 불러오기 - 지네릭 레포지토리 패턴 (비동기 방식)
엔티티프레임워크에서 데이터 요청 시 비동기 방식을 사용하는 방법을 보겠습니다. 프로젝트 구성하기 애플리케이션 생성 .NET web-API 생성하기 개발도구 설치하기 .NET을 이용하여 웹애플리케이
jin-co.tistory.com
스페시피케이션 설정
코어 폴더에 스페시피케이션 폴더를 만듭니다

생성된 폴더에 스페시피케이션 인터페이스를 생성

지네릭 타입을 추가하고

지네릭 문법을 가지는 메서드를 추가합니다 (보기에서는 개별아이템을 반환하는 메서드와 전체리스트를 반환하는 메서드를 추가)
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }

▶ 베이스 스페시피케이션
같은 폴더에 인터페이스를 구현할 클래스 생성하고

인터페이스를 구현합니다.

구현된 메서드를 {get;}으로 교체, 인클루드 메서드에 초기값을 아래처럼 지정하고 매개변수를 가지지 않는 컨스트럭터와 조건을 매개변수로 갖는 컨스트럭터를 추가합니다
new List<Expression<Func<T, object>>>();

개체를 추가하는 메서드를 추가
protected void AddInclude(Expression<Func<T, object>> includeExpression) {
Includes.Add(includeExpression);
}

인프라스트럭처 폴더로 이동하여 인클루드를 생성할 클래스 생성하고

타입 및 타입제한설정한 뒤

아래 스태틱 메서드를 추가합니다
public static IQueryable<T> GetQuery(IQueryable<T> input, ISpec<T> spec)
{
var query = input;
if (spec.Criteria != null)
{
query = query.Where(spec.Criteria);
}
query = spec.Includes.Aggregate(query, (entity, expression) => entity.Include(expression));
return query;
}

지네릭인터페이스에 기존 메서드에 생성한 스페시피케이션을 매개변수로 추가합니다
Task<List<T>> GetListAsync(ISpec<T> spec);
Task<T> GetItem(ISpec<T> spec);

지네릭 레포지토리 클래스에서

스페시피케이션을 적용하는 메서드를 추가합니다
private IQueryable<T> ApplySpec(ISpec<T> spec)
{
return SpecEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
기존에 대이터를 불러오는 메서드에 스페시피케이션을 매개변수로 추가하고 새로 생성한 메서드를 통해 스페시피케이션을 전달합니다
public async Task<List<T>> GetListAsync(ISpec<T> spec)
{
return await ApplySpec(spec).ToListAsync();
}

▶ 리스트 스페시피케이션 (인클루드)
코어폴더의 스페시피케이션 폴더에 아이템 스페시피케이션 클래스 생성

베이스 스페시피케이션을 부모로 추가

컨스트럭터를 추가하고 인클루드 구문추가
public ItemWithTargetAndTypeSpec()
{
AddInclude(x => x.ItemTarget);
AddInclude(x => x.ItemType);
}

컨트롤러 업데이트
컨트롤러로 이동하여 스페시피케이션 매개변수를 추가한 메서드로 교체하고 설정한 스페시피케이션 클래스를 매개변수로 전달합니다
[HttpGet]
public async Task<List<Item>> GetItems()
{
var spec = new ItemWithTargetAndTypeSpec();
return await _itemRepo.GetListAsync(spec);
}

▶ 개별 아이템 가져오기
개별아이템을 가져오는 방법도 보겠습니다.
위에서 미리 구현해 둔 'GetItem' 메서드를 스페시피케이션을 사용하도록 수정합니다
public async Task<T> GetItem(ISpec<T> spec)
{
return await ApplySpec(spec).FirstOrDefaultAsync();
}

아이템 스페시피케이션 클래스로 이동하여 매개변수가 추가된 컨스트럭처를 추가합니다

생성된 컨스트럭터의 매개변수와 지네릭 변수를 개별아이템을 가져오는데 필요한 아이디로 교체하고 다른 엔티티를 추가하는 인클루드문을 추가합니다
public ItemWithTargetAndTypeSpec(int id) : base(x => x.Id == id) {
AddInclude(x => x.ItemTarget);
AddInclude(x => x.ItemType);
}

컨트롤러로 이동하여 개별아이템을 가져오는 엔드포인트를 추가하고 개별아이템과 참조하는 엔티티를 함께 반환하는 코드를 추가합니다
[HttpGet("{id}")]
public async Task<ActionResult<Item>> GetItem(int id)
{
var spec = new ItemWithTargetAndTypeSpec(id);
return await _itemRepo.GetItem(spec);
}


이상으로 관계형 데이터베이스에서 지네릭 레포지토리 패턴을 활용하여 참조한 데이터를 포함하여 가져오는 방법을 보았습니다.
'백엔드 > 닷넷' 카테고리의 다른 글
데이터 트랜스퍼 옵젝트 (DTO) (0) | 2023.04.17 |
---|---|
관계형 데이터 불러오기 - 비동기 방식 (2) | 2023.04.16 |
관계형 데이터 불러오기 (참조 데이터 포함) - 레포지토리 패턴 (0) | 2023.04.10 |
서버 아키텍처 - 지네릭 레포지토리 (0) | 2023.04.09 |
서버 아키텍처 - 레포지토리 (인터페이스 사용) (0) | 2023.04.07 |