在循环中使用 LINQ Entity Framework 表达式中的变量获取最后一个值

Using variables in LINQ Entity Framework expression inside a loop gets the last value

我正在尝试使用 C# Entity framework LINQ 创建一个 SQL 查询,它会产生类似的 SQL ,如下所示:

select * from centros
where SUBSTRING(UPPER(c_nombre),1,1)='M'
and (SUBSTRING(UPPER(c_nombre),2,1)='A' OR SUBSTRING(UPPER(c_nombre),2,1)='Á')
and SUBSTRING(UPPER(c_nombre),3,1)='L'
order by c_nombre
limit 10

这是我的代码:

public List<Entidad> SearchByDescServer(string desc, int limit)
{
    List<Entidad> entidades = new List<Entidad>();
    IQueryable<IEntidad> models = entitySet.AsNoTracking();
    for (int index = 0; index < desc.Length; index++) {
        string currentChar = desc.Substring(index,1).ToUpper();
        models = models.Where(e=>e.Descripcion.Substring(index,1).ToUpper()==currentChar);
    }
    models = models.OrderBy(e => e.Descripcion).Take(limit);            
    foreach (IEntidad m in models.ToList()) {
        entidades.Add(m.ToEntidad());
    }
    return entidades;
}

事情很简单,我在循环中向 IQueryable 对象添加 where 子句。我正在使用 PostgreSQL 数据库。调试告诉我这个:

Executed DbCommand (12ms) [
Parameters=[
@__8__locals1_index_0='3', 
@__currentChar_1='A' (Size = 150), 
@__8__locals1_index_2='3',
@__currentChar_3='A' (Size = 150), 
@__8__locals1_index_4='3',
@__currentChar_5='A' (Size = 150), 
@__p_6='10'
], CommandType='Text', CommandTimeout='30']
      SELECT e.c_id, e.c_activo, e.c_codigo, e.c_nombre
      FROM centros AS e
      WHERE ((UPPER(SUBSTRING(e.c_nombre, @__8__locals1_index_0 + 1, 1)) = @__currentChar_1) AND (UPPER(SUBSTRING(e.c_nombre, @__8__locals1_index_2 + 1, 1)) = @__currentChar_3)) AND (UPPER(SUBSTRING(e.c_nombre, @__8__locals1_index_4 + 1, 1)) = @__currentChar_5)
      ORDER BY e.c_nombre
      LIMIT @__p_6

所以发生的事情是索引参数始终是循环后索引变量的最后一个值。谁能给我解释一下这种行为??

谢谢

引入临时变量并在lambda中使用它:

for (int index = 0; index < desc.Length; index++) 
{
    var tmp = index;
    string currentChar = desc.Substring(index, 1).ToUpper();
    models = models.Where(e=>e.Descripcion.Substring(tmp, 1).ToUpper()==currentChar);
}

至于原因 - 这就是 C# 闭包在 for 循环中的工作方式。简而言之 - 对于您的匿名函数 e=>e.Descripcion.Substring(i, 1).ToUpper()==currentChar 编译器生成特殊的 class 它将保存捕获的 i 变量,该变量将从索引的第一个值更新到最后一个值:

var actions = new List<Action>();
for (int i = 0; i < 10; i++)
{   
    actions.Add(() => Console.WriteLine(i));    
}

// will print 10 times "10"
foreach (var a in actions)
{
    a();
}

请记住 foreach 循环的行为不同:

var actions = new List<Action>();
foreach (var i in Enumerable.Range(0, 10))
{
    actions.Add(() => Console.WriteLine(i));    
}

// prints from 0 to 9
foreach (var a in actions)
{
    a();
} 

有关详细信息,您可以阅读这篇 answer by Jon Skeet and/or this 文章。