본문 바로가기

백엔드/닷넷

관계형 데이터 불러오기 (참조 데이터 포함) - 지네릭 레포지토리 패턴

반응형

지네릭 레포지토리 패턴은 동적으로 타입을 지정할 수 있게 해 주는 패턴으로 동일한 기능을 하는 메서드를 중복 생성할 필요를 줄여 주는 장점이 있습니다. 하지만 지네릭 레포지토리 패턴을 사용할 경우 엔티티프레임 워크에서 제공하는 기능을 바로 사용할 수 없기 때문에 연관된 테이블의 데이터를 가져오기 위해 추가 작업이 필요한데, 다양한 패턴 중 스페시피케이션이라는 패턴을 사용하여 연관된 데이터를 가져오는 방법을 살펴 보겠습니다.

목차

프로젝트 설정

애플리케이션 생성

 

.NET web-API 생성하기

개발도구 설치하기 .NET을 이용하여 웹애플리케이션을 만들기 위해서는 .NET에서 제공하는 개발도구가 필요합니다. 개발도구 다운로드는 아래 링크에서 가능합니다. .NET | Free. Cross-platform. Open Sour

jin-co.tistory.com

데이터베이스 관계추가하기

 

데이터베이스 관계 추가하기

엔티티프레임워크를 사용하여 테이블간에 관계를 추가 해 보겠습니다. 애플리케이션 생성 먼저 .NET 애플리케이션을 생성합니다. 생성하기 .NET web-API 생성하기 개발도구 설치하기 .NET을 이용하

jin-co.tistory.com

해당 보기에서는 아래처럼 엔티티 세 개를 생성하였습니다.

▶ 베이스 엔티티 추가

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

 

엔티티 폴더에 베이스 엔티티 클래스 생성

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

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

▶ 테스트용 데이터 파일

item.json
0.00MB
itemTarget.json
0.00MB
itemType.json
0.00MB

 

레포지토리 패턴 구성하기

 

서버 아키텍처 - 레포지토리 (인터페이스 사용)

인터페이스는 필요한 사항을 규정하는 일종의 계약서로 레포지토리 패턴에 인터페이스를 추가해 보겠습니다. 프로젝트 구성하기 프로젝트 생성 및 기본구조 구성 서버 아키텍처 - 프로젝트 분

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

이상으로 관계형 데이터베이스에서 지네릭 레포지토리 패턴을 활용하여 참조한 데이터를 포함하여 가져오는 방법을 보았습니다.

 

728x90
반응형