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

Rebuilding relationships causes annotations on navigations to be lost #10612

Closed
jose8789 opened this issue Dec 27, 2017 · 3 comments
Closed

Rebuilding relationships causes annotations on navigations to be lost #10612

jose8789 opened this issue Dec 27, 2017 · 3 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

@jose8789
Copy link

Cannot include a private collection in an entity

System.NotSupportedException : Collection is read-only.
   at System.ThrowHelper.ThrowNotSupportedException(ExceptionResource resource)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.Add(Object instance, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.AddToCollection(InternalEntityEntry entry, INavigation navigation, IClrCollectionAccessor collectionAccessor, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.ToDependentFixup(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, ISet`1 handledForeignKeys, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.TrackedFromQuery(InternalEntityEntry entry, ISet`1 handledForeignKeys)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.TrackedFromQuery(InternalEntityEntry entry, ISet`1 handledForeignKeys)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.MarkUnchangedFromQuery(ISet`1 handledForeignKeys)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTrackingFromQuery(IEntityType baseEntityType, Object entity, ValueBuffer valueBuffer, ISet`1 handledForeignKeys)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.StartTracking(Object entity, IEntityType entityType)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.<IncludeCollectionAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler.<_IncludeAsync>d__20`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskLiftingExpressionVisitor.<_ExecuteAsync>d__8`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.AsyncSelectEnumerable`2.AsyncSelectEnumerator.<MoveNext>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.<SingleOrDefault_>d__381`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.TaskResultAsyncEnumerable`1.Enumerator.<MoveNext>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator`2.<MoveNextCore>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Linq.AsyncEnumerable.AsyncIterator`1.<MoveNext>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.<MoveNext>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<ExecuteSingletonAsyncQuery>d__23`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at booking.student.infrastructure.Repositories.StudentRepository.<FindAsync>d__6.MoveNext() in /Users/Jose8789/oculos/college/services/booking.student/infrastructure/booking.student.infrastructure/Repositories/StudentRepository.cs:line 36
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at booking.student.tests.StudentManagementTest.<ChangeProgressStatus>d__3.MoveNext() in /Users/Jose8789/oculos/college/services/booking.student/tests/booking.student.tests/StudentManagementTest.cs:line 107
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

Steps to reproduce

public class Student 
    {
        private List<StudentProgress> _progresses;
        protected Student()
        {
            _progresses = new List<StudentProgress>();
        }
        public IEnumerable<StudentProgress> Progresses => _progresses.AsReadOnly();
    }

public void Configure(EntityTypeBuilder<Student> studentConfiguration)
        {
            studentConfiguration.HasKey(x => x.Id);
            studentConfiguration.Ignore(x => x.DomainEvents);

            studentConfiguration.Metadata.FindNavigation(nameof(Student.Progresses))
                .SetPropertyAccessMode(PropertyAccessMode.Field);
        }
public class StudentProgress : Entity
    {
        public int ProgressStatusId { get; private set; }
        public int Year { get; private set; }
        public Grade Grade { get; private set; }
        public int CourseId { get; private set; }

        public StudentProgress(Grade grade, int year, int courseId, ProgressStatus progress)
        {
            ProgressStatusId = progress.Id;
            Year = year;
            Grade = grade;
            CourseId = courseId;
        }
    }

this is where I'm getting the error:
public async Task<Student> FindAsync(int identity)
        {
            var student = await _context.Set<Student>()
                .Include(x=>x.Progresses) // this line is generating the error
                .Where(b => b.Id == identity)
                .SingleOrDefaultAsync();

            return student;
        }
@ajcvickers
Copy link
Member

@jose8789 I attempted to reproduce this with the code posted, but there is a lot of missing pieces. Can you look at the code below and see what is different about your code that would allow us to reproduce what you are seeing? Also, which version of EF Core and with database provider are you using?

public class Student 
{
    public int Id { get; set; }

    private List<StudentProgress> _progresses;
    
    public Student()
    {
        _progresses = new List<StudentProgress>();
    }

    public IEnumerable<StudentProgress> Progresses => _progresses.AsReadOnly();
}

public class StudentProgress
{
    public int Id { get; set; }

    public int ProgressStatusId { get; private set; }
    public int Year { get; private set; }
    public int CourseId { get; private set; }

    public StudentProgress()
    {
    }

    public StudentProgress(int year, int courseId, int progressStatusId)
    {
        ProgressStatusId = progressStatusId;
        Year = year;
        CourseId = courseId;
    }
}

public class TestContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TestDatabase;ConnectRetryCount=0");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Student>(b =>
        {
            b.Property(e => e.Id).ValueGeneratedNever();

            b.HasKey(x => x.Id);

            b.HasMany(e => e.Progresses).WithOne().HasForeignKey(e => e.ProgressStatusId);

            b.Metadata.FindNavigation(nameof(Student.Progresses))
                .SetPropertyAccessMode(PropertyAccessMode.Field);
        });
    }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new TestContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.Add(new Student { Id = 7 });
            context.Add(new StudentProgress(1, 2, 7));
            await context.SaveChangesAsync();
        }

        using (var context = new TestContext())
        {
            var identity = 7;
            
            var student = await context.Set<Student>()
                    .Include(x=>x.Progresses) // this line is generating the error
                    .Where(b => b.Id == identity)
                    .SingleOrDefaultAsync();
        }
    }
}

@jose8789
Copy link
Author

jose8789 commented Dec 30, 2017

I found out why I was having that exception, I just don't know if this is a bug or not. To Reproduce the exception you can use this model:

    public class Timetable 
    {
       public int Id {get; private set;}
        private readonly List<TimetableItem> _timetableItems;
        public IEnumerable<TimetableItem> TimetableItems => _timetableItems.AsReadOnly();

        private Timetable()
        {
            _timetableItems = new List<TimetableItem>();
        }
    }

    public class TimetableItem 
    {
        public int Slot { get; private set; }
        public DayOfWeek DayOfWeek { get; private set; }
        public Guid SubjectId { get; private set; }
        private TimetableItem()
        {
        }
    }

    public class TimetableEntityTypeConfiguration : IEntityTypeConfiguration<Timetable>
    {
        public void Configure(EntityTypeBuilder<Timetable> timetatableConfiguration)
        {
            timetatableConfiguration.ToTable(nameof(Timetable));

            timetatableConfiguration.Metadata.FindNavigation(nameof(Timetable.TimetableItems))
                .SetPropertyAccessMode(PropertyAccessMode.Field);
        }
    }

    public class TimetableItemEntityTypeConfiguration : IEntityTypeConfiguration<TimetableItem>
    {
        public void Configure(EntityTypeBuilder<TimetableItem> timetableItemConfiguration)
        {
            timetableItemConfiguration.ToTable(nameof(TimetableItem));

            timetableItemConfiguration.Property<int>("TimetableId"); // if I create this property explicitly from model this error doesn't appear
            timetableItemConfiguration.HasKey("TimetableId", nameof(TimetableItem.Slot),
                nameof(TimetableItem.DayOfWeek));

            timetableItemConfiguration.HasOne<Timetable>()
                .WithMany(x => x.TimetableItems)
                .HasForeignKey("TimetableId");
        }
    }

    public Timetable FindForBooking(int timetableId)
        {
            return _context.Set<Timetable>()
                .Include(x => x.TimetableItems)
                .FirstOrDefault(x => x.Id == timetableId);
        }

But if I create use this model:

       public class TimetableItem 
       {
            public int Slot { get; private set; }
            public DayOfWeek DayOfWeek { get; private set; }
            public Guid SubjectId { get; private set; }
            public int TimetableId{get; private set;} // addition of this property avoids the exception
        
            private TimetableItem()
            {
            }
        }

the error doesn't appear

@ajcvickers
Copy link
Member

Note for triage: I was able to repro this with 2.0.1--looks like something very similar to: #7674

@jose8789 The problem seems to be that when the relationship is configured with the shadow key, then the original annotation on the navigation property is lost. A workaround is to make sure this:

timetatableConfiguration.Metadata.FindNavigation(nameof(Timetable.TimetableItems))
            .SetPropertyAccessMode(PropertyAccessMode.Field);

Happens after this:

timetableItemConfiguration.HasOne<Timetable>()
    .WithMany(x => x.TimetableItems)
    .HasForeignKey("TimetableId");

@ajcvickers ajcvickers added this to the 2.1.0 milestone Jan 3, 2018
@ajcvickers ajcvickers modified the milestones: 2.1.0-preview1, 2.1.0 Jan 17, 2018
@AndriySvyryd AndriySvyryd changed the title Collection is read-only Rebuilding relationships causes annotations on navigations to be lost Feb 10, 2018
@AndriySvyryd AndriySvyryd removed their assignment Feb 15, 2018
@AndriySvyryd AndriySvyryd added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Feb 15, 2018
@ajcvickers ajcvickers modified the milestones: 2.1.0-preview2, 2.1.0 Nov 11, 2019
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

3 participants