表达式树 lambda 可能不包含空传播操作符

下面代码中的代码行 price = co?.price ?? 0,给出了上面的错误,但是如果我从 co.?中删除 ?,它就可以正常工作。

我试图跟随 这个 MSDN 的例子他们在线使用 select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };所以,似乎我需要了解什么时候使用 ???和什么时候不使用。

错误 :

表达式树 lambda 可能不包含空传播操作符

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,
....
....
};
....
....
}
79874 次浏览

您引用的例子使用 LINQ to Objects,其中查询中的隐式 lambda 表达式被转换为 代表... ... 而您使用 EF 或类似的方法,使用 IQueryable<T>查询,其中 lambda 表达式被转换为 表情树。表达式树不支持空条件运算符(或元组)。

按老办法来:

price = co == null ? 0 : (co.price ?? 0)

(我相信空聚合运算符在表达式树中是可行的。)

链接到的代码使用 List<T>List<T>实现 IEnumerable<T>,但不实现 IQueryable<T>。在这种情况下,投影在内存中执行,?.工作。

你在使用一些 IQueryable<T>,它的工作原理非常不同。对于 IQueryable<T>,将创建投影的表示,并由 LINQ 提供程序决定在运行时如何处理它。由于向后兼容性原因,此处不能使用 ?.

根据您的 LINQ 提供程序,您可能能够使用普通的 .,但仍然得不到任何 NullReferenceException

Jon Skeet 的回答是正确的,在我的例子中,我在 Entity 类中使用 DateTime。 当我试图使用

(a.DateProperty == null ? default : a.DateProperty.Date)

是我搞错了

Property 'System.DateTime Date' is not defined for type 'System.Nullable`1[System.DateTime]' (Parameter 'property')

所以我需要为我的实体类改变 DateTime?

(a.DateProperty == null ? default : a.DateProperty.Value.Date)

虽然表达式树不支持 C # 6.0 null 传播,但是我们可以创建一个访问者来修改表达式树以实现安全的 null 传播,就像操作符所做的那样!

这是我的:

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);
}
}
}

它通过以下测试:

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");
}
}