Asp.Netコアミドルウェアまたは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リクエストにスコープされた作業単位があるようにしたいと思います。いくつかの例外など)。

過去に私はこの目的のためにWebAPI2をNHibernateで使用していました。アクションフィルターを使用してアクションを実行し、アクションを実行するとトランザクションを終了してセッションを閉じます。これはhttp://isbn.directory/book/9781484201107で推奨された方法でした

しかし、今はAsp.Net Core(Asp.Net Core Mvcを使用していますが、これは関係ないはずです)とEntity Frameworkを使用しています。これはすでに理解していますが、すでに作業単位を実装しています。

私はミドルウェアをASP.NETパイプラインに(MVCの前に)差し込んでおくと正しいことができると思います。だからリクエストが出ます:

パイプラインASP.NET:MyUnitOfWorkMiddleware ==> MVCコントローラ==>リポジトリ==> MVCコントローラ==> MyUnitOfWorkMiddleware

私はこのミドルウェアにDbContextの変更を保存させることを考えていました。これは、例外が発生しなければ、私のリポジトリの実装では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コントローラレベルでこのアプローチを使用すると、新しいリソースをPOSTするときにデータベースによって作成されたリソースIDにアクセスすることはできません。なぜなら、IDはdbContextの変更が保存されるまで生成されないからです私のミドルウェアでは、コントローラの実行が終了した後)。コントローラで新しく作成したリソースのIDにアクセスする必要がある場合はどうすればよいですか?

どんなアドバイスも大歓迎です!

更新1ミドルウェアのDbContextインスタンスがMVC(およびリポジトリ)の存続期間中と同じではないため、これを達成するためにミドルウェアを使用するアプローチに問題がありました。 エンティティフレームワークコア1.0の質問を参照してください。DbContextはHTTP要求にスコープされていません

更新2私はまだ良い解決策を見つけていません。基本的にはこれまでの私の選択肢です:

  1. DBに変更をできるだけ早く保存してください。つまり、リポジトリの実装自体に保存することを意味します。このアプローチの問題は、Httpリクエストでは、複数のリポジトリ(データベースに何かを保存してからクラウドストレージにBLOBをアップロードする)を使用したいと思うかもしれないし、作業単位を持つためには、複数のエンティティや複数の永続化メソッド(DBとBlobストレージ)を扱うリポジトリ。これは目的全体を破ります
  2. アクションフィルタを実装します。ここでは、DBトランザクションでアクションの実行全体をラップします。コントローラのアクション実行の最後に、例外がない場合はDBにチェーンをコミットしますが、例外がある場合はロールバックしてコンテキストを破棄します。この問題は、HTTPクライアントに返すためにコントローラのアクションが生成されたエンティティのIDを必要とすることがあるということです(POST / api / carsを取​​得した場合は、201を返すことを望みます。エンティティがDBに保存されておらず、IDがまだ一時的な0なので、/ api / cars / 123とId 123で作成された新しいリソースはまだ利用できません。 POST動詞要求に対するコントローラーのアクションの例:

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

どうすればコントローラ全体のアクションをDBトランザクションにラップさせることができますか?また、コントローラからのHttpレスポンスで返すために、データベースによって生成されたIDはすべて利用可能ですか?または、httpのレスポンスを上書きし、DBの変更がコミットされた後、アクションフィルタレベルでIDを設定するという、エレガントな方法はありますか?

UPDATE 3: nathanaldensrのコメントによると、生成されたコードを使用して、両方の世界のベストを得ることができました(DBトランザクション_WoWでコントローラのアクション実行をラッピングし、DBが変更をコミットする前に作成された新しいリソースのIDを知ることもできます)代わりにGuidは、データベースに依存してGuidを生成します。

受け入れられた回答

私は同じ問題に直面しており、どのアプローチをとるべきかもわかりません。私が使用したアプローチの1つは以下の通りです:

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

人気のある回答

エンティティフレームワークコア1.0でDbContextがHTTPリクエストスコープされていないミドルウェアがインジェクトされるDbContextのインスタンスがMVC実行中(私のコントローラやリポジトリ内で)と同じではないため、ミドルウェアを使用できませんでした。

グローバルフィルタを使用してコントローラのアクションを実行した後、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ときにStartup.csフィルタが自分のMVCに接続されます。

public void ConfigureServices(IServiceCollection services)
{
   //..
   //Entity Framework 7
   services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<SpeediCargoDbContext>(options => {
               options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
            });

        //MVC 6
        services.AddMvc(setup =>
        {
            setup.Filters.AddService(typeof(UnitOfWorkFilter));
        });
   //..
}

これはまだ質問が残っています(私の質問でUPDATE 2を参照)。 私のコントローラがhttp POSTリクエストに、DBで作成されたエンティティのIdを含むLocation Accepted with 201ヘッダーに応答するようにしたいのですが?コントローラのアクションが実行を終了すると、変更はまだDBにコミットされていません。したがって、アクションフィルタが変更を保存し、DBが値を生成するまで、作成されたエンティティのIDはまだ0です。



Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ