c# - error system nullreferenceexception referencia a objeto no establecida como instancia de un objeto
"Referencia de objeto no establecida en una instancia de un objeto"-pero nada es nulo? (3)
Sí, probablemente pienses; "Dios, ¿otro?".
Sí, otro.
"Referencia a objeto no establecida como instancia de un objeto."
He estado trabajando con EF6 últimamente y después de desarrollarlo durante un tiempo, descubrí que se necesitaba un poco más de optimización. Mucho se ha reelaborado sin problemas, pero parece que no puedo entenderlo.
En mi aplicación he estado usando esta pieza de pseudo código para obtener elementos de la base de datos.
DbContext context = new DbContext();
public IEnumerable<string> GetExistingNames(IEnumerable<string> names)
{
foreach(string name in names)
{
string existingName = context.Names.Where(n => n.Name == name).FirstOrDefault();
if(existingName == null) continue;
yield return existingName;
}
}
Tenga en cuenta que el DbContext
solo está ahí para aclararlo. Se elimina cuando es necesario.
Este enfoque "funciona" pero significaría que si tuviera, digamos, 20 nombres para buscar, golpearía la base de datos unas 20 veces. ¡Ay!
Por lo tanto, comencé a buscar una forma de implementar una única consulta. Encontré una manera, pero en realidad no está funcionando como debería. Este es mi enfoque actual;
public IEnumerable<string> GetExistingNames(ICollection<string> names)
{
IQueryable<Names> query = context.Names.Where(n => names.Contains(n.Name));
if(query == null) yield break;
foreach(var name in query)
{
yield return name.Name;
}
}
A mi entender, esto debería traducirse en SELECT ... FROM Names WHERE ... IN (...)
. Sin embargo, mi aplicación se bloquea en foreach(var name in query)
tan pronto como llega al name
, lanzando la temida NullReferenceException
. Sin embargo, pasa if(query == null)
, lo que significa que la consulta no es nula. En este punto, estaba confundido. ¿Cómo no puede ser nulo, pero todavía arrojar este error?
No estaba seguro de si la consulta se ejecuta si intento acceder a ella con este enfoque. Por lo tanto, intenté crear una lista a partir de la consulta utilizando ToList()
, pero arroja la misma excepción al crear la lista.
Parece que cada vez que hago una llamada para query
, me da una NullReferenceException
. Sin embargo, todavía pasa if(query == null)
. Entonces, mi pregunta es;
¿Por qué está pasando la prueba, pero no es accesible? ¿He malinterpretado IQueryable<>
? Y si lo malinterpreté, ¿cómo debería hacerse correctamente?
EDITAR
He depurado antes de publicar. Sé con seguridad eso;
-
names
no es nulo. -
context
no es nulo
Código que llama a la función:
//A wrapper for the DbContext. This is only used for some methods
//which require the DbContext
DbContextWrapper wrapper = new DbContextWrapper();
public void ProcessNames(List<string> inputNames)
{
//...
foreach(string existingName in wrapper.GetExistingNames(inputNames))
{
//Do something with the names
}
//...
}
EDIT 2
Después de más depuración, descubrí que la consulta que se está creando es algo diferente. Se supone que es;
SELECT `Extent1`.`Name`
FROM `Names` AS `Extent1`
WHERE (`Extent1`.`Name` IN ( @gp1,@gp2))
Sin embargo, entiendo esto;
System.Data.Entity.Infrastructure.DbQuery<MyDbContext.Names>
Como la consulta real.
El rastro de la pila;
at MySql.Data.Entity.SqlGenerator.Visit(DbPropertyExpression expression)
at MySql.Data.Entity.SqlGenerator.Visit(DbInExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbInExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
at MySql.Data.Entity.SqlGenerator.VisitBinaryExpression(DbExpression left, DbExpression right, String op)
at MySql.Data.Entity.SqlGenerator.Visit(DbAndExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbAndExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
at MySql.Data.Entity.SelectGenerator.Visit(DbFilterExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbFilterExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
at MySql.Data.Entity.SqlGenerator.VisitInputExpression(DbExpression e, String name, TypeUsage type)
at MySql.Data.Entity.SelectGenerator.VisitInputExpressionEnsureSelect(DbExpression e, String name, TypeUsage type)
at MySql.Data.Entity.SelectGenerator.Visit(DbProjectExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbProjectExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
at MySql.Data.Entity.SelectGenerator.GenerateSQL(DbCommandTree tree)
at MySql.Data.MySqlClient.MySqlProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree)
at System.Data.Entity.Core.Common.DbProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition..ctor(DbProviderFactory storeProviderFactory, DbCommandTree commandTree, DbInterceptionContext interceptionContext, IDbDependencyResolver resolver, BridgeDataReaderFactory bridgeDataReaderFactory, ColumnMapFactory columnMapFactory)
at System.Data.Entity.Core.EntityClient.Internal.EntityProviderServices.CreateCommandDefinition(DbProviderFactory storeProviderFactory, DbCommandTree commandTree, DbInterceptionContext interceptionContext, IDbDependencyResolver resolver)
at System.Data.Entity.Core.EntityClient.Internal.EntityProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Boolean streaming, Span span, IEnumerable`1 compiledQueryParameters, AliasGenerator aliasGenerator)
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__6()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__5()
at System.Data.Entity.Infrastructure.DefaultExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at MyNameSpace.DbContextWrapper.<GetExistingNames>d__1b.MoveNext() in c:~omitted~/DbContextWrapper.cs:line 70
at MyNameSpace.NameProcessor.ProcessNames(List<string> inputNames) in c:~omitted~/NameProcessor.cs:line 60
La verificación nula en la consulta nunca fallará, ya que devuelve el objeto IQueryable (es decir, la consulta que se está creando). Lo ha instanciado y comenzó a generar una consulta, por lo que siempre pasará.
Para que quede claro: IQueryable es más o menos equivalente a una cadena que contiene la instrucción ADO.Net Select. No es, inherentemente, la información real.
Esto no explica por qué arroja una excepción nula, pero sí explica por qué pasa el cheque nulo, y el foreach todavía podría fallar.
EDITAR: Al intentar duplicar esto, descubrí que recibo una excepción cuando uso el siguiente código:
public IEnumerable<string> GetExistingNames(ICollection<string> names)
{
IQueryable<Names> query = Names.Where(n => names.Contains(n.Name));
if (query == null) yield break;
foreach (var name in query)
{
yield return name.Name;
}
}
No era una NullReferenceException
, sino una NotSupportedException
, ya que ICollections Contains
no tiene traducción soportada a SQL. Al cambiar el parámetro a List
el problema desapareció:
public IEnumerable<string> GetExistingNames(List<string> names)
O puede convertirlo en una lista sobre la marcha:
IQueryable<Names> query = Names.Where(n => names.ToList().Contains(n.Name));
¿Por qué no agregas un método de extensión para aliviar el estrés que esto te trae? prueba esta pieza de código
namespace HelperExtensionMethods
{
public static class ExtensionMethods
{
public static string UpdateNullString(this string testNullstring)
{
if (TestNullstring == null)
return "";
return Testullstring;
}
}
}
y luego llámalo así
using HelperExtesionMethods
DbContext context = new DbContext();
public IEnumerable<string> GetExistingNames(ICollection<string> names)
{
IQueryable<Names> query = context.Names.Where(n => names.UpdateNullString().Contains(n.Name.UpdateNullString()));
if(query == null) yield break;
foreach(var name in query)
{
yield return name.Name;
}
}
Después de que hayas publicado stacktrace, descubrí que estás usando MySQL y, por lo tanto, creo que aciertas con este error: Excepción al usar IEnumera.Contains (model.property) en Where predicate
Así que la solución sería asegurarse de tener versiones de MySQL Connector / NET 6.7.6 / 6.8.4 / 6.9.5 y más nuevas. O intente utilizar Any
método en lugar de Contains
.
Ps Este informe de error provino de esta publicación de Alnedru: Int []. Contiene no funciona en EF6