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

时间:2018-10-27 19:37:31

标签: c# linq

(请参阅我写的答案以进一步了解情况。)

下面是一个对表STUDENTS的选定行有效的查询。然后,一次编辑将破坏s变量。怎么了?

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

students

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

然后致电:

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

以下是查询:

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);

分配部分有效,没有问题。分配ID会使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"; } } 看起来像: Query after assignment

query现在似乎无效。 query的将来使用也被证明是无效的(尽管foreach可以很好地完成)。 就其价值而言,query很好,但是通过原始表也使grade无效似乎也很好。

3 个答案:

答案 0 :(得分:4)

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

延迟执行已被解释了很多次,因此我不会在上面花费时间。简短地说,当枚举 时,该查询仅在 中执行(只是随时)。枚举意味着foreachToList等,从技术上来讲,是在调用可枚举的GetEnumerator(或枚举数的第一个MoveNext)时发生的。

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

主要问题是DBNull。从您的解释看来,最初{strong> all 的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返回索引器,索引器除了提供强类型对列的访问也会自动为您处理DBNull(当您请求null或可为空的类型时将它们转换为string),因此您不会遇到此类问题。

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

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

问题将消失。我强烈建议对其他列访问器也这样做。

答案 1 :(得分: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便完成了初始化结婚的准备,但是恰好是时候在AssignSections中构造的查询中使用结婚了!

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

魔术?

答案 2 :(得分: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,尤其是在不确定它如何工作的情况下。