web-dev-qa-db-ja.com

dotnet Core 2.2.6から3.0.0に変更した後のEF Linqエラー

ソリューションを新しいCore Framework 3.0.0にアップグレードしようとしています。今、私には理解できない小さな問題があります。

見て、この方法は2.2.6では問題がありませんでした。

public async Task<IEnumerable<ApplicationUser>> GetBirthdayUsersCurrentMonth()
    {
        return await ApplicationDbContext.Users
            .Where(x => x.Gender != ApplicationUser.GenderTypes.generic)
            .Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
            .Where(x => x.RetireDate == null)
            .OrderBy(x => x.BirthDate.GetValueOrDefault())
            .ToListAsync();
    }

3.0.0では、次のようなLinqエラーが発生します。

InvalidOperationException:LINQ式 'Where(source:Where(source:DbSet、predicate:(a)=>(int)a.Gender!= 0)、predicate:(a)=> a.BirthDate.GetValueOrDefault()。Month == DateTime.Now.Month) 'は翻訳できませんでした。変換可能な形式でクエリを書き直すか、AsEnumerable()、AsAsyncEnumerable()、ToList()、またはToListAsync()への呼び出しを挿入して、クライアント評価に明示的に切り替えます

この行を無効にすると:

.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)

エラーはなくなりましたが、もちろん、すべてのユーザーを取得します。そして、私はこのクエリでエラーを見ることができません。これはおそらくEF Core 3.0.0のバグですか?

21
monsee

将来の読者のために。

これと同じ問題が発生しました。

「インライン日付計算」を単純に置き換えることで、問題を乗り越えることができました。

"BEFORE" IQueryable(下記)(機能しません):

(「前」のこの部分に焦点を当てる

"perParentMaxUpdateDate.UpdateDateStamp <DateTime.Now.Add(cutOffTimeSpan)"

    IQueryable<MyChildEntity> filteredChildren = from chd in this.entityDbContext.MyChilds
                                                 join perParentMaxUpdateDate in justParentsAndMaxUpdateTs
                                                 on new { CompoundKey = chd.UpdateDateStamp, chd.MyParentUUID } equals new { CompoundKey = perParentMaxUpdateDate.UpdateDateStamp, perParentMaxUpdateDate.MyParentUUID }
                                                 where magicValues.Contains(chd.MyChildMagicStatus)
                                                 && perParentMaxUpdateDate.UpdateDateStamp < DateTime.Now.Add(cutOffTimeSpan) /* <- FOCUS HERE */
                                                 select chd;

"AFTER" IQueryable(下記)(これは正常に動作します):

    DateTime cutOffDate = DateTime.Now.Add(cutOffTimeSpan); /* do this external of the IQueryable......and use this DateTime ..... instead of the "inline" time-span calcuation as seen above */

    IQueryable<MyChildEntity> filteredChildren = from chd in this.entityDbContext.MyChilds
                                                 join perParentMaxUpdateDate in justParentsAndMaxUpdateTs
                                                 on new { CompoundKey = chd.UpdateDateStamp, chd.MyParentUUID } equals new { CompoundKey = perParentMaxUpdateDate.UpdateDateStamp, perParentMaxUpdateDate.MyParentUUID }
                                                 where magicValues.Contains(chd.MyChildMagicStatus)
                                                 && perParentMaxUpdateDate.UpdateDateStamp < cutOffDate /* <- FOCUS HERE */
                                                 select chd;

私の問題と元の質問の問題が原因であると思います。.....DateTimesは少しトリッキーです。

したがって、(はい)以前は機能していたコードが機能しなくなったことは不愉快です...「クライアントでコードを実行しないデフォルト」を理解することは、パフォーマンスにとって非常に重要です。

PS、私は3.1にいます

私のパッケージは完全を期すために参照しています。

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
0
granadaCoder

ソリューションの.netCoreバージョンを3.0にアップグレードしようとしているので、アップグレードを実行する人の範囲で質問に答えます。

EF Core 3.0の重大な変更の公式ドキュメント を参照すると、次の行が見つかります

LINQクエリはクライアントで評価されなくなりました

GetValueOrDefault()はEFで解釈できないため、以下のクエリはクライアント側で評価されなくなります。

.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)

これが3.0より前に機能していた理由は、未加工のSQLに変換できないセグメントの前にすべてを評価し、クライアント(c#)側で残りのセグメントを評価するためです。つまり、コードはおおよそ次のように評価されます。

return (await ApplicationDbContext.Users
            .Where(x => x.Gender != ApplicationUser.GenderTypes.generic).ToListAsync()) //sql evaluated till here
            .Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
            .Where(x => x.RetireDate == null)
            .OrderBy(x => x.BirthDate.GetValueOrDefault())
            .ToList();

これはEF Core 3.0では許可されなくなりました。理由は、クライアント側の評価を隠すことは、大規模なデータセットを使用する本番環境では不利であるのに対し、開発ではパフォーマンスヒットが見落とされる可能性があるためです。

2つのソリューションがあります。

影響を受ける行を次のように書き換えることをお勧めします。defaultMonthValueは、GetValueOrDefault()拡張内で使用されたデフォルトの月の整数を持つconst intです。

.Where(x => (x.BirthDate != null && x.BirthDate.Value.Month == DateTime.Now.Month) || (x.BirthDate == null && defaultMonthValue == DateTime.Now.Month))

2番目の、ただし推奨されない解決策は、問題のあるセグメントの前に.AsEnumerable()を明示的に追加して、EFに前のステートメントを評価させることです。

.AsEnumerable() // switches to LINQ to Objects
.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)

2.2から3.0に移行する予定で、実際の移行の前に2.2コードベースのクライアント評価の重大な変更をテストする場合のヒント:

Microsoft docs から、startup.csに以下を追加して、3.0クライアント側のクエリスローをシミュレートします。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
        .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
0
Markuzy