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
.
P.s. This bug report came from this post by Alnedru: Int[].Contains doesn't work in EF6