c# - framework - odbc.net core
La excepción "Objeto que se puede nullable debe tener un valor" después de comprobar null en un objeto no primitivo/no estructural (2)
Estoy obteniendo el Nullable object must have a value after checking for null
en un objeto normal, después de una verificación nula. He encontrado varias preguntas, sobre todo con respecto a linq-to-sql, teniendo el mismo problema, pero siempre con tipos primitivos que aceptan nulos (como en bool?
O DateTime?
).
La línea que causa la excepción en mi caso se ve así:
myDataContext.Orders.Where(y => customer.Address == null || (string.IsNullOrEmpty(customer.Address.Street) || y.Customers.Addresses.Street == customer.Address.Street)))
clase de customer
ve así:
public class Customer
{
private Address address = null;
public Address Address{get{return address;} set{address=value;}}
}
address
propiedad de address
ve así:
public class Address
{
private string street = null;
public string Street{get{return street ;} set{street =value;}}
}
Si reemplazo la línea de código anterior con esto:
string custStreet = null;
if (customer.Address != null)
{
custStreet = customer.Address.Street;
}
myDataContext.Orders.Where(y =>(customer.Address == null || (string.IsNullOrEmpty(custStreet) || y.Customers.Addresses.Street == custStreet)))
funciona bien No entiendo la razón de eso. Tampoco quiero definir innumerables variables antes de ejecutar la instrucción Lambda.
Tenga en cuenta también que la declaración anterior de Lambda es parte de una cláusula Lambda Where
mucho más grande que contiene unas cuantas declaraciones más. Sé que podría trabajar con Expression Trees, pero habiendo codificado hasta aquí, realmente no quiero cambiar ahora.
editar
a medida que la pregunta fue respondida, voy a contarte cómo trabajé al respecto: me construyo un iniciador de propiedades recursivo. Todo lo que no sea una cadena, una lista / matriz o un tipo primitivo se lanza contra la clase Activator
. Obtuve la idea desde aquí e hice algunos cambios en ella (básicamente, ignoro todo lo que no necesita ser inicializado y en lugar de Activator.CreateInstance(Type.GetType(property.PropertyType.Name));
utilicé Activator.CreateInstance(property.PropertyType));
Ni siquiera estoy seguro de si la versión utilizada en la pregunta original funcionaría o por qué alguien querría usarla).
El valor de la expresión customer.Address.Street
debe evaluar con su valor * antes de que la consulta se pueda traducir a SQL. Esa expresión no se puede dejar en el SQL subyacente para que la base de datos posiblemente, o posiblemente no, evalúe un valor. El proveedor de consultas tiene que evaluarlo incondicionalmente para determinar cómo debería ser el SQL. Entonces sí, necesitas realizar la verificación nula fuera de la expresión. Por supuesto, hay varias formas en que podría hacerlo, pero esa lógica de verificación nula necesita estar fuera de la expresión que traduce el proveedor de consultas.
Al contrario de lo que escribí en los comentarios, el problema es que los proveedores de consultas ni siquiera intentan reducir las expresiones de predicado eliminando las partes constantes. Como @Servy declaró correctamente en los comentarios, no están obligados a hacerlo, y hablando en términos generales puede haber una razón técnica para no hacerlo, pero en realidad las personas tienden a usar tales condiciones en sus expresiones de consulta y esperan que funcionen como si se evalúan en LINQ to Objects.
He visto muchas preguntas con un uso similar, la última es que los condicionales de LINQ a Entidades dan resultados extraños , y el comentario / respuesta "estándar" es - usa Encadenado Where
with if
o algún constructor de predicados. Entonces empiezo a pensar, vale, los proveedores no hacen eso, entonces ¿por qué no lo hacemos nosotros mismos? Después de todo, somos desarrolladores y podemos escribir (algunos) códigos. Así que terminé con el siguiente método de extensión que usa ExpressionVisitor
para modificar el árbol de expresiones de consulta. Estaba pensando en publicarlo en la pregunta vinculada, pero como me he involucrado de alguna manera en este hilo, aquí tienes:
public static class QueryableExtensions
{
public static IQueryable<T> ReduceConstPredicates<T>(this IQueryable<T> source)
{
var reducer = new ConstPredicateReducer();
var expression = reducer.Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class ConstPredicateReducer : ExpressionVisitor
{
private int evaluateConst;
private bool EvaluateConst { get { return evaluateConst > 0; } }
private ConstantExpression TryEvaluateConst(Expression node)
{
evaluateConst++;
try { return Visit(node) as ConstantExpression; }
catch { return null; }
finally { evaluateConst--; }
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var operandConst = TryEvaluateConst(node.Operand);
if (operandConst != null)
{
var result = Expression.Lambda(node.Update(operandConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return EvaluateConst ? node : base.VisitUnary(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var leftConst = TryEvaluateConst(node.Left);
if (leftConst != null)
{
if (node.NodeType == ExpressionType.AndAlso)
return (bool)leftConst.Value ? Visit(node.Right) : Expression.Constant(false);
if (node.NodeType == ExpressionType.OrElse)
return !(bool)leftConst.Value ? Visit(node.Right) : Expression.Constant(true);
var rightConst = TryEvaluateConst(node.Right);
if (rightConst != null)
{
var result = Expression.Lambda(node.Update(leftConst, node.Conversion, rightConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
}
return EvaluateConst ? node : base.VisitBinary(node);
}
protected override Expression VisitConditional(ConditionalExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var testConst = TryEvaluateConst(node.Test);
if (testConst != null)
return Visit((bool)testConst.Value ? node.IfTrue : node.IfFalse);
}
return EvaluateConst ? node : base.VisitConditional(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var expressionConst = node.Expression != null ? TryEvaluateConst(node.Expression) : null;
if (expressionConst != null || node.Expression == null)
{
var result = Expression.Lambda(node.Update(expressionConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return EvaluateConst ? node : base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var objectConst = node.Object != null ? TryEvaluateConst(node.Object) : null;
if (objectConst != null || node.Object == null)
{
var argumentsConst = new ConstantExpression[node.Arguments.Count];
int count = 0;
while (count < argumentsConst.Length && (argumentsConst[count] = TryEvaluateConst(node.Arguments[count])) != null)
count++;
if (count == argumentsConst.Length)
{
var result = Expression.Lambda(node.Update(objectConst, argumentsConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
}
return EvaluateConst ? node : base.VisitMethodCall(node);
}
}
}
Con ese método de extensión implementado, todo lo que necesita es insertar .ReduceConstPredicates()
al final de sus consultas (antes de AsEnumerable()
, ToList
y similares):
var query = myDataContext.Orders
.Where(y => customer.Address == null || string.IsNullOrEmpty(customer.Address.Street) || y.Customers.Addresses.Street == customer.Address.Street)
.ReduceConstPredicates();