문제

저장소 패턴을 사용할 때 복잡한 객체 그래프에 대한 간절한로드 문제를 올바르게 처리하는 방법이 궁금합니다. 이것은 내가 생각하는 ORM 특정 문제가 아닙니다.

첫 시도:

public interface IProductRepository : IRepository<Product>
{
  Product GetById(int id);
  IProductRepository WithCustomers();
}

이것은 제대로 작동하지만 항상 자신을 반복하는 것이 포함됩니다 (모든 곳에서 저장소 구현 방법으로 사용자 정의 작성 '.

다음 접근법 :

public interface IRepository<T> where T : IAggregateRoot
{
  ...
  void With(Expression<Func<T, object>> propToExpand);
}

With 메소드는 개인 수집에 항목을 추가합니다. 나중에 필요한 엔티티/IE를 검색 할 때 어떤 소품을 열망 해야하는지 알아 내기 위해 나중에 사용됩니다.

이런 종류는 작동하며 괜찮습니다. 그러나 나는 사용을 싫어한다 :

productRepository.With(x=>x.Customer);
productRepository.With(x=>x.Price);
productRepository.With(x=>x.Manufacturer);
var product = productRepository.GetById(id);

기본적으로 - 문제는 체인이 없다는 것입니다. 나는 다음과 같이하고 싶습니다.

var product = productRepository
  .With(x=>x.Customer)
  .With(x=>x.Price)
  .With(x=>x.Manufacturer)
  .GetById(id);

나는 이것을 달성 할 수 없었다. 내가 할 수 있더라도 - 그 솔루션이 우아할지 확실하지 않습니다.

이것은 내가 근본적인 것을 놓치고 있다는 생각으로 이어집니다 (어디서나 사례 부족). 이것을 처리하는 방법이 다릅니다. 모범 사례는 무엇입니까?

도움이 되었습니까?

해결책

흥미로운 문제와 나는 당신이 이것에 문제가있는 첫 번째 문제가 아니라고 확신합니다 (나는 절대적으로 가지고 있습니다).

나를 위해, 진짜 질문은 : 당신의 열렬한 로딩 논리를 어디에두고 싶습니까?

클라이언트 코드의 저장소 외부

var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);

나는 그것이 좋은 소프트웨어 디자인이라고 생각하지 않습니다. 그러한 구성이 전체 앱을 통해 흩어져 있다면 이것이 "수천 컷으로 죽음"을 일으킬 수있는 것처럼 보입니다.

또는 저장소 내에서. 예시:

interface IProductRepository {
    Product GetById(int id);
    Product GetByIdWithCustomers(int i);
}

따라서 클라이언트 코드는 다음과 같습니다.

var product = productRepository.GetByIdWithCustomers(id);

일반적으로 기본 CRUD 작업이 정의 된 하나의 기본 위치를 만듭니다.

public class BaseRepository<TEntity, TPrimaryKey> {
    public void Save(TEntity entity) { ... }
    public void Delete(TEntity entity) { ... }
    public TEntity Load(TPrimaryKey id) { ... } // just gets the entity by primary key
}

그런 다음 도메인 개체를 가져 오기위한 특정 방법을 제공하기 위해이 기본 클래스 / 인터페이스를 확장합니다. 당신의 접근 방식은 다소 비슷한 방향으로 진행되는 것 같습니다.

public class MediaRepository : BaseRepository<Media, int> {
    public long CountMediaWithCategories() { ... }
    public IList<Media> MediaInCategories(IList<Category> categories) { .... }
}

좋은 점 : 모든 ORM 물건 (열렬한로드 구성, 페치 깊이 등)이 리포지토리 클래스에 캡슐화되면 클라이언트 코드는 결과 세트를 얻습니다.

나는 당신이하려고하는 것처럼 매우 일반적인 저장소로 작업을 시도했지만 주로 도메인 객체에 대한 특정 쿼리와 저장소를 작성하게되었습니다.

다른 팁

var product = productRepository
 .With(x=>x.Customer)
 .With(x=>x.Price)
 .With(x=>x.Manufacturer)
 .GetById(id);

위와 같은 객체 그래프의 쿼리 깊이를 결정하려는 당신의 소망을 이해할 수 있지만 더 쉬운 방법이있을 수 있다고 생각합니다. ID로 제품 (고객, 가격 및 제조업체와 함께)을 반환하기로 선택하는 대신 제품을 반환하는 것은 어떻습니까? 다른 모든 것들은 제품의 게으른로드 특성입니다.

데이터 액세스 계층의 Poco Object Model에 의해 '체인'으로이 '완전한 그래프 접근성'을 달성합니다. 이런 식으로 한 번에 얼마나 많은 간절한로드 된 데이터를 뽑아 낼 수 있는지 알 필요가 없습니다. 객체 그래프에서 필요한 것을 요구하며 모델은로드 된 내용과 DAL에서 추가로 복구해야 할 사항을 알고 있습니다. 보세요 이것들 답변 - 나는 거기에서 내 접근 방식을 설명하려고 노력합니다. 더 많은 설명이 필요하면 알려 주시면이 답변을 편집하겠습니다.

오래된 질문이지만 아마도 누군가를 도울 수 있습니다. 나는 좋은 aproach를 찾기 위해 언젠가 보냈다. 여기 C#에서 찾은 내용이있다.

irepository.cs :

public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , params Expression<Func<TEntity, object>>[] properties);
}

저장소 .CS

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{    
    private readonly DbSet<TEntity> _dbset;

    public Repository(DbSet<TEntity> dbset)
    {
        _dbset = dbset;
    }

    public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , Expression<Func<TEntity, object>>[] properties)
    {
        if (where == null) 
            throw new ArgumentNullException(nameof(where));    
        if (properties == null) 
            throw new ArgumentNullException(nameof(properties));

        var query = _dbset as IQueryable<TEntity>; // _dbSet = dbContext.Set<TEntity>()

        query = properties
                   .Aggregate(query, (current, property) => current.Include(property));

        return query.AsNoTracking().Where(where).ToList();
    }
}

사용하는 방법:

var repository = new Repository<User>();
var users = repository.GetAll(p => p.Id == 1, d => d.Address, d => d.Carts);

ref : 링크

나는 당신이하려는 일에 감사 할 수 있지만, 당신은 기본 저장소 패턴을 넘어서는 것입니다.

최소 리포지토리 인터페이스에는 다음을위한 방법이 포함될 수 있습니다.

  • Getbyid
  • 추가하다
  • 제거하다

그 위에 추가 메소드를 추가하면 인터페이스가 모든 집계 루트에 반드시 의미가없는 상황으로 들어가기 시작합니다.

때로는 완전히 아름다운 API를 갖는 것은 불가능합니다. 당신이 가진 것이 당신을 위해 "충분히"일한다면, 나는 그것과 함께 갈 것입니다. 더 나은 API를 제공하기 위해 리포지토리 패턴에서 벗어나야한다면 프로그램을 제공하십시오!

리포지토리 패턴은 모든 / 종료 솔루션이 아닙니다. 때로는 다른 솔루션이 필요합니다.

저장소 외부에 필요한 모든 내용을 표시하려면 각 일반 방법에 대해 옵션 매개 변수 (C#)를 나열 할 수 있습니다.

TEntity Find(Func<TEntity, bool> expression, params string[] eagerLoads);

그런 다음 클라이언트 계층에서 :

IProductRepository.Find(x => x.Id == id, "Customer", "Price")

안전한 타입이 되려면 엔티티를 열거하십시오.

public enum BusinessEntities { Customer, Price, Manufacturer }

IProductRepository.Find(x => x.Id == id, BusinessEntities.Customer.ToString(), BusinessEntities.Price.ToString())

나는 원하는 것을 구체적으로 묻는 것이 고객의 책임이라고 생각합니다. 일반 저장소는 기본 CRUD를 처리해야합니다.

에서 BaseRepository.cs 이 방법을 만들 수 있습니다.

public async Task<IEnumerable<T>> GetWithChild(string child)
{
    return await _entities.Include(child).ToListAsync();
}

내 API에서도 서비스 계층도 구현했지만 API 에서이 방법을 호출하여 변수 이름을로드 할 수 있습니다.

분명히, 당신의 상황에서는 몇 줄을 더 포함시켜야합니다.

나는 이전에 답을 게시했지만 여전히 해결책에 만족하지 않았습니다. 여기에 더 나은 솔루션이 있습니다.

Baserepository에서 CS

public async Task<IEnumerable<T>> GetAll(params Expression<Func<T, object>>[] properties)
{
      IQueryable<T> query = _entities;

      query = properties.Aggregate(query, (current, property) => current.Include(property));

      return await query.AsNoTracking().ToListAsync();
}

그리고 방법을 다음과 같이 사용할 수 있습니다

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top