DbContextのTracking設定をすべてのエンティティに設定する

テストプログラムなどで更新処理を実行するときにContextのTrackingが有効な場合、Contextから更新後のデータを取得するときに最初に取得した情報をContextが保持していてテストがFailになる事があります。忘れた頃に発生する事象で、たいてい急いでいるときにテスト対象のプログラムは正しいけどテストが通らないというつらい状況になったりします。

このようなケースでは最初にエンティティを取得するときにAsNoTrackingを設定すればOKです。

 var personCustomer = CustomerContext.Context.Customers.AsNoTracking().Include(c => c.Person).Include(c => c.Organization)
                .First(c => c.Person != null);

とはいうものの、テストのように基本データを参照しかしないケースではデフォルトとしてAsNoTrackingを設定したいですよね。むしろ、TrackingしたいときにだけAsTrackingを設定することで余計なハマりをなくしたいわけです。

そこでContext単位でAsnoTrackingを一括で設定できる方法はないだろうかということになりますが、以下の通り設定すればContextに含まれるエンティティの取得がすべてTracking対象ではなくなります。

 Context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

参考

以下の公式ドキュメントにもあるようにReadonlyとしてエンティティを使用する場合にはChangeTrackerの生成コストが小さくなるためおすすめのようです。

ChangeTracker Class (Microsoft.EntityFrameworkCore.ChangeTracking) | Microsoft Docs

Gets or sets the tracking behavior for LINQ queries run against the context. Disabling change tracking is useful for read-only scenarios because it avoids the overhead of setting up change tracking for each entity instance. You should not disable change tracking if you want to manipulate entity instances and persist those changes to the database using SaveChanges(). This method sets the default behavior for the context, but you can override this behavior for individual queries using the AsNoTracking(IQueryable) and AsTracking(IQueryable) methods. The default value is TrackAll. This means the change tracker will keep track of changes for all entities that are returned from a LINQ query.

大人数で開発するときにはこの手のはまりは人数分のコストになるので、技術的に解決できるところはあらかじめ解決しておきたいですね。

追記

Npgsqlを利用している場合には、テスト対象データのバージョン番号を取得するためにはAsTrackingを設定する必要があります。

C#プログラマーのための 基礎からわかるLINQマジック!

C#プログラマーのための 基礎からわかるLINQマジック!

ASP.NET Core2のStartup.csでJson.NETの設定を行う

ASP.NET CoreでJson.NET - Newtonsoftの設定をStartup.csに行うには以下の通りConfigureServicesで初期化を行うことができます。

public void ConfigureServices(IServiceCollection services)
        {
             ...
            services.AddMvc().AddJsonOptions(options =>
            {
                options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
                options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
            });
            ....
}

今回は値が設定されていない項目については出力しないように設定するようにしました。

なお、この設定は各フィールドに属性を以下の通り設定することでも対応できます。

[JsonProperty("property_name", NullValueHandling=NullValueHandling.Ignore)]
public string Hoge{get;set;}

ASP.NET MVCプログラミング入門 (マイクロソフト関連書)

ASP.NET MVCプログラミング入門 (マイクロソフト関連書)

ASP.NET Core2でSerilogを利用する

これまではNLogを利用することが多かったのですが、パフォーマンスやお手軽さからSerilog — simple .NET logging with fully-structured eventsを使用してみようと思います。

前提条件

対象のプロジェクトはASP.NET Core2で作成しました。

設定方法

ASP.NET Core2でserilog/serilog-aspnetcore: Serilog integration for ASP.NET Core 2+の設定は以下の通りとなります。

  1. NuGetパッケージのインストール
  2. Program.csの変更

NuGetパッケージのインストール

NuGetパッケージは以下のものをインストールします。

  • Serilog.AspNetCore -DependencyVersion Highest
  • Serilog.Sinks.Console
  • Serilog.Sinks.RollingFile

インストール後のプロジェクトファイルは以下の通りとなります。

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
    <PackageReference Include="Npgsql" Version="3.2.5" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="2.0.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1-dev-00757" />
    <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
  </ItemGroup>
</Project>

Program.csの変更

Program.csは以下の通り変更します。

        public static int Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .WriteTo.RollingFile("logs/beagle-customer-{Date}.log")
                .CreateLogger();

            try
            {
                var host = BuildWebHost(args);
                host.Run();

                return 0;
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
                return 1;
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
               // ここでログの利用を宣言する
                .UseSerilog()
                .Build();
    }

ポイントはMainメソッド内でLoggerの設定を行い、例外がスローされたときにもログを出力するように設定していることです。

// ロガーの設定を行う
Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                .Enrich.FromLogContext()
               // コンソールへの出力を行う
                .WriteTo.Console()
               // ファイルへの出力を行う
                .WriteTo.RollingFile("logs/beagle-customer-{Date}.log")
                .CreateLogger();

なお、出力先についてはNuGetで公開されているSink(NuGet Gallery | Packages matching Tags:"serilog")を利用することで柔軟に対応ができます。

設定した内容はUseSerilog()メソッドを設定することで利用可能となります。

public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
               // ここでログの利用を宣言する
                .UseSerilog()
                .Build();

感想

最初はASP.NET Core2についてもよく知らなかったので戸惑うことが多かった(特にテストプロジェクトでの出力とか)ですが、慣れてくるとログの出力先などSinkが色々とあって便利だなと感じました。

参考

Serilog vs NLog Benchmarks

Pro ASP.NET Core MVC 2

Pro ASP.NET Core MVC 2