I'm trying to join tree lists together with left joins in linq, but I'm getting an System.NullReferenceException in the select statement.
printing out dataCost gives data in the form { ParrentLineNo = 0, Cost = 230 } and dataPrice gives { ParrentLineNo = 0, Price = 500 }
I was hoping that dataJoined would hold data in the form {lineNo,Cost,Price}
I think the problem is lineNo = 9. It is not included in the dataCost, så I would expect the x.cost in dataJoined to be null.
I have tried with x.Cost?? line.Cost, but that does not work with doubles.
hope you can help med solve this.
This is my code:
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);
}
}
}
}
The cause of the problem is you have lines like this, that assign DefaultIfEmpty
to a variable:
from line in lines
join x in dataCost on line.ParrentLineNo equals x.ParrentLineNo into mma
from x in mma.DefaultIfEmpty()
Since DefaultIfEmpty
will return the default of the type if the result is empty, and knowing that the default value for all classes is null
, then we can't later do this:
select new
{
line.LineNo,
x.Cost, // Can't do this when 'x' is 'null'
y.Price
};
Because x
is null
, we can't access it's Cost
property.
One way around this is to just use a null
value for that anonymous type property in that case:
select new
{
line.LineNo,
x?.Cost, // The '?.' operator returns 'null' if the left side is 'null'
y?.Price
};
Note that we should treat y
the same way, since with some data sets it may also be null
and we'd have the same exception.
I saw in one of your comments you said,
" How can I get an output for lineNo 9 like { LineNo = 0, Cost = null, Price = 500 }"
To actually output "null"
(as a string), we need to take over how the classes are output rather than relying on the default Property = value
, since null
produces just a blank space in the default implementation. Instead, we can do something like:
// 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"}}}");
}
Another solution would be to ignore cases where x
is null (remove them from our results) by not using the DefaultIfEmpty
method:
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
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.