【问题标题】:Index was out of range exception in query查询中的索引超出范围异常
【发布时间】:2011-05-18 02:47:40
【问题描述】:

每条路线都包含特定顺序的位置。
例如: NY -> LA 不同于 LA -> NY。
我想编写一个方法来获取位置数组并返回 true 或 false 是否存在具有相同位置和顺序的路线。
我需要使用 linq 到实体和实体框架(Route 和 Location 是实体)来做到这一点。 这是我写的:

    public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
    {
        Route route = null;
        if (locationsInRoute.Count > 0)
        {
            var query = GetRoutesQuery().
                Where(x => x.Locations.Count() == locationsInRoute.Count);

            for (int i = 0; i < locationsInRoute.Count; i++)
            {
                long locationId = locationsInRoute[i].LocationId;
                query = query.Where(x => 
    x.Locations.ElementAt(i).LocationId == locationId); //THROWS EXCEPTION
            }
            route = query.SingleOrDefault();
        }
        return route!=null;
    }

我在标记的行中得到以下异常:

Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

这个异常的原因是什么?

编辑
执行route = query.SingleOrDefault();时发生异常,异常抱怨Where(x =&gt; x.Locations.ElementAt(i).LocationId == locationId);

【问题讨论】:

  • GetRoutes() 方法返回什么类型?
  • @Kirk Broadhurst:GetRoutesQuery() 返回 IQueryable。当我使用 GetRoutesQuery().ToList() 时,我没有例外。问题出在我标记的地方。
  • 如果没有看到您的完整系统和应用程序,很难判断。我建议您调试、放置一些断点并检查/观察查询、locationsInRoute 和 i 的值。
  • @Kirk Broadhurst:我无法设置断点和调试 SingleOrDefault。

标签: c# .net entity-framework linq-to-entities


【解决方案1】:

我相信这个查询是完全错误的。首先,它不是 linq-to-entities 查询,而且永远不会是因为 linq to entity 无法使用索引。我认为比较有序序列必须在 memory = linq-to-objects 中执行。

另一个问题是这样的:

for (int i = 0; i < locationsInRoute.Count; i++)
{
    long locationId = locationsInRoute[i].LocationId;
    query = query.Where(x => x.Locations.ElementAt(i).LocationId == locationId);
}
route = query.SingleOrDefault();

我认为这是known gotcha,当使用 Linq、查询内置循环和延迟执行时 - 我相信这总是将 locationId 与最后一个元素进行比较。

在我看来,最有效的方法是存储过程,使用表值参数传递您的预期序列并使用 SQL 游标比较存储过程中的序列。

【讨论】:

    【解决方案2】:

    看起来您的 x.Locations.Count() 可能小于您的 locationsInRoute.Count。你确定不是吗?我是说 b/c 你正在调用 x.Locations.ElementAt(i),如果 i > Count() 将抛出。

    作为旁注,您正在做的更好的解决方案是覆盖相等性或在您想要唯一的类上实现 IComparer,然后您可以使用 Any() 和 Contains() 之类的东西进行测试。

    【讨论】:

    • 我不能使用 Contains() 或 Any() 因为位置的顺序很重要。我认为 i>Count()-1 因为在执行时查询 i 已经等于 Count()。
    【解决方案3】:

    如果你得到一个索引超出范围异常,它一定意味着locationsRoute集合中的元素数量超过了IQueryable中的元素数量。如果您尝试测试提供的列表中的每个位置是否包含在路线中,您应该能够执行以下操作:

    var locationIds = locationsInRoute.Select(l => l.LocationId);
    query = query.Where(r => r.Locations.All(l => locationIds.Contains(l.LocationId)))
    

    【讨论】:

    • 包含不是一个好的条件,因为 (NY, LA) 包含 (LA. NY) 但它们与我之前描述的路线不同。
    • 在这种情况下,我将创建 ID 或哈希码的排序字符串组合,作为比较唯一“路由标识符”的简单方法
    【解决方案4】:

    我猜这与您使用 ElementAt, which can't be translated to SQL(请参阅无翻译的运算符部分)来对您的 IQueryable 进行操作有关。这在第一次迭代中实现了 IQueryable 的结果集,因此后续迭代 Route 项将无法访问其相关的 Locations 集。这可能只发生在第二次迭代中,但 LINQ 延迟执行性质的无数影响在任何情况下对我来说并不完全清楚 ;-) HTH

    您可以在 SingleOrDefault 处放置一个断点并检查它在那里执行的 SQL 语句,以查看为什么没有返回记录供 SingleOrDefault 查找。尽管根据您拥有的路由数量,SQL 可能非常难看。

    【讨论】:

    • 你能想到其他方法来查找路线是否存在吗?
    • 这看起来很荒谬,但您可以尝试 Skip(i - 1).Take(1) 而不是 ElementAt(i)。不过,谈论一个 hack,我不知道性能如何。
    【解决方案5】:

    感谢@Ladislav Mrnka 的建议,这是解决方案:

    public class LocationSequenceEqual : IEqualityComparer<Location>
        {
            public bool Equals(Location x, Location y)
            {
                return x.Id == y.Id;
            }
    
            public int GetHashCode(Location obj)
            {
                return obj.Id.GetHashCode();
            }
        }
    
        public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
        {
            Route route = null;
            if (locationsInRoute.Count > 0)
            {
                var query = GetRoutesQuery().
                    Where(x => x.Locations.Count() == locationsInRoute.Count);
    
                query = query.Where(x => x.Locations.OrderBy(l => l.Order).
                        Select(l => l.Location).SequenceEqual(locations, new LocationSequenceEqual()));
                route = query.FirstOrDefault();
            }
            return route!=null;
        }
    

    【讨论】:

      【解决方案6】:

      如果 Location 有你上面指出的 Order,这个可以完全在 (Linq to) SQL 中完成:

      public bool IsRouteExists(IList<LocationInRoute> locationsInRoute)
      {
          Route route = null;
          if (locationsInRoute.Count == 0)
              return;
      
          var possibleRoutes = GetRoutesQuery().
              Where(x => x.Locations.Count() == locationsInRoute.Count);
      
          var db = GetDataContext(); //get a ref to the DataContext or pass it in to this function
          for (var i = 0; i < locationsInRoute.Length; i++)
          {
              var lcoationInRoute = locationsInRoute[i];
              possibleRoutes = possibleRoutes.Where(x => x.Locations.Any(l => l.Id == locationInRoute.Id && l.Order == locationInRoute.Order));
          }
          route = possibleRoutes.FirstOrDefault();
      
          return route!=null;
      }
      

      【讨论】:

      • 有一个技巧——我没有提到。这些位置可以以相同的顺序但不同的编号存储。例如:NY(1) - LA(2) 与 NY(4) - LA(9) 相同。顺序不必相同 - 只是顺序。看看我上面的解决方案。
      猜你喜欢
      • 2011-03-16
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      • 2023-04-10
      • 1970-01-01
      相关资源
      最近更新 更多