ソリューションを新しい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のバグですか?
将来の読者のために。
これと同じ問題が発生しました。
「インライン日付計算」を単純に置き換えることで、問題を乗り越えることができました。
"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" />
ソリューションの.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));
}