简体   繁体   中英

“Object reference not set to an instance of an object” - but nothing is null?

Yeah, you probably think; "God, another one?".

Yes, another one.

"Object reference not set to an instance of an object."

I've been working with EF6 lately and after developing for some time, I found that a little bit more optimization was needed. Alot has been reworked without problems, but it seems I can't figure out this one.

In my application I've been using this piece of pseudo code to get items from the database.

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

Note that the DbContext is only there for clarification. It gets disposed when it's needed.

This approach "works" but it would mean that if I had, say, 20 names to look up, I would hit the database for about 20 times. Ouch!

Therefore I started looking for a way to implement a single query. I've found a way, but it's not really working as it should. This is my current approach;

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

This should, to my knowledge, translate in SELECT ... FROM Names WHERE ... IN (...) . However, my application crashes at foreach(var name in query) as soon as it hits name , throwing the feared NullReferenceException . It does, however, pass if(query == null) , meaning the query is not null. At this point, I was confused. How can it not be null, but still throw this error?

I was not sure if the query gets executed if I try to access it with this approach. Therefore, I tried to create a list from the query using ToList() , but this throws the same exception upon creating the list.

It seems like everytime I make a call to query , it gives me a NullReferenceException . However, it still passes if(query == null) . So, my question is;

Why is it passing the test, but is it not accessible? Did I misinterpret IQueryable<> ? And if I did misinterpret it, how should it be done properly?

EDIT

I have debugged before posting. I know for sure that;

  • names is not null.
  • context is not null.

Code calling the function:

//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

After some more debugging, I found that the query being created is somewhat different. It is supposed to be;

SELECT `Extent1`.`Name` 
FROM `Names` AS `Extent1` 
WHERE (`Extent1`.`Name` IN ( @gp1,@gp2))

However, I get this;

System.Data.Entity.Infrastructure.DbQuery<MyDbContext.Names>

As the actual query.

The stack trace;

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

After you posted stacktrace I spotted that you using MySQL and so my guess is that you hit this bug: Exception when using IEnumera.Contains(model.property) in Where predicate

So solution would be to ensure you have versions of MySQL Connector/NET 6.7.6 / 6.8.4 / 6.9.5 and newer. Or try to use Any method instead of Contains .

Ps This bug report came from this post by Alnedru: Int[].Contains doesn't work in EF6

Your null check on query will never fail, because it's returning the IQueryable object (that is, the query being built). You've instantiated it and started building a query, so it will always pass.

To be clear - IQueryable is roughly equivalent to a string containing the ADO.Net Select statement. It is not, inherently, the actual data.

This doesn't explain why it's throwing a null exception, but it does explain why the null check passes, and the foreach could still fail.

EDIT: When attempting to duplicate this, I found that I get an exception when using the below code:

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

It wasn't a NullReferenceException , but a NotSupportedException , as ICollections Contains has no supported translation to SQL. Switching the parameter to List caused the problem to disappear:

public IEnumerable<string> GetExistingNames(List<string> names)

Or you can convert it to a list on the fly:

IQueryable<Names> query = Names.Where(n => names.ToList().Contains(n.Name));

Why don't you just add an extension method to alleviate the stress this brings you. try this piece of code

namespace HelperExtensionMethods
{
    public static class ExtensionMethods
    {
        public static string UpdateNullString(this string testNullstring)
        {
            if (TestNullstring == null)
                return "";
            return Testullstring;
        }
    }
}

and then call it like so

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM