Asp.Net Core 미들웨어 또는 Mvc 필터를 사용하는 Entity Framework Core 1.0 작업 단위

asp.net-core-1.0 entity-framework-core middleware unit-of-work

문제

저는 RESTful API를 위해 EF Core 1.0 (이전에 알려진 광고 EF7)과 ASP.NET Core 1.0 (이전에는 ASP.NET 5로 알려짐)을 사용하고 있습니다.

HTTP 요청에 응답 할 때 DbContext에 대한 모든 변경 사항이 데이터베이스에 저장되거나 아무 것도 저장되지 않도록 HTTP 요청에 대해 범위가 지정된 작업 단위를 지정하고 싶습니다. 예를 들어, 일부 예외).

과거에는 NHibernate에서 ActionAPT를 사용하여 WebAPI2를 사용하여 실행중인 액션에 대한 트랜잭션을 시작하고 실행 된 액션에 대해 트랜잭션을 끝내고 세션을 닫습니다. 이것은 http://isbn.directory/book/9781484201107 에서 권장되는 방법이었습니다.

그러나 지금은 Asp.Net Core (Asp.Net Core MVC와 관련이 없지만)를 사용하고 있으며 Entity Framework는 이미 작업 단위를 구현하고 있습니다.

MVC 이전의 ASP.NET 파이프 라인에 연결된 미들웨어를 사용하는 것이 올바른 방법 일 것이라고 생각합니다. 그래서 요청이 갈 것입니다 :

파이프 라인 ASP.NET : MyUnitOfWorkMiddleware ==> MVC 컨트롤러 ==> 저장소 ==> MVC 컨트롤러 ==> MyUnitOfWorkMiddleware

예외가 발생하지 않으면이 미들웨어에 DbContext 변경 사항을 저장하도록 고려 중이므로 저장소 구현시 dbcontext.SaveChanges() 를 수행 할 필요가 dbcontext.SaveChanges() 모든 것이 중앙 집중식 트랜잭션과 같을 것입니다. 의사 코드에서는 다음과 같이 될 것이라고 생각합니다.

class MyUnitOfWorkMiddleware
{
     //..
     1-get an instance of DbContext for this request.
     try {
         2-await the next item in the pipeline.
         3-dbContext.SaveChanges();
     }
     catch (Exception e) {
         2.1-rollback changes (simply by ignoring context)
         2.2-return an http error response
     }
}

이게 말이 돼? 아무도 비슷한 예를 든 사람이 있습니까? 이 문제를 해결하기 위해 좋은 연습이나 권장 사항을 찾을 수 없습니다.

또한, 내 MVC 컨트롤러 수준에서이 접근 방식을 사용하면 ID가 dbContext 변경 사항이 저장 될 때까지 생성되지 않기 때문에 새로운 리소스를 게시 할 때 데이터베이스에서 만든 모든 리소스 ID에 액세스 할 필요가 없습니다. (나중에 파이프 라인에 저장됩니다. 컨트롤러가 실행을 마친 후 내 미들웨어에서). 내 컨트롤러에서 새로 생성 된 리소스 ID에 액세스해야한다면 어떻게해야합니까?

모든 조언을 크게 주시면 감사하겠습니다!

업데이트 1 : 미들웨어의 DbContext 인스턴스가 MVC (및 리포지토리) 수명 기간과 동일하지 않기 때문에 미들웨어를 사용하는 방식에 문제가 있다는 것을 발견했습니다. Entity Framework Core 1.0 질문을 참조하십시오. DbContext가 HTTP 요청에 범위가 지정되지 않았습니다.

업데이트 2 : 아직 좋은 해결책을 찾지 못했습니다. 기본적으로 이들은 지금까지 제 선택 사항입니다 :

  1. 가능한 빨리 DB에 변경 사항을 저장하십시오. 이는 리포지토리 구현 자체에 저장한다는 의미입니다. 이 접근 방식의 문제점은 Http 요청에 대해 여러 저장소 (예 : 데이터베이스에 항목을 저장 한 다음 BLOB를 클라우드 저장소에 업로드)를 사용하고 작업 단위 (Unit of Work)를 갖기 위해 하나 이상의 엔티티 또는 둘 이상의 퍼시스턴스 메소드 (DB 및 블롭 스토리지)를 처리하는 리포지토리
  2. DB 트랜잭션에서 전체 액션 실행을 래핑하는 액션 필터 구현하기. 컨트롤러의 액션 실행이 끝날 때 예외가 없다면 DB에 chanches를 적용하지만 예외가 있으면 롤백하고 컨텍스트를 삭제합니다. 이 문제는 내 컨트롤러의 작업에 http 클라이언트에 반환하기 위해 생성 된 Entity의 ID가 필요할 수 있다는 것입니다 (예 : POST / api / cars를 얻으면 201을 반환합니다. 엔티티가 DB에 저장되지 않았으므로 / api / cars / 123 및 Id 123에서 작성된 새 자원은 아직 사용 가능하지 않으며 Id는 여전히 임시 0 임). POST 동사 요청에 대한 컨트롤러의 동작 예 :

    return CreatedAtRoute("GetCarById", new { carId= carSummaryCreated.Id }, carSummaryCreated); //carSummaryCreated.Id would be 0 until the changes are saved in DB

어떻게하면 전체 컨트롤러의 작업을 DB 트랜잭션으로 래핑 할 수 있고 동시에 컨트롤러에서 HTTP 응답으로 반환하기 위해 데이터베이스에서 생성 한 ID를 사용할 수 있습니까? 또는 .. DB 응답이 커밋 된 후 HTTP 응답을 덮어 쓰고 작업 필터 수준에서 ID를 설정하는 우아한 방법이 있습니까?

업데이트 3 : nathanaldensr 의 의견에 따라 생성 된 코드를 사용하여 DB 컨테이너에서 컨트롤러의 액션 실행을 감싸는 동시에 (심지어 DB가 변경되기 전에 작성된 새 리소스의 ID를 알고있는) 두 가지 장점을 모두 얻을 수있었습니다. Guids 대신 데이터베이스를 사용하여 Guid를 생성합니다.

수락 된 답변

나는 또한 똑같은 문제에 직면하고 있으며 따라야 할 접근법을 확신하지 못한다. 내가 사용한 접근법 중 하나는 다음과 같습니다.

public class UnitOfWorkFilter : ActionFilterAttribute
{
    private readonly AppDbContext _dbContext;

    public UnitOfWorkFilter(AppDbContext dbContext,)
    {
        _dbContext = dbContext;
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        if (!context.HttpContext.Request.Method.Equals("Post", StringComparison.OrdinalIgnoreCase))
            return;
        if (context.Exception == null && context.ModelState.IsValid)
        {
            _dbContext.Database.CommitTransaction();
        }
        else
        {
            _dbContext.Database.RollbackTransaction();
        }
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.HttpContext.Request.Method.Equals("Post", StringComparison.OrdinalIgnoreCase))
            return;
        _dbContext.Database.BeginTransaction();
    }
}

인기 답변

Entity Framework Core 1.0에서 DbContext가 http 요청 범위를 벗어나면 미들웨어가 주입되는 DbContext의 인스턴스가 MVC 실행 중 (내 컨트롤러 또는 저장소에서) DbContext와 같지 않기 때문에 미들웨어를 사용할 수 없습니다.

전역 필터를 사용하여 컨트롤러의 액션을 실행 한 후에도 DbContext의 변경 사항을 저장하기 위해 비슷한 접근 방식을 취해야했습니다. 아직 MVC 6의 필터에 대한 공식 문서가 없으므로이 솔루션에 관심이있는 사람이라면 필터 및 필터를 전역 적으로 사용하여 컨트롤러의 동작 전에 실행하는 방법을 참조하십시오.

public class UnitOfWorkFilter : ActionFilterAttribute
{
    private readonly MyDbContext _dbContext;
    private readonly ILogger _logger;

    public UnitOfWorkFilter(MyDbContext dbContext, ILoggerFactory loggerFactory)
    {
        _dbContext = dbContext;
        _logger = loggerFactory.CreateLogger<UnitOfWorkFilter>();
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext executingContext, ActionExecutionDelegate next)
    {
        var executedContext = await next.Invoke(); //to wait until the controller's action finalizes in case there was an error
        if (executedContext.Exception == null)
        {
            _logger.LogInformation("Saving changes for unit of work");
            await _dbContext.SaveChangesAsync();
        }
        else
        {
            _logger.LogInformation("Avoid to save changes for unit of work due an exception");
        }
    }
}

MVC를 구성 할 때 필터가 Startup.cs 에서 내 MVC에 연결됩니다.

public class UnitOfWorkFilter : ActionFilterAttribute
{
    private readonly MyDbContext _dbContext;
    private readonly ILogger _logger;

    public UnitOfWorkFilter(MyDbContext dbContext, ILoggerFactory loggerFactory)
    {
        _dbContext = dbContext;
        _logger = loggerFactory.CreateLogger<UnitOfWorkFilter>();
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext executingContext, ActionExecutionDelegate next)
    {
        var executedContext = await next.Invoke(); //to wait until the controller's action finalizes in case there was an error
        if (executedContext.Exception == null)
        {
            _logger.LogInformation("Saving changes for unit of work");
            await _dbContext.SaveChangesAsync();
        }
        else
        {
            _logger.LogInformation("Avoid to save changes for unit of work due an exception");
        }
    }
}

이것은 여전히 ​​질문을 남겨 둡니다 (내 질문에 업데이트 2 참조). 내 컨트롤러가 DB에 생성 된 엔티티의 ID를 포함하는 201 Accepted with Location 헤더를 사용하여 http POST 요청에 응답하도록하려면 어떻게해야합니까? 컨트롤러의 조치가 실행을 완료하면 변경 사항이 아직 DB에 커밋되지 않았으므로 작업 필터가 변경 사항을 저장하고 DB가 값을 생성 할 때까지 생성 된 엔티티의 ID는 여전히 0입니다.




아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow
이 KB는 합법적입니까? 예, 이유를 알아보십시오.
아래 라이선스: CC-BY-SA with attribution
와 제휴하지 않음 Stack Overflow
이 KB는 합법적입니까? 예, 이유를 알아보십시오.