(请参阅我写的答案以进一步了解情况。)
下面是一个对表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
现在似乎无效。 query
的将来使用也被证明是无效的(尽管foreach可以很好地完成)。
就其价值而言,query
很好,但是通过原始表也使grade
无效似乎也很好。
答案 0 :(得分:4)
这里没有魔术。它是LINQ查询Deferred Execution和DBNull
用法的结合,无法与其他类型进行比较。
延迟执行已被解释了很多次,因此我不会在上面花费时间。简短地说,当枚举 时,该查询仅在 中执行(只是随时)。枚举意味着foreach
,ToList
等,从技术上来讲,是在调用可枚举的GetEnumerator(或枚举数的第一个MoveNext)时发生的。
您需要记住的是,在您定义它们时,IEnumerable<T>
(或IQueryable<T>
)返回的LINQ查询不会被执行(评估),而是在每次枚举时(直接或间接)。这应该解释“我的答案令人惊讶的是LINQ重新排序代码” 部分来自您自己的答案。不,LINQ不会对代码进行重新排序,这是您的代码通过在与定义查询变量的位置不同的某些点重新评估LINQ查询来实现。如果您只想在特定时刻评估一次,则可以通过添加ToList
,ToArray
和类似的方法来进行评估,这些方法枚举查询并将结果存储在内存集合中的某个位置,并进一步使用该集合处理。它仍然是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,尤其是在不确定它如何工作的情况下。