Entity Framework Plus を使ってタイプセーフに一括更新を行う

Entity Framework Core を使っていて、 エンティティをまとめて更新したい場面は結構あるが、 タイプセーフかつ効率的に更新する手段は、 今のところ標準で提供されていない。

ExecuteSqlCommand を使って直接 SQL を発行すればいいんだけど、 テストではInMemoryプロバイダーを使用しているので、 一括更新だけ違うやり方でテストしなければいけなくなるのが面倒。

そこで Entity Framework Plus を使ってみることにした。 Entity Framework と Entity Framework Core の機能を拡張できるライブラリ。

entityframework-plus.net

Entity Framework Core 用のパッケージを使う。

www.nuget.org

IQueryable に拡張メソッドが追加されるので、 非同期に一括で更新したい場合は UpdateAsync を使えばいい。

var publishedAt = DateTimeOffset.UtcNow;

var query = from e in context.Entries
            join b in context.Blogs on e.BlogId equals b.Id
            where e.TenantId == tenantId
            where b.UserId == userId
            where e.IsDraft
            select e;

await query.UpdateAsync(e => new Entry()
{
    IsDraft = false,
    PublishedAt = publishedAt,
});

普通に使うぶんにはこれでOK。 ただ注意点があって、テストで InMemory プロバイダーを使っていると

UpdateAsync results in Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.Internal.InMemoryQueryContextFactory'

という例外が発生してしまう。 その場合は

BatchUpdateManager.InMemoryDbContextFactory = () => new ApplicationDbContext(options);

という風に、Entity Framework Plus に DbContext を取得するためのデリゲートを登録しておくことで回避可能。