Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChangeTracker.Entries() throws NullReferenceException #6157

Closed
xrkolovos opened this issue Jul 25, 2016 · 9 comments
Closed

ChangeTracker.Entries() throws NullReferenceException #6157

xrkolovos opened this issue Jul 25, 2016 · 9 comments
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@xrkolovos
Copy link

xrkolovos commented Jul 25, 2016

Steps to reproduce

Realy don't know

The issue

I have this code

public async Task<int> SaveChangesAsync()
        {
            try
            {
                ApplyConcepts();
                return await base.SaveChangesAsync();
            } 
            catch (Exception ex) {
                throw ex;
            }
        }
protected virtual void ApplyConcepts()
        {
            foreach (var entry in ChangeTracker.Entries())
            {
                switch (entry.State)
                {
                    case EntityState.Added:
                        SetCreationAuditProperties(entry);
                        //CheckAndSetTenantIdProperty(entry);
                        //EntityChangedEventHelper.TriggerEntityCreatedEvent(entry.Entity);
                        break;
                    case EntityState.Modified:
                        PreventSettingCreationAuditProperties(entry);
                        //CheckAndSetTenantIdProperty(entry);
                        SetModificationAuditProperties(entry);

                        if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
                        {
                            if (entry.Entity is IDeletionAudited)
                            {
                                SetDeletionAuditProperties(entry.Entity.As<IDeletionAudited>());
                            }
                            //EntityChangedEventHelper.TriggerEntityDeletedEvent(entry.Entity);
                        }
                        else
                        {
                            //EntityChangedEventHelper.TriggerEntityUpdatedEvent(entry.Entity);
                        }

                        break;
                    case EntityState.Deleted:
                        PreventSettingCreationAuditProperties(entry);
                        HandleSoftDelete(entry);
                        //EntityChangedEventHelper.TriggerEntityDeletedEvent(entry.Entity);
                        break;
                }
            }
        }

And when i try to insert something random i get this error. Any clue?

Exception message:
ChangeTracker.Entries() 'ChangeTracker.Entries()' threw an exception of type 'System.NullReferenceException'    System.Collections.Generic.IEnumerable<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry> {System.NullReferenceException}

Stack trace:
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.RelationshipsSnapshot.RemoveFromCollection(IPropertyBase propertyBase, Object removedEntity)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.RemoveFromCollectionSnapshot(IPropertyBase propertyBase, Object removedEntity)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.RemoveFromCollection(InternalEntityEntry entry, INavigation navigation, IClrCollectionAccessor collectionAccessor, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable`1 added, IEnumerable`1 removed)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable`1 added, IEnumerable`1 removed)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigation navigation)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
   at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.TryDetectChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.Entries()

Further technical details

EF Core version: 1.0.0
Operating system: win10
Visual Studio version: VS 2015

@ajcvickers
Copy link
Contributor

@xrkolovos Can you please post code for the entity types that are being used? Also, if the application is adding more than just a single entity, then can you post code that reproduces how the entities are being added?

@xrkolovos
Copy link
Author

xrkolovos commented Jul 25, 2016

I actually managed to make it work.
The issue was that before saving my new entity i was retrieving some entities from the db without using AsNotracking(). This code was trying to create many rows automatically for the user from some inputs. After calling Table.Add(entity) for each one of them, the entity that was retrieved without AsNotracking(that was related with the one i was trying to save) was attached to the new entity causing this problem.
The mayor problem i think was poor error information.

@ajcvickers
Copy link
Contributor

@xrkolovos Glad you got it working, but there still should not be any case where this code throws null-ref exception, so if you could help me reproduce the issue that would be great.

@xrkolovos
Copy link
Author

@ajcvickers it's complicate to find a way to reproduce it. I must give you all the source code for my model layer and repository layer and add you sample console app to trigger it.
Do you have any other ideas?
We can have a call/meeting if you want to show you.

@rowanmiller
Copy link
Contributor

We're not too concerned about this one as it sounds like a very difficult situation to get into. If you end up supplying a way to reproduce it, then we'll look into it, but we're not too concerned if you can't.

@multiarc
Copy link
Contributor

@ajcvickers looks like I spot the same issue.
Here is the preconditions:

public class Customer
{
   public int Id { get; set; }
   public int Status { get; set; }
   public ICollection<Order> Orders { get; set; }
}

public class Order
{
   public int Id { get; set; }
   public int CustomerId { get; set; }
   public Customer Customer { get; set; }
}
  1. Read the collection of Order entity with tracking (do not include Customer)
  2. Update the Order.CustomerId to the different one (existing real Id, not sure if matters)
  3. Read the collection of primary entities with tracking (Customers), this way NavigationFixer won't connect Orders collections respectively to Customer
  4. Update any field in customer (Status)
  5. SaveChanges()
  6. Profit -> ((HashSet)_values[index]).Remove(removedEntity); will fail in RelationshipsSnapshot because the related collection is null, because wasn't set by NavigationFixer because was updated prior to read.

@ajcvickers ajcvickers reopened this Sep 28, 2016
@ajcvickers ajcvickers self-assigned this Sep 28, 2016
@rowanmiller rowanmiller added this to the 1.1.0 milestone Oct 3, 2016
@ajcvickers
Copy link
Contributor

The problem here is that EF is not aware of the change made to CustomerId. When a change is made to an entity, it is a requirement that the application make EF aware of this change before performing any further operations with the context. In this case there are three main ways of doing this:

  • Call context.ChangeTracker.DetectChanges()
  • Use EF APIs to make the update--e.g. context.Entry(order).Property(e => e.CustomerId).CurrentValue = 2;
  • Implement INotifyPropertyChanged on entity types and configure EF to use these nnotifications with HasChangeTrackingStrategy

Any one of these will result in correct behavior.

The problem with any other approach is that detecting changes can be slow, which means if we do it automatically in lots of places, then EF perf will get much worse. So we choose to call DetectChanges automatically only in the places where it adds most value. Typically before a new query is not one of those places.

The behavior if the context is not made aware of some change to an entity is undefined. However, in this case it is possible to be more resilient to a null relationship snapshot. I will submit a PR for that,

@ajcvickers
Copy link
Contributor

Note for triage: RelationshipsSnapshot can be null when DetectChanges (or something equivalent) has not been used when it was needed. This is an application bug (not calling DetectChanges or similar) and the behavior of EF is undefined. However, in some cases the application may work fine because the FK is correct and the lack of fixed up navigation property is not significant to EF.

Three possible resolution paths come to mind for this case:

  • Be resilient to null relationship snapshot. This is a trivial change, and can sometimes make an application work correctly--such as in this case.
  • However, even though the application may work, it also may not, and being resilient may mean the application fails later, perhaps with data corruption. Therefore, we could check for null and throw an exception indicating that DetectChanges (or similar) is needed. Of course, there are many other code paths where DetectChanges is needed where this exception would never be thrown.
  • Do nothing. Behavior of EF is undefined, so throwing NullRefEx is reasonable.

@ajcvickers ajcvickers removed this from the 1.1.0 milestone Oct 4, 2016
@rowanmiller rowanmiller modified the milestones: 1.2.0, 1.1.0 Oct 10, 2016
@rowanmiller
Copy link
Contributor

Consensus is that we go with option 1

ajcvickers added a commit that referenced this issue Oct 12, 2016
ajcvickers added a commit that referenced this issue Oct 14, 2016
@ajcvickers ajcvickers modified the milestones: 1.1.0-preview1, 1.1.0 Oct 14, 2016
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Oct 14, 2016
@ajcvickers ajcvickers modified the milestones: 1.1.0-preview1, 1.1.0 Oct 15, 2022
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

4 participants