ArgumentException

プログラミング

Entity Framework Core 1.1でCountとFirstを使うとArgumentExceptionを吐く

tsoft_writericon

こんにちは、TSoftです。

.NET Core 1.0で開発を進めていたプログラムを、2016年11月16日にリリースされた.NET Core 1.1とEF Core 1.1にアップデートしたところ、タイトルの様な部分で動かなくなってしまいました…。

ググっても情報が出ないので、慣れないブログ記事でまとめてみたいと思います。
用語等に誤りがありましたら、ぜひコメントでご指摘ください。

環境

Windows 10
Visual Studio 2015
.NET Core SDK 1.0.0-preview2-1-003177
Microsoft.AspNetCore.Mvc 1.1.0
Microsoft.EntityFrameworkCore 1.1.0

SQL Server 2014 Express LocalDB
Microsoft.EntityFrameworkCore.SqlServer 1.1.0

CentOS 7
PostgreSQL 9.2.15
Npgsql.EntityFrameworkCore.PostgreSQL 1.1.0
※この環境ではスタックトレースの行番号が誤って表示されたので注意

内容

下記のコードのように、1対多で親側のクラスに子クラスがListとして存在するとします。

//DbContextは省略
public class ParentItem
{
    public int Id { get; set; }
    public string Value1 { get; set; }
    public List<SubItem> SubItems { get; set; }
}

public class SubItem
{
    public int Id { get; set; }
    public int ParentItemId { get; set; }
    [ForeignKey("ParentItemId")]
    public ParentItem ParentItem { get; set; }
    public string Value1 { get; set; }
}

Where句やCount句を利用してデータベースからParentItemを選択するとき、条件式に子要素のListに対してCountメソッドとFirstメソッドを呼びます。

await _context.ParentItems
            .Include(parent => parent.SubItems)
            .Where(parent => parent.SubItems.Count() == 0 || parent.SubItems.First().Value1 == "foobar")
            .ToListAsync()

すると、下記のような例外が発生します。

ArgumentException: Incorrect number of arguments supplied for call to method 'System.Threading.Tasks.Task`1[System.Int32] GetResult[Int32](System.Collections.Generic.IAsyncEnumerable`1[Microsoft.EntityFrameworkCore.Storage.ValueBuffer], System.Threading.CancellationToken)'
Parameter name: method
System.Dynamic.Utils.ExpressionUtils.ValidateArgumentCount(MethodBase method, ExpressionType nodeKind, int count, ParameterInfo[] pis)

厄介なことに、1回目では例外が出ますが、2回目以降は応答が返ってこなくなりdotnetプロセスがCPUを永遠と使う現象に陥ります。

回避策

幸い、いくつか回避策があります。

First()をFirstOrDefault()に置き換えて、Count()をFirstOrDefault()より後ろに持って行く

await _context.ParentItems
            .Include(parent => parent.SubItems)
            .Where(parent => parent.SubItems.FirstOrDefault().Value1 == "foobar" || parent.SubItems.Count() == 0)
            .ToListAsync()

非同期メソッドではなく同期メソッドを使用する

_context.ParentItems
        .Include(parent => parent.SubItems)
        .Where(parent => parent.SubItems.Count() == 0 || parent.SubItems.First().Value1 == "foobar")
        .ToList()

そのほかに、0であるかの判定であればFirstOrDefault() == nullを利用するなどあると思います。

おわりに

普段こういった記事に助けられているので、自分も助けになれば幸いです。
英語が達者ならIssueを投げられたのですが…

リンク

GitHub - aspnet/EntityFramework
https://github.com/aspnet/EntityFramework
NuGet Gallery | Microsoft.EntityFrameworkCore
https://www.nuget.org/packages/Microsoft.EntityFrameworkCore/

-プログラミング
-, ,