Programing

각 객체에 대해 일반 저장소와 특정 저장소를 작성하는 이점

lottogame 2020. 7. 1. 08:02
반응형

각 객체에 대해 일반 저장소와 특정 저장소를 작성하는 이점


ASP.NET MVC 응용 프로그램을 개발 중이며 이제 리포지토리 / 서비스 클래스를 구축하고 있습니다. 모든 리포지토리가 구현하는 일반 IRepository 인터페이스를 만드는 것과 각 리포지토리에 고유 한 인터페이스와 메서드 집합이있는 주요 이점이 있는지 궁금합니다.

예를 들어, 일반적인 IRepository 인터페이스는 다음과 같습니다 ( 이 답변 에서 가져옴 ).

public interface IRepository : IDisposable
{
    T[] GetAll<T>();
    T[] GetAll<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
    void Delete<T>(T entity);
    void Add<T>(T entity);
    int SaveChanges();
    DbTransaction BeginTransaction();
}

각 리포지토리는이 인터페이스를 구현합니다 (예 :

  • 고객 리포지토리 :이 리포지토리
  • 제품 리포지토리 :이 리포지토리
  • 기타

이전 프로젝트에서 수행 한 대안은 다음과 같습니다.

public interface IInvoiceRepository : IDisposable
{
    EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
    EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
    InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
    InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
    InvoiceEntity CreateInvoice();
    InvoiceLineEntity CreateInvoiceLine();
    void SaveChanges(InvoiceEntity); //handles inserts or updates
    void DeleteInvoice(InvoiceEntity);
    void DeleteInvoiceLine(InvoiceLineEntity);
}

두 번째 경우, 표현식 (LINQ 또는 기타)은 리포지토리 구현에 완전히 포함됩니다. 서비스를 구현하는 사람은 호출 할 리포지토리 함수를 알아야합니다.

서비스 클래스에서 모든 표현식 구문을 작성하고 리포지토리로 전달하는 이점을 보지 못합니다. 이것은 messup하기 쉬운 LINQ 코드가 많은 경우에 복제되고 있다는 것을 의미하지 않습니까?

예를 들어 기존 인보이스 발행 시스템에서는

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

몇 가지 다른 서비스 (고객, 송장, 계정 등)에서 여러 곳에서 다음을 작성하는 것보다 훨씬 깔끔해 보입니다.

rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);

특정 접근 방식을 사용할 때의 유일한 단점은 Get * 함수의 많은 순열로 끝날 수 있다는 것입니다. 그러나 여전히 식 논리를 서비스 클래스로 푸시하는 것이 바람직합니다.

내가 무엇을 놓치고 있습니까?


이것은 리포지토리 패턴만큼 오래된 문제입니다. IQueryable쿼리의 균일 한 표현 인 LINQ가 최근 소개되면서이 주제에 대해 많은 논의가있었습니다.

나는 일반적인 저장소 프레임 워크를 구축하기 위해 열심히 노력한 후에 특정 저장소를 선호합니다. 어떤 영리한 메커니즘을 시도하더라도 항상 같은 문제가 발생했습니다. 리포지토리는 모델링되는 도메인의 일부이며 해당 도메인은 일반적인 것이 아닙니다. 모든 엔터티를 삭제할 수있는 것은 아니며 모든 엔터티를 추가 할 수있는 것은 아니며 모든 엔터티에 리포지토리가있는 것은 아닙니다. 쿼리는 매우 다양합니다. 저장소 API는 엔티티 자체만큼 고유합니다.

내가 자주 사용하는 패턴은 특정 저장소 인터페이스를 갖지만 구현을위한 기본 클래스를 갖는 것입니다. 예를 들어 LINQ to SQL을 사용하여 다음을 수행 할 수 있습니다.

public abstract class Repository<TEntity>
{
    private DataContext _dataContext;

    protected Repository(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    protected IQueryable<TEntity> Query
    {
        get { return _dataContext.GetTable<TEntity>(); }
    }

    protected void InsertOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().InsertOnCommit(entity);
    }

    protected void DeleteOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
    }
}

Replace DataContext with your unit-of-work of choice. An example implementation might be:

public interface IUserRepository
{
    User GetById(int id);

    IQueryable<User> GetLockedOutUsers();

    void Insert(User user);
}

public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(DataContext dataContext) : base(dataContext)
    {}

    public User GetById(int id)
    {
        return Query.Where(user => user.Id == id).SingleOrDefault();
    }

    public IQueryable<User> GetLockedOutUsers()
    {
        return Query.Where(user => user.IsLockedOut);
    }

    public void Insert(User user)
    {
        InsertOnCommit(user);
    }
}

Notice the public API of the repository does not allow users to be deleted. Also, exposing IQueryable is a whole other can of worms - there are as many opinions as belly buttons on that topic.


I actually disagree slightly with Bryan's post. I think he's right, that ultimately everything is very unique and so on. But at the same time, most of that comes out as you design, and I find that getting a generic repository up and using it while developing my model, I can get an app up very quickly, then refactor to greater specificity as I find the need to do so.

So, in cases like that, I have often created a generic IRepository that has the full CRUD stack, and that lets me get quickly to playing with the API and letting folks play w/ the UI and do integration & user acceptance testing in parallel. Then, as I find I need specific queries on the repo, etc, I start replacing that dependency w/ the specific one if needed and going from there. One underlying impl. is easy to create and use (and possibly hook to an in-memory db or static objects or mocked objects or whatever).

That said, what I have started doing lately is breaking up the behavior. So, if you do interfaces for IDataFetcher, IDataUpdater, IDataInserter, and IDataDeleter (for example) you can mix-and-match to define your requirements through the interface and then have implementations that take care of some or all of them, and I can still inject the does-it-all implementation to use while I'm building the app out.

paul


I prefer specific repositories which derives from generic repository (or list of generic repositories to specify exact behavior) with overridable method signatures.


Have a generic repository that is wrapped by a specific repository. That way you can control the public interface but still have the advantage of code-reuse that comes from have a generic repository.


public class UserRepository : Repository, IUserRepository

Shouldn't you inject IUserRepository to avoid exposing the interface. As people have said, you may not need the full CRUD stack etc.

참고URL : https://stackoverflow.com/questions/1230571/advantage-of-creating-a-generic-repository-vs-specific-repository-for-each-obje

반응형