我的带有 LINQ 表达式的 ForEach 循环无法翻译?

My ForEach loop with a LINQ expression cannot be translated?

我需要检查我所有的化学品,并在有效期为 2 天或 15 天后向用户发出警告。 我把它设置成这样,所以警告会发出两次。过期前 2 周一次,过期前 2 天一次。

所以我有这个循环:

foreach (var chemical in _context.Chemical.Where(c => (c.DateOfExpiration - DateTime.Now).TotalDays == 15 || (c.DateOfExpiration - DateTime.Now).TotalDays == 2))
{
    // send out notices
    var base = _context.Base.Find(chemical.BaseId);
    var catalyst = _context.Catalyst.Find(chemical.CatalystId);

    _warningService.SendSafetyWarning(chemical, base, catalyst.Name);
}

但是每当我 运行 它时,我都会得到这个错误:

System.InvalidOperationException: 'The LINQ expression 'DbSet<Chemical>
.Where(p => (p.DateOfExpiration - DateTime.Now).TotalDays == 15 || (p.DateOfExpiration - DateTime.Now).TotalDays == 2)'
could not be translated.

我不确定为什么在我 运行 时它会给我这个错误。

有没有更好的方法来完成我想做的事情?

谢谢!

您可以先声明变量,然后将其传递给您的表达式。喜欢 DateOfExpiration == nextFifteenDays …..

var nextFifteenDays = DateTime.Now.Adddays(15);


var nextTwoDays = DateTime.Now.AddDays(2);

或者您可以使用 EntityFunctions.Diffdays

@neil 很好地解释了错误的原因,DateTime.Now 会不断变化,因此无法使用。

这个问题有两种解决方法。

我先解释一下为什么会报错。 EF 正在将您的代码转换为 SQL Expression。使用 Expression 时,您无法在任何 C# 代码中进行解析并期望它能正常工作。 EF 需要能够将其转换为 SQL。这就是为什么您收到异常说明它无法翻译您的代码的原因。

当直接在 DbSetDbContextWhere 子句中工作时,您实际上是在与 IQueryableWhere 方法对话。其中有一个参数 Expression<Func<bool, T>>,其中 T 是您的 DbSet<T> 类型。

您要么需要简化查询,以便将其翻译成 SQL。或者您可以将 IQueryable 转换为 IEnumerable.

当您想运行在SQL服务器上查询时,您需要使用第一种方法。但是,当您可以 运行 在客户端进行查询时,您可以使用第二种方法。

vivek nuna 提到的第一个方法是使用 EntityFunctions。它可能不足以满足您的用例,因为它的功能有限。

关于 DateTime.Now 在每次迭代中都不同的说法是不正确的。因为查询只转换一次,所以它不会在每次迭代时 运行 查询。它根本不知道如何将您的查询翻译成 SQL.

https://docs.microsoft.com/en-us/dotnet/api/system.data.objects.entityfunctions?view=netframework-4.8

第二种方法是在 DbSet 之后添加 AsEnumerable。这将从您的 SQL 服务器查询您的所有数据,并在 C# 中进行评估。如果您有大型数据集,通常不建议这样做。示例:

var chemicals = _context.Chemical.AsEnumerable(); // this will get the complete collection

chemicals.Where(i => i.Value == true); // everything will work now

并回答“更好”的方法。这是更简洁的代码:

如果您有 BaseCatalyst 的导航属性,您也可以将其包含在查询中。

请注意,您仍然可以在使用 _context.Chemical 上的 Where 提取所有客户端之前查询服务器端,然后包含并调用 AsEnumerable.

var chemicals = _context.Chemical.Include(i => i.Base).Include(i => i.Catalyst).AsEnumerable();

foreach (var chemical in chemicals.Where(i => true)) // set correct where clause
{
    _warningService.SendSafetyWarning(chemical, chemical.Base, chemical.Catalyst);
}

DateTime.TotalDays returns 一个双。

将代码更改为:

可能会更幸运
foreach (var chemical in _context.Chemical.Where(c => ((int)(c.DateOfExpiration - DateTime.Now).TotalDays == 15) || ((int)(c.DateOfExpiration - DateTime.Now).TotalDays == 2)))
{
    // send out notices
    var base = _context.Base.Find(chemical.BaseId);
    var catalyst = _context.Catalyst.Find(chemical.CatalystId);

    _warningService.SendSafetyWarning(chemical, base, catalyst.Name);
}