question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow replacing the dependent side of an identifying FK with a new instance

See original GitHub issue

I have a One-to-ZeroOrOne relationship that uses a pattern that is different to the style given in EF Core Relationships documentation. EF Core picks up my pattern, i.e. it knows it is a one-to-one relationship, but fails on an update when there is already an entry.

I have successfully used this one-to-one relationship pattern in EF6 and, to me anyway, it seems like a good pattern as it combines the Primary Key and Foreign Key.

NOTE: This is not urgent as I can swap to your recommended style for a one-to-one relationship, but it would be useful to know if you plan to fix this, as I use this pattern in an example.

If you are seeing an exception, include the full exceptions details (message and stack trace).

The instance of entity type 'PriceOffer' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph(EntityEntryGraphNode node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, Object oldValue, Object newValue)
   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.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at test.UnitTests.DataLayer.TestOneToOneUpdate.TestConnectedUpdateExistingRelationship() in C:\Users\Jon\Documents\Visual Studio 2015\Projects\EfCoreInAction\test\UnitTests\DataLayer\TestOneToOneUpdate.cs:line 84

Steps to reproduce

Include a complete code listing (or project/solution) that we can run to reproduce the issue.

Partial code listings, or multiple fragments of code, will slow down our response or cause us to push the issue back to you to provide code to repoduce the issue.

The two main entity classes are:

public class Book                                   
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime PublishedOn { get; set; }
    public string Publisher { get; set; }
    public decimal Price { get; set; }
    public string ImageUrl { get; set; }
    //----------------------------------------------
    //relationships
    public PriceOffer Promotion { get; set; }       
    public ICollection<Review> Reviews { get; set; }
    public ICollection<BookAuthor> AuthorsLink { get; set; }                   
}

And the one-to-one entity class, PriceOffer

public class PriceOffer                        
{
    public int BookId { get; set; }            
    public decimal NewPrice { get; set; }
    public string PromotionalText { get; set; }
}

My DbContext contains the information to define the key for PriceOffer

public class EfCoreContext : DbContext
{
    public DbSet<Book> Books { get; set; }            
    public DbSet<Author> Authors { get; set; }        
    public DbSet<PriceOffer> PriceOffers { get; set; }

    public EfCoreContext( DbContextOptions<EfCoreContext> options)      
        : base(options) {}                            

    protected override void  OnModelCreating(ModelBuilder modelBuilder)    
    {                                                 
        modelBuilder.Entity<BookAuthor>()             
            .HasKey(x => new {x.BookId, x.AuthorId}); 
        modelBuilder.Entity<PriceOffer>()             
            .HasKey(x => x.BookId);                   
    }                                                 
}

The problem comes when I try to replace/remove a PriceOffer from a Book, i.e. it only fails if there is a PriceOffer on the Book already. Here is the Unit Test that fails. It fails on the line context.SaveChanges().

[Fact]
public void TestConnectedUpdateExistingRelationship()
{
    //SETUP
    var inMemDb = new SqliteInMemory();

    using (var context = inMemDb.GetContextWithSetup())
    {
        context.SeedDatabaseFourBooks();
        var book = context.Books
            .Include(p => p.Promotion)
            .First(p => p.Promotion != null);

        //ATTEMPT
        book.Promotion = new PriceOffer
        {
            NewPrice = book.Price / 2,
            PromotionalText = "Half price today!"
        };
        context.SaveChanges();

        //VERIFY
        var bookAgain = context.Books
            .Include(p => p.Promotion)
            .Single(p => p.BookId == book.BookId);
        bookAgain.Promotion.ShouldNotBeNull();
        bookAgain.Promotion.PromotionalText.ShouldEqual("Half price today!");
    }
}

One extra piece of information. If I set the book.Promotion to null and call SaveChanges then it works, i.e. it deletes the existing PriceOffer. See Unit Test below that does not fail

[Fact]
public void TestDeleteExistingRelationship()
{
    //SETUP
    var inMemDb = new SqliteInMemory();

    using (var context = inMemDb.GetContextWithSetup())
    {
        context.SeedDatabaseFourBooks();

        var book = context.Books
            .Include(p => p.Promotion)
            .First(p => p.Promotion != null);

        //ATTEMPT
        book.Promotion = null;
        context.SaveChanges();

        //VERIFY
        var bookAgain = context.Books
            .Include(p => p.Promotion)
            .Single(p => p.BookId == book.BookId);
        bookAgain.Promotion.ShouldBeNull();
        context.PriceOffers.Count().ShouldEqual(0);
    }
}

Further technical details

EF Core version: “version”: “1.1.0” Database Provider: “Microsoft.EntityFrameworkCore.SqlServer”: “1.1.0”, NOTE: It also fails on “Microsoft.EntityFrameworkCore.SqlServer”: “1.0.1”/NET Core “version”: “1.0.1”, so its not a regression. Operating system: Window 10 IDE: Visual Studio 2015, update 3.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:37 (19 by maintainers)

github_iconTop GitHub Comments

3reactions
JonPSmithcommented, Jan 6, 2017

Hi @divega,

Clearly there is an alternative to the one-to-one PK+FK pattern I am using and I will swap to that. For that reason this issue isn’t a priority at all.

My only long-term concern is that the one-to-one PK+FK pattern looks and feels like a proper relationship until you try and change an existing relationship. All of my example code is a) simple and b) has a unit tests, so I caught this problem easily. Someone else might hit this problem and struggle to diagnose it.

1reaction
JonPSmithcommented, Oct 16, 2017

Hi @AndriySvyryd, I would like to check this fix out, as my book will go to the publishers before 2.1.0 will be released. Is there a nightly NuGet build I can access?

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - The property is part of a key and so cannot be modified ...
To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then ...
Read more >
Changing Foreign Keys and Navigations - EF Core
How to change relationships between entities by manipulating foreign keys and navigations.
Read more >
Having trouble with foreign key in entity, can't track ...
Scenario: I have an entity that is central to the business, let's call it ... can't track because the FK is already part...
Read more >
3 common foreign key mistakes (and how to avoid them)
The best way to avoid dangling foreign keys is simply to use a modern database system that can validate constraints when they're added...
Read more >
4. Using Convention and Configuration for Relationships
Code First sees that you have defined both a reference and a collection navigation property, so by convention it will configure this as...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found