[C#] 웹 요청 당 하나의 DbContext… 왜?

DbContext다양한 DI 프레임 워크를 사용하여 HTTP 웹 요청마다 하나만 작성하고 사용하도록 Entity Framework를 설정하는 방법을 설명하는 많은 기사를 읽었습니다 .

왜 이것이 처음부터 좋은 생각입니까? 이 방법을 사용하면 어떤 이점이 있습니까? 이것이 좋은 아이디어가되는 특정한 상황이 있습니까? 이 방법을 사용하여 DbContext저장소 메소드 호출 당 인스턴스를 작성할 때 수행 할 수없는 작업이 있습니까?



답변

참고 :이 답변은 Entity Framework에 대해 이야기 DbContext하지만 LINQ to SQL DataContext및 NHibernate 와 같은 모든 종류의 작업 단위 구현에 적용됩니다 ISession.

Ian을 반향하여 시작하겠습니다 DbContext. 전체 응용 프로그램에 대해 하나 를 갖는 것은 나쁜 생각입니다. 이것이 적합한 유일한 상황은 단일 스레드 응용 프로그램과 해당 단일 응용 프로그램 인스턴스에서만 사용되는 데이터베이스가있는 경우입니다. 는 DbContext스레드 안전하지하고 있기 때문에 DbContext데이터를 캐시, 그것은 오래된 곧 가져옵니다. 이렇게하면 여러 사용자 / 응용 프로그램이 해당 데이터베이스에서 동시에 작업 할 때 모든 종류의 문제가 발생합니다 (물론 매우 일반적입니다). 그러나 나는 당신이 이미 그것을 알고 있고 그것을 필요로하는 사람에게 새로운 인스턴스 (즉, 일시적인 생활 방식)를 주입하지 않는 이유를 알고 싶어 DbContext합니다. (단일 DbContext스레드 당 또는 컨텍스트 당 컨텍스트가 나쁜 이유에 대한 자세한 내용은 이 답변을 읽으십시오 ).

DbContext일시적 으로 as 를 등록하면 효과가 있지만 일반적으로 특정 범위 내에서 이러한 작업 단위의 단일 인스턴스를 가지기를 원합니다. 웹 응용 프로그램에서는 웹 요청의 경계에 이러한 범위를 정의하는 것이 실용적입니다. 따라서 웹당 요청 라이프 스타일입니다. 이를 통해 전체 개체 집합이 동일한 컨텍스트 내에서 작동 할 수 있습니다. 즉, 동일한 비즈니스 트랜잭션 내에서 작동합니다.

동일한 컨텍스트 내에서 일련의 작업을 수행하려는 목표가 없으면 일시적인 라이프 스타일은 좋지만 몇 가지주의해야 할 사항이 있습니다.

  • 모든 객체는 자체 인스턴스를 가져 오므로 시스템 상태를 변경하는 모든 클래스는 호출해야합니다 _context.SaveChanges()(그렇지 않으면 변경 사항이 손실 됨). 이는 코드를 복잡하게 만들고 코드에 두 번째 책임 (컨텍스트 제어 책임)을 추가하며 단일 책임 원칙을 위반하는 것입니다 .
  • DbContext다른 클래스의 컨텍스트 인스턴스에서 사용할 수 없으므로 [로드하여 저장 한 ] 엔터티가 해당 클래스의 범위를 벗어나지 않도록해야합니다. 엔티티가 필요할 때 ID로 다시로드해야하므로 성능 문제가 발생할 수 있기 때문에 코드가 엄청나게 복잡해질 수 있습니다.
  • DbContext구현 하기 때문에 IDisposable여전히 생성 된 모든 인스턴스를 삭제하려고합니다. 이렇게하려면 기본적으로 두 가지 옵션이 있습니다. 을 호출 한 직후에 동일한 방법으로 처리해야 context.SaveChanges()하지만,이 경우 비즈니스 로직은 외부에서 전달 된 객체의 소유권을 가져옵니다. 두 번째 옵션은 생성 된 모든 인스턴스를 Http 요청의 경계에 배치하는 것입니다. 그러나이 경우 해당 인스턴스를 폐기해야 할시기를 컨테이너에 알리려면 범위를 지정해야합니다.

다른 옵션은 전혀 주입 하지 않는DbContext 것입니다. 대신 DbContextFactory새 인스턴스를 만들 수 있는 a를 삽입 합니다 (이전에이 방법을 사용 했었습니다). 이렇게하면 비즈니스 로직이 컨텍스트를 명시 적으로 제어합니다. 다음과 같이 보일 수 있습니다 :

public void SomeOperation()
{
    using (var context = this.contextFactory.CreateNew())
    {
        var entities = this.otherDependency.Operate(
            context, "some value");

        context.Entities.InsertOnSubmit(entities);

        context.SaveChanges();
    }
}

이것의 장점은 DbContext명시 적으로 수명을 관리하고 쉽게 설정할 수 있다는 것입니다. 또한 특정 범위에서 단일 컨텍스트를 사용할 수 있으므로 단일 비즈니스 트랜잭션에서 코드를 실행하고 엔티티가 동일한 위치에서 시작하므로 엔티티를 전달할 수있는 것과 같은 분명한 이점이 DbContext있습니다.

단점은 DbContext메쏘드에서 메쏘드 (메서드 인젝션이라고 함) 를 전달해야한다는 것입니다 . 어떤 의미에서이 솔루션은 ‘범위’접근 방식과 동일하지만 이제 범위는 응용 프로그램 코드 자체에서 제어되며 여러 번 반복 될 수 있습니다. 작업 단위 작성 및 처리를 담당하는 응용 프로그램입니다. DbContext의존성 그래프가 생성 된 후에 생성 되기 때문에 생성자 주입은 그림에서 벗어나므로 한 클래스에서 다른 클래스로 컨텍스트를 전달해야하는 경우 메소드 삽입을 연기해야합니다.

Method Injection은 그렇게 나쁘지는 않지만 비즈니스 로직이 더 복잡해지고 더 많은 클래스가 참여할 때 메소드에서 메소드로, 클래스로 클래스를 전달해야합니다. 이것은 과거에). 간단한 응용 프로그램의 경우이 접근법은 잘 작동합니다.

단점으로 인해이 팩토리 접근 방식은 더 큰 시스템에 적용되며 다른 접근 방식이 유용 할 수 있으며 이는 컨테이너 또는 인프라 코드 / 컴포지션 루트 가 작업 단위를 관리하게하는 접근 방식입니다. 이것이 당신의 질문에 관한 스타일입니다.

컨테이너 및 / 또는 인프라에서이를 처리 할 수있게함으로써 UoW 인스턴스를 생성 (선택 사항) 커밋 및 처리하여 비즈니스 로직을 단순하고 깨끗하게 유지함으로써 애플리케이션 코드가 오염되지 않습니다 (단일 책임). 이 방법에는 몇 가지 어려움이 있습니다. 예를 들어, 인스턴스를 커밋하고 처리 했습니까?

작업 단위 처리는 웹 요청이 끝날 때 수행 할 수 있습니다. 그러나 많은 사람들 이 이것이 작업 단위를 커밋하는 장소라고 잘못 생각합니다. 그러나 애플리케이션의 시점에서 작업 단위가 실제로 커밋되어야하는지 확인할 수 없습니다. 예를 들어 비즈니스 계층 코드가 콜 스택에서 더 높은 예외를 발생시킨 경우 커밋 하지 않을 것 입니다.

실제 솔루션은 다시 일종의 범위를 명시 적으로 관리하는 것이지만 이번에는 컴포지션 루트 내에서 수행합니다. 명령 / 핸들러 패턴 뒤에있는 모든 비즈니스 로직을 추상화 하면이를 수행 할 수있는 각 명령 핸들러를 감싸는 데코레이터를 작성할 수 있습니다. 예:

class TransactionalCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    readonly DbContext context;
    readonly ICommandHandler<TCommand> decorated;

    public TransactionCommandHandlerDecorator(
        DbContext context,
        ICommandHandler<TCommand> decorated)
    {
        this.context = context;
        this.decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        this.decorated.Handle(command);

        context.SaveChanges();
    } 
}

따라서이 인프라 코드를 한 번만 작성하면됩니다. 모든 견고한 DI 컨테이너를 사용하면 그러한 데코레이터가 모든 ICommandHandler<T>구현을 일관된 방식으로 감싸도록 구성 할 수 있습니다 .


답변

여기에 단일 답변이 실제로 질문에 대한 답변이 아닙니다. OP는 싱글 톤 / 애플리케이션 당 DbContext 디자인에 대해 묻지 않았으며, 웹별 요청 디자인과 존재할 수있는 잠재적 이점에 대해 질문했습니다.

Mehdi는 환상적인 리소스이므로 http://mehdi.me/ambient-dbcontext-in-ef6/ 를 참조하겠습니다 .

가능한 성능 향상.

각 DbContext 인스턴스는 데이터베이스에서로드하는 모든 엔티티의 첫 번째 레벨 캐시를 유지 보수합니다. 기본 키로 엔터티를 쿼리 할 때마다 DbContext는 기본적으로 데이터베이스에서 쿼리하도록 기본 설정하기 전에 첫 번째 수준 캐시에서 엔터티를 검색하려고 시도합니다. 데이터 쿼리 패턴에 따라 여러 순차 비즈니스 트랜잭션에서 동일한 DbContext를 재사용하면 DbContext 1 차 레벨 캐시로 인해 데이터베이스 쿼리가 줄어 듭니다.

지연 로딩이 가능합니다.

뷰 모델이나 다른 종류의 DTO를 반환하는 것과 달리 서비스가 영구 엔터티를 반환하고 해당 엔터티에 지연로드를 활용하려는 경우 해당 엔터티가 검색된 DbContext 인스턴스의 수명이 연장되어야합니다. 비즈니스 거래의 범위. 서비스 메소드가 리턴하기 전에 사용했던 DbContext 인스턴스를 처리 한 경우 리턴 된 엔티티에서 특성을 지연로드하려는 시도는 실패합니다 (지연로드를 사용하는지 여부에 관계없이 우리가 다루지 않을 다른 논쟁은 좋은 아이디어입니다) 여기). 웹 애플리케이션 예제에서 lazy-loading은 일반적으로 별도의 서비스 계층에서 반환 된 엔터티의 컨트롤러 작업 메서드에 사용됩니다. 이 경우

단점도 있습니다. 이 링크에는 주제를 읽을 수있는 다른 많은 자료가 들어 있습니다.

다른 사람 이이 질문에 걸려 넘어 실제로 질문을 해결하지 못하는 답변에 열중하지 않는 경우에 대비하여 게시하십시오.


답변

Microsoft의 두 가지 상충되는 권장 사항이 있으며 많은 사람들이 DbContexts를 완전히 다른 방식으로 사용합니다.

  1. DbContext Alive가 DB 연결 등과 같은 귀중한 리소스를 차지하기 때문에 가능한 한 빨리 DbContext 를 처리하는 것이 좋습니다 .
  2. 다른 하나는 요청 당 하나의 DbContext가 권장된다고 말합니다.

귀하의 요청이 Db와 관련이없는 많은 일을하는 경우 DbContext는 아무런 이유없이 유지되기 때문에 서로 모순됩니다. 따라서 귀하의 요청이 임의의 작업이 완료되기를 기다리는 동안 DbContext를 계속 유지하는 것은 낭비입니다 …

에 따라 많은 사람들 규칙 1은 자신의 내부에 자신의 DbContexts이 “저장소 패턴” 만들고 데이터베이스 쿼리마다 새로운 인스턴스를 수 있도록 X * DbContext 의 요청에 따라

그들은 단지 데이터를 얻고 최대한 빨리 컨텍스트를 처리합니다. 이것은 많은 사람들에 의해 수용 가능한 관행으로 간주됩니다 . 이것은 최소한의 시간 동안 db 자원을 점유하는 이점이 있지만, UnitofWork캐싱 캔디 EF가 제공하는 모든 것을 분명히 희생시킵니다 .

DbContext 의 단일 다목적 인스턴스를 유지하면 캐싱 의 이점이 극대화 되지만 DbContext는 스레드로부터 안전하지 않으며 각 웹 요청이 자체 스레드에서 실행되므로 요청 당 DbContext가 가장 길어질 수 있습니다.

따라서 요청 당 1 Db 컨텍스트 사용에 대한 EF 팀의 권장 사항은 웹 애플리케이션에서 UnitOfWork가 하나의 요청 내에있을 가능성이 높으며 해당 요청에 하나의 스레드가 있다는 사실을 분명히 기반으로합니다. 따라서 요청 당 하나의 DbContext는 UnitOfWork 및 Caching의 이상적인 이점과 같습니다.

그러나 많은 경우에 이것은 사실이 아닙니다. 별도의 UnitOfWork를 로깅 하는 것을 고려 하므로 비동기 스레드 에서 요청 후 로깅을위한 새로운 DbContext 가 허용됩니다.

마지막으로 DbContext의 수명은이 두 매개 변수로 제한됩니다. UnitOfWork스레드


답변

DbContext가 전혀 스레드 안전하지 않기 때문에 확실합니다. 따라서 공유하는 것은 결코 좋은 생각이 아닙니다.


답변

질문이나 토론에서 실제로 다루지 않은 것 중 하나는 DbContext가 변경 사항을 취소 할 수 없다는 사실입니다. 변경 사항을 제출할 수는 있지만 변경 트리를 지울 수 없으므로 요청 당 컨텍스트를 사용하는 경우 어떤 이유로 든 변경 사항을 버려야하는 경우 운이 없습니다.

개인적으로 필요할 때 DbContext 인스턴스를 만듭니다. 일반적으로 필요한 경우 컨텍스트를 다시 만들 수있는 비즈니스 구성 요소에 연결됩니다. 그렇게하면 단일 인스턴스를 강제로 사용하지 않고 프로세스를 제어 할 수 있습니다. 또한 실제로 사용되는지 여부에 관계없이 각 컨트롤러 시작시 DbContext를 만들 필요가 없습니다. 그런 다음 요청 당 인스턴스를 계속 유지하려면 CTOR에서 인스턴스를 생성하거나 (DI를 통해 또는 수동으로) 각 컨트롤러 방법에서 필요에 따라 생성 할 수 있습니다. 개인적으로 필자는 실제로 필요하지 않을 때 DbContext 인스턴스를 작성하지 않도록 후자의 방법을 사용합니다.

그것은 당신이 보는 각도에 달려 있습니다. 나에게 요청 당 인스턴스는 의미가 없었습니다. DbContext가 실제로 HTTP 요청에 속합니까? 행동의 관점에서 그것은 잘못된 곳입니다. 비즈니스 컴포넌트는 Http 요청이 아니라 컨텍스트를 작성해야합니다. 그런 다음 필요에 따라 비즈니스 구성 요소를 작성하거나 버릴 수 있으며 컨텍스트의 수명에 대해 걱정하지 않아도됩니다.


답변

나는 이전 의견에 동의합니다. 단일 스레드 응용 프로그램에서 DbContext를 공유하려면 더 많은 메모리가 필요하다는 것이 좋습니다. 예를 들어 Azure의 웹 응용 프로그램 (한 개의 작은 인스턴스 하나)에는 150MB의 메모리가 더 필요하며 시간당 약 30 명의 사용자가 있습니다.
HTTP 요청에서 응용 프로그램 공유 DBContext

실제 이미지 예 : 응용 프로그램이 12PM에 배포되었습니다


답변

내가 좋아하는 것은 작업 단위 (사용자가 보는 것처럼-즉 페이지 제출)를 ORM 의미에서 작업 단위와 정렬한다는 것입니다.

따라서 전체 페이지 제출 트랜잭션을 트랜잭션으로 만들 수 있으며, 각각 새로운 컨텍스트를 작성할 때 CRUD 메소드를 노출하는 경우에는 수행 할 수 없습니다.