Search code examples
c#linqasp.net-core-mvcentity-framework-coreasp.net-core-1.0

Entity Framework Core "Object reference not set to an instance of an object" with nested .Any in LINQ


I have an ASP.NET Core application using Entity Framework Core 1.0.0.

On a specific query I am getting an "Object reference not set to an instance of an object" exception.

The query that is causing the exception is:

return mContext.ItemDatas
    .Include( a => a.ItemDataUserRoles )
    .Include( b => b.Item )
    .Include( c => c.User ).ThenInclude( d => d.Roles )
    .Where(
        s =>
            ( s.User.Id == user.Id ||
              s.ItemDataUserRoles.Any( r => r.ItemDataId == s.Id &&
                                              s.User.Roles.Any( t => t.RoleId == r.UserRoleId ) ) ) &&
            ( string.IsNullOrEmpty( id ) || s.Id == id ) &&
            ( string.IsNullOrEmpty( itemName ) || s.Item.ItemName.ToLower() == itemName.ToLower() ) &&
            s.IsActive );

The goal of the query is to return an ItemData object that is fully populated where the item belongs to a User or belongs to any role that the user belongs to. The ItemData table has a foreign key to User that indicates which user it belongs to. There is also an ItemDataUserRoles table that keeps track of the many-many relationship between ItemData and UserRole.

The rest of the query is filtering results based on an optional "id" and "itemName" that can be passed into the method.

The specific object that appears to be null is s.Item. If I change "s.Item.ItemName.ToLower()" to s.ItemId.ToLower() it works fine.

However, the actual culprit appears to be:

s.ItemDataUserRoles.Any( r => r.ItemDataId == s.Id &&
                                                     
s.User.Roles.Any( t => t.RoleId == r.UserRoleId ) ) ) &&

If I remove the "s.User.Roles.Any" section it works fine (but doesn't give me the results I need). Obviously, I can do a separate query to get the user role data and cross check, but before I separate it out manually like that, I want to make sure I'm not missing something stupid. I have spent so much time trying to figure out what is going on that my brain is fried.

It is also worth noting that if I remove the filtering on id and itemName, the query works fine and appears to correctly return the items that belong to the user or to a role that the user belongs to.

Below is the stack trace (InnerException is null):

at lambda_method(Closure , InternalEntityEntry ) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.SimpleFullyNullableDependentKeyValueFactory1.TryCreateFromCurrentValues(InternalEntityEntry entry, TKey& key) at Microsoft.EntityFrameworkCore.Query.Internal.WeakReferenceIdentityMap1.CreateIncludeKeyComparer(INavigation navigation, InternalEntityEntry entry) at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCore(Object entity, INavigation navigation) at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.Include(QueryContext queryContext, Object entity, IReadOnlyList1 navigationPath, IReadOnlyList1 relatedEntitiesLoaders, Int32 currentNavigationIndex, Boolean queryStateManager) at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.Include(QueryContext queryContext, Object entity, IReadOnlyList1 navigationPath, IReadOnlyList1 relatedEntitiesLoaders, Boolean queryStateManager) at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.Include(Object entity) at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.Include(Object entity) at Microsoft.EntityFrameworkCore.Query.Internal.GroupJoinInclude.Include(Object entity) at Microsoft.EntityFrameworkCore.Query.QueryMethodProvider.<_GroupJoin>d__264.MoveNext() at System.Linq.Enumerable.<SelectManyIterator>d__1633.MoveNext() at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.<_TrackEntities>d__152.MoveNext() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor1.EnumeratorExceptionInterceptor.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) at Web.ItemHandlers.GenericItemHandler`1.Get(DataContract dataContract, UserAccount user, ItemDbContext dbc) at Web.Controllers.ItemDataController.Get(String item)


Solution

  • There was a really stupid error in my query.

                  s.ItemDataUserRoles.Any( r => r.ItemDataId == s.Id &&
                  s.User.Roles.Any( t => t.RoleId == r.UserRoleId ) ) ) &&
    

    I was accessing the ItemDataUserRoles for the current item, however, I was then filtering the list of ItemDataUserRoles for my item to only those that match my item. Obviously, a completely redundant check. This wasn't breaking it, however. What was breaking it is the next thing I did was check that those ItemDataUserRoles match the user on the original ItemData row instead of the passed in user. This was creating a weird SQL query that was giving erroneous results.