简体   繁体   中英

EF4 linq NullReferenceException on non null object

I am using ef4 code first with a generic repository. My repository has a select method that looks like this:

public IEnumerable<T> Select(Func<T, bool> predicate)
{
    return objectSet.Where(predicate);
}

I am calling it with the following code

pushQueueRepository.Select(x => x.User.ID == user.ID && x.PageID == pageID);

*note - pushQueueRepository has been properly instantiated.

When I run this I am getting a NullReferenceException. When I look at it in debug after the exception is thrown it shows the error being x.User.ID == user.ID. When I mouse over x.User it is null. However when I expand x there us a User object in x.User (not null) that does have an id.

FYI x is a PushQueue object that is defined as such:

public class PushQueue : IEntity
{
    ...

    [Required]
    public User User { get; set; }

    ... 
}

This doesn't seem right, am I missing something?

Thanks.

The reason for getting the exception is because you are loading all of the PushQueues in memory and then trying to apply your predicate: x => x.User.ID == user.ID and because lazy loading is NOT enabled by your code, x.User will not be lazy loaded therefore the exception is being thrown. You've not mark User navigation property as virtual , so it didn't opt in to EF Lazy Loading. When expand it in debug mode in VS, you are explicitly loading it but at runtime it's not lazy loaded and that's why you see it's populated when you expand it.

To fix this you need to change the signature of your Select method as that's the main problem: you are passing Func<T, bool> , while it needs to be Expression<Func<T, bool>> instead. Basically you want your predicate to be executed in the data store and not in memory, so you need to change the code to this:

public IEnumerable<T> Select(Expression<Func<T, bool>> predicate)
{
    return objectSet.Where(predicate);
}

Of course alternatively you can keep the select method as it is now and enable lazy loading, this way, the NullReferenceException would go away but it will result in horrible performance at runtime since EF will be trying to lazy load User on every single PushQueue object and then apply your predicate:

public virtual User User { get; set; }     

It's quite possible that by expanding x , you're causing other properties to be evaluated which then populate x.User .

Of course if this is an EF repository I'd expect the actual query to be executed in the database anyway, so what you see in the case of a query failure is somewhat unusual anyway. Have you tried looking at what query is executing in SQL?

I think you need to use the .Include:

pushQueueRepository.Include("User").Select(x => x.User.ID == user.ID && x.PageID == pageID)

.Include forces EF to load the User object right away, which would otherwise be lazily loaded and thereby not available for your comparison on User.ID.

Having a string in the .Include is not very elegant and unfortunately not compile-time checked. You can do something like this:

pushQueueRepository.Include(typeof(User).Name)....

Not very elegant either, but at least it is checked by the compiler ;-)

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