编辑结果数据时LINQ查询无效?

问题描述 投票:2回答:3

(请参阅我写的答案,以便更好地了解情况。)

下面是一个很好的查询,在选定的表STUDENTS行上运行。然后一个编辑破坏query变量。这有什么不对?

students是从导入数据表中选择的行,部分由以下部分定义:

    importTable.Columns.Add("SECTION", typeof(string));
    importTable.Columns.Add("NUMBER", typeof(string));
    importTable.Columns.Add("ID", typeof(string));

(因为DataTable是无类型的,我需要将数据转换为字符串以使用字段)。

然后叫:

IEnumerable<DataRow> s = importTable.AsEnumerable();
IEnumerable<DataRow> t = s
  .OrderBy(r => r["HALL"]);
IEnumerable<DataRow> sortedTable = t
  .OrderBy(r => 
          { //if (r["ID"] is DBNull)
            //  return "";
            //else
              return r["ID"]; // ERROR
          });

IEnumerable<DataRow> tue = sortedTable.Where(r => r["DAY"].Equals("TUE"));
IEnumerable<DataRow> wed = sortedTable.Where(r => r["DAY"].Equals("WED"));
AssignSections(tue);
AssignSections(wed);

这是查询:

public void AssignSections(IEnumerable<DataRow> students)
{
  IEnumerable<IEnumerable<DataRow>> query = from e in students.AsEnumerable()
              orderby (e["SHORTSCHOOL"]  as string).PadRight(30) + e["SEED"] as string
              group e by new { DAY=e["DAY"], GRADE=e["GRADE"] } into g
              orderby g.Key.GRADE as string
              select g.AsEnumerable();

  var queryList = query.ToList(); // ArgumentException during "WED" call

  foreach (var grade in query)
    foreach (var student in grade)
      if (student["ID"] == DBNull.Value)
      {
        student["SECTION"] = "S";
        student["ID"] = "ID1";
      }
}

分配SECTION工作,没有问题。分配ID会导致query看起来像:Query after assignment

query现在看来无效。 query的未来用途也证明是无效的(尽管foreach完成得很好)。对于它的价值,grade很好,但students也通过原始表无效也似乎也没问题。

c# linq
3个回答
4
投票

这里没有魔力。它是LINQ查询Deferred ExecutionDBNull的使用的组合,无法与其他类型进行比较。

延迟执行已经多次解释,所以我不会花时间在它上面。简而言之,查询是在枚举时唯一(但随时)执行的。枚举意味着foreachToList等,从技术上说,当调用可枚举的GetEnumerator(或调查员的第一个MoveNext)时。

您需要记住的是,在您定义它们时,不会执行(评估)返回LINQ查询的IEnumerable<T>(或IQueryable<T>),而是每次枚举它们(直接或间接)。这应该解释为“令我惊讶的答案是LINQ重新排序代码”部分来自你自己的答案。不,LINQ不对代码重新排序,通过在与定义查询变量的位置不同的某些点重新评估LINQ查询,这是您的代码。如果您只想在特定点评估它们一次,那么可以通过添加ToListToArray和类似的方法来实现,这些方法枚举查询并将结果存储在内存集合中的某些内容中,并使用该集合进行进一步处理。它仍然是IEnumerable<T>,但是进一步的枚举将枚举查询结果而不是重新评估查询。

主要问题是DBNull。从你的解释看起来最初所有的ID值都是DBNull,所以第一个查询运行良好(DBNull知道如何与自己比较:)。一旦源包含至少一个不是DBNull的值,使用OrderBy的任何进一步查询该列与默认的IComparer将失败。

它可以使用以下简单代码轻松复制无数据表:

var data = new[]
{
    new { Id = (object)DBNull.Value },
    new { Id = (object)DBNull.Value }
};
var query = data.OrderBy(e => e.Id);

query.ToList(); // Success

data[1] = new { Id = (object)"whatever" };
query.ToList(); // Fail

显示延迟查询执行和重新评估,或直接(以证明问题不是编辑):

new[]
{
    new { Id = (object)DBNull.Value },
    new { Id = (object)"whatever" }
}
.OrderBy(e => e.Id)
.ToList(); // Fail

解决方案是完全避免使用DBNull。使用as string最简单(并且比ToString()DataTable好得多)是使用DataRowExtensions.Field扩展方法而不是object返回索引器,除了提供对列的强类型访问外,还会自动处理DBNulls(当你请求null时将它们转换为string或可空类型),所以你不会遇到这样的问题。

可以通过将有问题的代码更改为来证明

.OrderBy(r => r.Field<string>("ID"))

问题就会消失。我强烈建议也为其他列访问器执行此操作。


0
投票

令我惊讶的是,LINQ重新排序代码。背景是这样的:

  IEnumerable<DataRow> s = importTable.AsEnumerable();
  IEnumerable<DataRow> t = s
      .OrderBy(r => r["HALL"]);
  IEnumerable<DataRow> sortedTable = t
      .OrderBy(r => 
              { //if (r["ID"] is DBNull)
                //  return "";
                //else
                  return r["ID"]; // ERROR
              });

  IEnumerable<DataRow> tue = sortedTable.Where(r => r["DAY"].Equals("TUE"));
  IEnumerable<DataRow> wed = sortedTable.Where(r => r["DAY"].Equals("WED"));
  AssignSections(tue);
  AssignSections(wed);

3条注释线表示故障。然后发生了什么:sortedTable被部分初始化,以便提供Where子句来初始化tue。但是后来,sortedTable完成了初始化,在代码中出现了对assign wed的调用,但是恰好及时在AssignSections中构造的查询中使用了wed!

所以ERROR在AssignSections期间出现,当代码绕过完成sortedTable的初始化时,我可以通过添加3个禁用行并在“return”上设置断点来检测到这一点;

魔法?


0
投票

DBNull和null不一样...... 正如您的原始错误消息所示“对象必须是字符串类型”(要分配给字符串) DBNull无法强制转换为字符串,it is a class ... 您需要在代码中处理此案例。有关简单的帮助方法,请参阅此链接: Unable to cast object of type 'System.DBNull' to type 'System.String

using System;
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {

            DBNull dbNull = DBNull.Value;
            Console.WriteLine(typeof(string).IsAssignableFrom(typeof(DBNull)));//False
            Console.WriteLine(dbNull is string); //False

            //Console.WriteLine((string)dbNull);  // compile time error
            //Console.WriteLine(dbNull as string); // compile time error

            Console.ReadLine();
        }
    }
}

另外,请确保阅读“延迟加载”/“延迟执行”如何在LINQ / IEnumerable上工作。 您不必一直使用IEnumerable,特别是如果您不确定它是如何工作的。

© www.soinside.com 2019 - 2024. All rights reserved.