The line price = co?.price ?? 0,
in the following code gives me the above error, but if I remove ?
from co.?
it works fine.
I was trying to follow this MSDN example where they are using ?
on line select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };
So, it seems I need to understand when to use ?
with ??
and when not to.
Error:
an expression tree lambda may not contain a null propagating operator
public class CustomerOrdersModelView
{
public string CustomerID { get; set; }
public int FY { get; set; }
public float? price { get; set; }
....
....
}
public async Task<IActionResult> ProductAnnualReport(string rpt)
{
var qry = from c in _context.Customers
join ord in _context.Orders
on c.CustomerID equals ord.CustomerID into co
from m in co.DefaultIfEmpty()
select new CustomerOrdersModelView
{
CustomerID = c.CustomerID,
FY = c.FY,
price = co?.price ?? 0,
....
....
};
....
....
}
The example you were quoting from uses LINQ to Objects, where the implicit lambda expressions in the query are converted into delegates... whereas you're using EF or similar, with IQueryable<T>
queryies, where the lambda expressions are converted into expression trees. Expression trees don't support the null conditional operator (or tuples).
Just do it the old way:
price = co == null ? 0 : (co.price ?? 0)
(I believe the null-coalescing operator is fine in an expression tree.)
The code you link to uses List<T>
. List<T>
implements IEnumerable<T>
but not IQueryable<T>
. In that case, the projection is executed in memory and ?.
works.
You're using some IQueryable<T>
, which works very differently. For IQueryable<T>
, a representation of the projection is created, and your LINQ provider decides what to do with it at runtime. For backwards compatibility reasons, ?.
cannot be used here.
Depending on your LINQ provider, you may be able to use plain .
and still not get any NullReferenceException
.
?.
would not have been prepared to handle ?.
in any reasonable way.
?.
is a new operator no? So old code would not use ?.
and thus not be broken. Linq Providers are not prepared to handle many other things such as CLR methods.
?.
. New code may be using old LINQ providers, which are prepared to handle CLR methods they don't recognise (by throwing an exception), since those fit nicely into the existing expression tree object model. Completely new expression tree node types don't fit in.
Jon Skeet's answer was right, in my case I was using DateTime
for my Entity class. When I tried to use like
(a.DateProperty == null ? default : a.DateProperty.Date)
I had the error
Property 'System.DateTime Date' is not defined for type 'System.Nullable`1[System.DateTime]' (Parameter 'property')
So I needed to change DateTime?
for my entity class and
(a.DateProperty == null ? default : a.DateProperty.Value.Date)
While expression tree does not support the C# 6.0 null propagating, what we can do is create a visitor that modify expression tree for safe null propagation, just like the operator does!
public class NullPropagationVisitor : ExpressionVisitor
{
private readonly bool _recursive;
public NullPropagationVisitor(bool recursive)
{
_recursive = recursive;
}
protected override Expression VisitUnary(UnaryExpression propertyAccess)
{
if (propertyAccess.Operand is MemberExpression mem)
return VisitMember(mem);
if (propertyAccess.Operand is MethodCallExpression met)
return VisitMethodCall(met);
if (propertyAccess.Operand is ConditionalExpression cond)
return Expression.Condition(
test: cond.Test,
ifTrue: MakeNullable(Visit(cond.IfTrue)),
ifFalse: MakeNullable(Visit(cond.IfFalse)));
return base.VisitUnary(propertyAccess);
}
protected override Expression VisitMember(MemberExpression propertyAccess)
{
return Common(propertyAccess.Expression, propertyAccess);
}
protected override Expression VisitMethodCall(MethodCallExpression propertyAccess)
{
if (propertyAccess.Object == null)
return base.VisitMethodCall(propertyAccess);
return Common(propertyAccess.Object, propertyAccess);
}
private BlockExpression Common(Expression instance, Expression propertyAccess)
{
var safe = _recursive ? base.Visit(instance) : instance;
var caller = Expression.Variable(safe.Type, "caller");
var assign = Expression.Assign(caller, safe);
var acess = MakeNullable(new ExpressionReplacer(instance,
IsNullableStruct(instance) ? caller : RemoveNullable(caller)).Visit(propertyAccess));
var ternary = Expression.Condition(
test: Expression.Equal(caller, Expression.Constant(null)),
ifTrue: Expression.Constant(null, acess.Type),
ifFalse: acess);
return Expression.Block(
type: acess.Type,
variables: new[]
{
caller,
},
expressions: new Expression[]
{
assign,
ternary,
});
}
private static Expression MakeNullable(Expression ex)
{
if (IsNullable(ex))
return ex;
return Expression.Convert(ex, typeof(Nullable<>).MakeGenericType(ex.Type));
}
private static bool IsNullable(Expression ex)
{
return !ex.Type.IsValueType || (Nullable.GetUnderlyingType(ex.Type) != null);
}
private static bool IsNullableStruct(Expression ex)
{
return ex.Type.IsValueType && (Nullable.GetUnderlyingType(ex.Type) != null);
}
private static Expression RemoveNullable(Expression ex)
{
if (IsNullableStruct(ex))
return Expression.Convert(ex, ex.Type.GenericTypeArguments[0]);
return ex;
}
private class ExpressionReplacer : ExpressionVisitor
{
private readonly Expression _oldEx;
private readonly Expression _newEx;
internal ExpressionReplacer(Expression oldEx, Expression newEx)
{
_oldEx = oldEx;
_newEx = newEx;
}
public override Expression Visit(Expression node)
{
if (node == _oldEx)
return _newEx;
return base.Visit(node);
}
}
}
It passes on the following tests:
private static string Foo(string s) => s;
static void Main(string[] _)
{
var visitor = new NullPropagationVisitor(recursive: true);
Test1();
Test2();
Test3();
void Test1()
{
Expression<Func<string, char?>> f = s => s == "foo" ? 'X' : Foo(s).Length.ToString()[0];
var fBody = (Expression<Func<string, char?>>)visitor.Visit(f);
var fFunc = fBody.Compile();
Debug.Assert(fFunc(null) == null);
Debug.Assert(fFunc("bar") == '3');
Debug.Assert(fFunc("foo") == 'X');
}
void Test2()
{
Expression<Func<string, int>> y = s => s.Length;
var yBody = visitor.Visit(y.Body);
var yFunc = Expression.Lambda<Func<string, int?>>(
body: yBody,
parameters: y.Parameters)
.Compile();
Debug.Assert(yFunc(null) == null);
Debug.Assert(yFunc("bar") == 3);
}
void Test3()
{
Expression<Func<char?, string>> y = s => s.Value.ToString()[0].ToString();
var yBody = visitor.Visit(y.Body);
var yFunc = Expression.Lambda<Func<char?, string>>(
body: yBody,
parameters: y.Parameters)
.Compile();
Debug.Assert(yFunc(null) == null);
Debug.Assert(yFunc('A') == "A");
}
}
Success story sharing
np()
method. See github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagationFunc
supports the operator usage. Also, I wonder if there's a reason the error says "null-propagating" as opposed to "null-conditional", which is what the official docs call it?