首页 > 解决方案 > linq左连接中的NullReferenceException,如何处理空值?

问题描述

我正在尝试将树列表与 linq 中的左连接一起加入,但我在 select 语句中得到了 System.NullReferenceException。

打印出 dataCost 以 { ParrentLineNo = 0, Cost = 230 } 的形式给出数据,dataPrice 给出 { ParrentLineNo = 0, Price = 500 }

我希望 dataJoined 以 {lineNo,Cost,Price} 的形式保存数据

我认为问题是 lineNo = 9。它不包含在 dataCost 中,所以我希望 dataJoined 中的 x.cost 为空。

我试过 x.Cost ?? line.Cost,但这不适用于双打。

希望你能帮助med解决这个问题。

这是我的代码:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup
{
    class Line
    {
        public int LineNo { get; set; }
        public int ParrentLineNo { get; set; }
        public double Cost { get; set; }
        public double Price { get; set; }
        public string IsItem { get; set; }
        public string VareType { get; set; }

        static public List<Line> Data()
        {
            Line line9 = new() { LineNo = 9, ParrentLineNo = 9, Cost = 99, Price = 999, IsItem = "No", VareType = "Vare" };
            Line line1 = new() { LineNo = 0, ParrentLineNo = 0, Cost = 100, Price = 500, IsItem = "No", VareType = "Stykliste" };
            Line line2 = new() { LineNo = 1, ParrentLineNo = 0, Cost = 110, Price = 0, IsItem = "Yes", VareType = "Vare" };
            Line line3 = new() { LineNo = 2, ParrentLineNo = 0, Cost = 120, Price = 0, IsItem = "Yes", VareType = "Vare" };
            Line line4 = new() { LineNo = 3, ParrentLineNo = 3, Cost = 130, Price = 1000, IsItem = "No", VareType = "Stykliste" };
            Line line5 = new() { LineNo = 4, ParrentLineNo = 3, Cost = 140, Price = 0, IsItem = "Yes", VareType = "Vare" };
            Line line6 = new() { LineNo = 5, ParrentLineNo = 3, Cost = 150, Price = 0, IsItem = "Yes", VareType = "Vare" };

            List<Line> lines = new() { line9, line1, line2, line3, line4, line5, line6 };
            return lines;
        }
    }
    class Program
    {
        static public void Main()
        {
            var lines = Line.Data();

            // Build pricedata
            var dataPrice = from line in lines
                            where line.IsItem == "No"
                            group line by line.ParrentLineNo into Group
                            select new
                            {
                                ParrentLineNo = Group.Key,
                                Price = Group.Sum(x => x.Price)
                            };

            // Build costdata
            var dataCost = from line in lines
                           where line.IsItem == "Yes"
                           group line by line.ParrentLineNo into Group
                           select new
                           {
                               ParrentLineNo = Group.Key,
                               Cost = Group.Sum(x => x.Cost)
                           };
    
            // Join it all
            var dataJoined = from line in lines
                             join x in dataCost on line.ParrentLineNo equals x.ParrentLineNo into mma
                             from x in mma.DefaultIfEmpty()
                             join y in dataPrice on line.ParrentLineNo equals y.ParrentLineNo into pma
                             from y in pma.DefaultIfEmpty()
                             select new
                             {
                                 line.LineNo,
                                 x.Cost,
                                 y.Price
                             };

            // Then display
            foreach (var d in dataJoined)
            {
                Console.WriteLine(d);
            }
        }
    }
}

标签: c#linq

解决方案


问题的原因是你有这样的行,它分配DefaultIfEmpty给一个变量:

from line in lines
join x in dataCost on line.ParrentLineNo equals x.ParrentLineNo into mma
from x in mma.DefaultIfEmpty()

由于DefaultIfEmpty如果结果为空,将返回类型的默认值,并且知道所有类的默认值为null,那么我们以后不能这样做:

select new
{
    line.LineNo,
    x.Cost,       // Can't do this when 'x' is 'null'
    y.Price
};

因为xis null,我们无法访问它的Cost属性。

解决此问题的一种方法是在这种情况下仅使用null该匿名类型属性的值:

select new
{
    line.LineNo,
    x?.Cost,      // The '?.' operator returns 'null' if the left side is 'null'
    y?.Price
};

请注意,我们应该y以相同的方式处理,因为对于某些数据集也可能如此null,并且我们会有相同的例外。


我在你的一个评论中看到你说,

“如何获得第 9 行的输出,例如 { LineNo = 0, Cost = null, Price = 500 }”

要实际输出"null"(作为字符串),我们需要接管类的输出方式,而不是依赖于 default Property = value,因为null在默认实现中只产生一个空格。相反,我们可以这样做:

// The '??' operator returns the right side if the left side is null

foreach (var d in dataJoined)
{ 
    Console.WriteLine($"{{LineNo = {d.LineNo}, " + 
        $"Cost = {d.Cost?.ToString() ?? "null"}, " +  
        $"Price = {d.Price?.ToString() ?? "null"}}}");
}

另一种解决方案是x通过不使用以下方法来忽略 null 的情况(从我们的结果中删除它们)DefaultIfEmpty

var dataJoined = from line in lines
    join x in dataCost on line.ParrentLineNo equals x.ParrentLineNo into mma
    from x in mma
    join y in dataPrice on line.ParrentLineNo equals y.ParrentLineNo into pma
    from y in pma
    select new
    {
        line.LineNo,
        x.Cost,
        y.Price
    };

推荐阅读