LINQ to SQL如何处理空值问题?

3

有人能帮我弄清楚这个问题吗?

下面的代码运行良好,并进入if语句。

foreach (var m in msg)
{
    if (string.IsNullOrEmpty(m.PhoneNumber))
    {
        m.PhoneNumber = (from c in db.Customers
                            where c.CustomerID == m.CustomerID
                            select c.PhoneNumber).Single();
    }
}

然而,在下面的代码中phoneNumber从未被设置。
foreach (var m in msg.Where(z => (z.PhoneNumber == null || z.PhoneNumber == "")))
{
     m.PhoneNumber = (from c in db.Customers
                      where c.CustomerID == m.CustomerID
                      select c.PhoneNumber).Single();
}

我认为这是因为上面的代码实际上对表达式进行了评估,而下面的代码没有。如果是这样的话,您如何在未评估的LINQ查询中检查null呢? 编辑 为了避免混淆,这里展示两种情况下msg的填充方式
    var msg = from m in db.Messages
              where (m.StatusID == (int)MessageStatus.Submitted && m.MessageBoxTypeID == (int)MessageBoxType.Outbox)
              select m;

你在两种情况下都使用相同的代码来创建“msg”序列吗?(除非我漏掉了什么,否则这两个片段对我来说是等价的。) - Douglas
你能展示一下 msg 的代码吗? - Douglas
我建议将DataContext上的日志设置为Console.Out(或文件),并查看生成的实际查询。手动运行相同的查询以查看结果。代码看起来应该能够正常工作,但显然还有其他问题。 - tvanfosson
1个回答

1
我对这个问题有点困惑,但我猜测可能是这样的。如果msg序列是一个转换为SQL查询的IQueryable<T>,那么这两个代码片段的行为可能会有所不同。假设你有:
var msg = 
    from m in dataContext.MyTable
    select m;

你的第一段代码会导致整个msg序列被枚举,从而向数据库发出未经过滤的SELECT…FROM命令,并获取表中的所有行。

foreach (var m in msg)

另外,您的第二个片段在枚举之前对序列应用了过滤器。因此,发送到数据库的命令是 SELECT...FROM...WHERE

foreach (var m in msg.Where(z => (z.PhoneNumber == null || z.PhoneNumber == "")))

在.NET中应用的过滤器的行为与其转换为Transact-SQL时可能会有所不同。例如,区分大小写。在您的情况下,我认为不匹配是由于条目的PhoneNumber包含空格,因为这些可能与SQL Server中的空字符串匹配。

为了测试这种可能性,请检查如果您将第二个片段更改为以下内容会发生什么:

foreach (var m in msg.ToList().Where(z => (z.PhoneNumber == null || z.PhoneNumber == "")))
编辑:您的问题可能是在后续访问期间(当您检查是否设置了PhoneNumber时)再次执行了查询。

如果您执行:

foreach (var m in msg.Where(z => (z.PhoneNumber == null || z.PhoneNumber == "")))
{
    m.PhoneNumber = …
}

bool stillHasNulls = msg.Any(z => z.PhoneNumber == null || z.PhoneNumber == "");

你会发现,stillHasNulls 可能仍然会评估为 true,因为当你重新评估 msg 序列时(在上面的情况下,当你执行 msg.Any 时,它会向数据库发出一个 EXISTS 命令),你对 m.PhoneNumber 的赋值将会丢失。

为了保留你的 m.PhoneNumber 赋值,你需要将它们持久化到数据库中(如果这是你想要的),或者确保每次访问相同的序列元素。一种方法是使用 ToList 预先填充序列作为集合。

msg = msg.Where(z => (z.PhoneNumber == null || z.PhoneNumber == "")).ToList();
foreach (var m in msg)
{
    m.PhoneNumber = …
}

在上面的代码中,筛选器仍会作为一个SELECT…FROM…WHERE发送到数据库,但结果会被急切地评估,然后存储为msg中的列表。对于msg上的任何后续查询都将针对预填充的内存集合进行评估(其中将包含您分配给其元素的任何新值)。

ToList()会评估语句并且会工作(与第二个片段相同)。我更想知道如何在不评估表达式的情况下检查是否为空。 - Tom Squires
你是如何检查PhoneNumber是否被设置的?你是在赋值语句上设置断点,还是在循环之后检查msg元素的值? - Douglas
如果你只是想在查询中检查 null(而不进行急切评估),那么你的第二个片段是正确的。 - Douglas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接