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

DetectChanges can result in "the property has a temporary value" error on save #15960

Closed
edwiles opened this issue Jun 5, 2019 · 0 comments · Fixed by #16880
Closed

DetectChanges can result in "the property has a temporary value" error on save #15960

edwiles opened this issue Jun 5, 2019 · 0 comments · Fixed by #16880
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@edwiles
Copy link

edwiles commented Jun 5, 2019

With nested entity types, calling DetectChanges can result in "the property has a temporary value" error on save, referring to the innermost entity type.

Steps to reproduce

  1. Create a console application, with the following code in a file Model.cs:
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

namespace ConsoleApp.SQLite
{
    public class MyContext : DbContext
    {
        public DbSet<FirstLevel> FirstLevels { get; set; }
        public DbSet<SecondLevel> SecondLevels { get; set; }
        public DbSet<ThirdLevel> ThirdLevels { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Data Source=mydb");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<FirstLevel>().HasData(new FirstLevel { Id = 1 });
        }
    }

    public class FirstLevel
    {
        public int Id { get; set; }
        public IList<SecondLevel> SecondLevels { get; set; }
    }

    public class SecondLevel
    {
        public int Id { get; set; }
        public int FirstLevelId { get; set; }
        public FirstLevel FirstLevel { get; set; }
        public IList<ThirdLevel> ThirdLevels { get; set; }
    }

    public class ThirdLevel
    {
        public int Id { get; set; }
        public int SecondLevelId { get; set; }
        public SecondLevel SecondLevel { get; set; }
    }
}
  1. Create the following code in a file Program.cs:
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp.SQLite
{
    public class Program
    {
        public static void Main()
        {
            using (var db = new MyContext())
            {
                // Get data
                var outputs = db.FirstLevels
                    .Include(o => o.SecondLevels)
                    .ThenInclude(s => s.ThirdLevels)
                    .SingleOrDefault(c => c.Id == 1);

                // Replace with new data
                AddData(outputs);

                // Do a detect changes (e.g. maybe we want to retrieve an entity via db.Entry())
                db.ChangeTracker.DetectChanges();

                // Replace the data again
                AddData(outputs);

                // Save
                db.SaveChanges();
            }
        }

        private static void AddData(FirstLevel first)
        {
            first.SecondLevels = new List<SecondLevel>
            {
                new SecondLevel
                {
                    ThirdLevels = new List<ThirdLevel> { new ThirdLevel(), new ThirdLevel() }
                },
                new SecondLevel
                {
                    ThirdLevels = new List<ThirdLevel> { new ThirdLevel(), new ThirdLevel() }
                }
            };
        }
    }
}
  1. Create the database and run the program:
dotnet ef migrations add InitialCreate
dotnet ef database update
dotnet run

The program execution fails with the error:

Unhandled Exception: System.InvalidOperationException: The property 'SecondLevelId' on entity type 'ThirdLevel' has a temporary value. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.Validate(ModificationCommand modificationCommand)
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.BatchCommands(IReadOnlyList`1 entries)+MoveNext()
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(DbContext _, ValueTuple`2 parameters)
   at Microsoft.EntityFrameworkCore.Storage.Internal.NoopExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at ConsoleApp.SQLite.Program.Main() in C:...\Program.cs:line 29

Note that the save is successful if the code is changed in any of these ways:

  • Remove the DetectChanges line
  • Remove the second call to AddData
  • Replace the lists of ThirdLevels with empty lists
  • Move the DetectChanges line to before the first call to AddData, or after the second call to AddData

We can work around this problem in our "real" code, but I'm curious as to why it's happening so we know the workaround is sufficient. In our case we need to set a particular entity (that has been retrieved from a cache) to state Unchanged by accessing db.Entry(entity).State, which triggers a call to DetectChanges.

Further technical details

EF Core version: 2.2.4
.NET SDK: 2.2.104
Database Provider: Microsoft.EntityFrameworkCore.Sqlite (though in our "real" code we use SqlServer)
Operating system: Windows 10 Enterprise
IDE: Visual Studio 15.9.7

@ajcvickers ajcvickers added this to the 3.0.0 milestone Jun 6, 2019
@ajcvickers ajcvickers self-assigned this Jun 6, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview7 Jul 2, 2019
ajcvickers added a commit that referenced this issue Jul 31, 2019
Fixes #15960

The issue here is that when the first graph of Added entities are replaces by the second graph, it causes the first graph to be detached, but this was only happening at the first level of the graph.

Other test changes are because we were relying on being able to detach a principal without it cascading, even though cascades are configured.
@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 Jul 31, 2019
ajcvickers added a commit that referenced this issue Aug 1, 2019
Fixes #15960

The issue here is that when the first graph of Added entities are replaces by the second graph, it causes the first graph to be detached, but this was only happening at the first level of the graph.

Other test changes are because we were relying on being able to detach a principal without it cascading, even though cascades are configured.
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview9 Aug 21, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0-preview9, 3.0.0 Nov 11, 2019
@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. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants