질문 : price = co?.price ?? 0,
다음 코드 의 줄 은 위의 오류를 제공합니다. 내가 제거하면되지만 ?
에서 co.?
그것을 잘 작동합니다. 나는 그들이 온라인에서 사용 하는 이 MSDN 예제 를 따르려고 노력하고 있었 으므로 언제 사용 하고 사용 하지 않아야하는지 이해해야 할 것 같습니다 .?
select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };
?
??
오류 :
식 트리 람다는 null 전파 연산자를 포함 할 수 없습니다.
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,
....
....
};
....
....
}
답변
인용 한 예제는 쿼리의 암시 적 람다식이 대리자 로 변환되는 LINQ to Objects를 사용 하는 반면에 EF 또는 이와 유사한 쿼리를 사용 IQueryable<T>
하여 람다식이 식 트리 로 변환되는 쿼리를 사용 합니다. 식 트리는 null 조건 연산자 (또는 튜플)를 지원하지 않습니다.
그냥 예전 방식대로하세요.
price = co == null ? 0 : (co.price ?? 0)
(널 통합 연산자는 표현식 트리에서 괜찮다고 생각합니다.)
답변
링크하는 코드는 List<T>
. List<T>
구현 IEnumerable<T>
하지만 IQueryable<T>
. 이 경우 투영은 메모리에서 실행되고 ?.
작동합니다.
IQueryable<T>
매우 다르게 작동 하는 일부를 사용하고 있습니다. 를 들어 IQueryable<T>
, 투사의 표현이 생성하고 LINQ 공급자는 런타임에 수행 할 작업을 결정한다. 이전 버전과의 호환성을 ?.
위해 여기에서 사용할 수 없습니다.
LINQ 공급자에 따라 일반을 사용할 수 있지만 .
여전히 NullReferenceException
.
답변
Jon Skeet의 대답은 옳았습니다. 제 경우에는 DateTime
Entity 클래스 에 사용 했습니다. 내가 사용하려고 할 때
(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");
}
}