しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

Entity Framework Core 2.1 で Lazy Loading を有効にする

Entity Framework 6 の頃はデフォルトで有効化されていたので、特に意識することなく使われていたであろう Lazy Loading ですが、Entity Framework Core では 2.1 から拡張という形で実装がされました。

デフォルトでは有効化されないので、Entity Framework 6 からの移行時にはまることが多そうと思ったので、簡単に試しておきました。

とりあえず単純な .NET Core コンソールアプリを作成しました。EF Core 2.1 を使っています。Generic Host を使った方が DI とか Logging が楽だと思いますが、コードが複雑になるので止めました。

class Program
{
    static async Task Main(string[] args)
    {
        var context = new AppDbContext();

        var post = await context.Posts
                                .FirstAsync();

        Console.WriteLine($"{post.Id} : {post.Title} - {post.Content}");
        Console.WriteLine($"{post.Author.Id} : {post.Author.Name}");
    }
}

public class AppDbContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Author> Authors { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=EFCoreTest;Integrated Security=SSPI");
    }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public Author Author { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
}

SQL Server LocalDB を使っているので、インストールされていないとエラーになります。

適当にマイグレーションのコマンドを実行して DB を作り、データを入れた後に実行すると以下のようにリレーションを設定したオブジェクトは null になります。

f:id:shiba-yan:20180730182542p:plain

解消するためには Entity Framework 6 の頃にもあった Include 拡張メソッドを使います。これで明示的に Author プロパティの値を引いてきてくれます。

var post = await context.Posts
                        .Include(x => x.Author)
                        .FirstAsync();

ちなみに Entity Framework Core では ThenInclude 拡張メソッドが用意されているので、ネストされている場合にも取得することが出来ます。

これでデータ自体はちゃんと取得できますが、Lazy Loading 前提で書かれていた場合には影響範囲が広すぎて辛いので、Entity Framework Core でも Lazy Loading を有効化します。

ドキュメントが用意されているので、これを読めば問題なく有効に出来ます。

Loading Related Data - EF Core | Microsoft Docs

Lazy Loading は別のパッケージとして提供されているので、NuGet からインストールします。

パッケージをインストールすると UseLazyLoadingProxies 拡張メソッドが使えるようになっているので、OnConfiguring で呼び出すように変更します。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseLazyLoadingProxies()
                  .UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=EFCoreTest;Integrated Security=SSPI");
}

そして Lazy Loading を行いたいプロパティを virtual に変更します。

パッケージ名からわかるように、自動的に Lazy Loading 用の Proxy クラスを作成するので、virtual にしないと動きません。この辺りは Entity Framework 6 と同じですね。

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public virtual Author Author { get; set; }
}

これで再実行するとちゃんと Author の値が取れていることが確認できます。

f:id:shiba-yan:20180730184516p:plain

Proxy 生成に何が使われているかは一目で分かりますね。Entity Framework 6 までは大体自前で作っていたはずですが、Entity Framework Core からは外部のライブラリを積極的に使うようになっているみたいです。

これで Entity Framework 6 から Entity Framework Core への移行が少し楽になりそうです。