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

ForeignKeyAttribute prevents inverse navigation to be properly matched up #16704

Closed
Wouter8 opened this issue Jul 23, 2019 · 8 comments
Closed
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

@Wouter8
Copy link

Wouter8 commented Jul 23, 2019

I've created a relatively complex structure of entities. I'm creating a quiz site and on this site I want to have multiple types of questions and therefor also custom types of answers.

I've created a simple method where an answer gets created based on the type of question it is answering. The object I'm getting is correct - it has the correct type and all fields are filled in correctly as well. Upon saving the context with a newly added answer of type MultipleAnswersRepeating I get an error that entity framework is trying to cast my object to another answer type, MultipleAnswers.

Unable to cast object of type xxx.Models.Answers.MultipleAnswersRepeating' to type 'xxx.Models.Answers.MultipleAnswers'

See the class definitions:

    public class MultipleAnswers : Answer
    {
        [ForeignKey("AnswerId")]
        public virtual ICollection<Partials.PartialAnswer> Answers { get; set; }
    }

    public class MultipleAnswersRepeating : Answer
    {
        [ForeignKey("AnswerId")]
        public virtual ICollection<Partials.PartialAnswerRepeating> Answers { get; set; }
    }


    public class PartialAnswerRepeating : PartialAnswer
    {
    }

    public class PartialAnswer : PartialAnswerBase
    {
    }

I'm getting exactly the same error in a very small project with just the relevant classes. You can find this project here.

I have specified all types that inherit from a base class in OnModelCreating.

        public DbSet<Answer> Answers { get; set; }
        public DbSet<PartialAnswerBase> PartialAnswers { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            // Answers
            builder.Entity<MultipleAnswers>();
            builder.Entity<MultipleAnswersRepeating>();

            // PartialAnswers
            builder.Entity<PartialAnswer>();
            builder.Entity<PartialAnswerRepeating>();

        }

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

Exception message:
Unable to cast object of type 'EFErrorTest.Models.Answers.MultipleAnswersRepeating' to type 'EFErrorTest.Models.Answers.MultipleAnswers'.

Stack trace:

Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.GetCollection(Object instance)\r\n   at
Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.GetOrCreateCollection(Object instance)\r\n   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.Add(Object instance, Object value)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.AddToCollection(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IReadOnlyList`1 containingPrincipalKeys, IReadOnlyList`1 containingForeignKeys, Object oldValue, Object newValue)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IReadOnlyList`1 keys, IReadOnlyList`1 foreignKeys, Object oldValue, Object newValue)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectKeyChange(InternalEntityEntry entry, IProperty property)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.PropertyChanged(InternalEntityEntry entry, IPropertyBase property, Boolean setModified)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean setModified)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.SetForeignKeyProperties(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, Boolean setModified)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IReadOnlyList`1 containingPrincipalKeys, IReadOnlyList`1 containingForeignKeys, Object oldValue, Object newValue)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IReadOnlyList`1 keys, IReadOnlyList`1 foreignKeys, Object oldValue, Object newValue)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectKeyChange(InternalEntityEntry entry, IProperty property)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.PropertyChanged(InternalEntityEntry entry, IPropertyBase property, Boolean setModified)\r\n   at 
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean setModified)\r\n   at 
Microsoft.EntityFrameworkCore.Update.ColumnModification.set_Value(Object value)\r\n   at Microsoft.EntityFrameworkCore.Update.ModificationCommand.PropagateResults(ValueBuffer valueBuffer)\r\n   at 
Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithPropagation(Int32 commandIndex, RelationalDataReader reader)\r\n   at 
Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
@ajcvickers
Copy link
Contributor

Note for triage: this also repros on 3.0 preview 7.

@Wouter8
Copy link
Author

Wouter8 commented Jul 26, 2019

@ajcvickers please let me know whenever you have an estimate for when this will be resolved. I need it for my project (which isn't live yet) and might need to disable some functionality until it's fixed.

Thanks in advance.

@ajcvickers
Copy link
Contributor

@AndriySvyryd Can you take a look at this?

@Wouter8 We'll have a better idea once we understand the issue. It may get fixed for the 3.0 release, but since that release is already over-booked we may have to push it out to a later release.

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Jul 30, 2019

@Wouter8 As a workaround remove the ForeignKeyAttributes and configure the relationships explicitly:

builder.Entity<MultipleAnswers>().HasMany(m => m.Answers).WithOne(p => (MultipleAnswers)p.Answer).HasForeignKey(p => p.AnswerId);
builder.Entity<MultipleAnswersRepeating>().HasMany(m => m.Answers).WithOne(p => (MultipleAnswersRepeating)p.Answer).HasForeignKey(p => p.AnswerId);

@AndriySvyryd AndriySvyryd changed the title EF trying to cast object to different type upon saving ForeignKeyAttribute prevents inverse navigation to be properly matched up Jul 30, 2019
@Wouter8
Copy link
Author

Wouter8 commented Jul 30, 2019

Awesome, I'll try that after my vacation. Thanks for getting back to me.

@Wouter8
Copy link
Author

Wouter8 commented Aug 4, 2019

@Wouter8 As a workaround remove the ForeignKeyAttributes and configure the relationships explicitly:

builder.Entity<MultipleAnswers>().HasMany(m => m.Answers).WithOne(p => (MultipleAnswers)p.Answer).HasForeignKey(p => p.AnswerId);
builder.Entity<MultipleAnswersRepeating>().HasMany(m => m.Answers).WithOne(p => (MultipleAnswersRepeating)p.Answer).HasForeignKey(p => p.AnswerId);

Tried this solution. This does generate a new column in the new migration, PartialAnswers.MultipleAnswersId specifically. This shouldn't be necessary since PartialAnswers.AnswerId should be used.

This new column doesn't even work, since it'll leave the existing AnswerId empty. @AndriySvyryd am I missing something? I've updated the sample project with your proposed solution (https://github.com/Wouter8/EFErrorTest)

@ajcvickers
Copy link
Contributor

@AndriySvyryd Simplified repo and model built on current nightly:

[Table("Answers")]
public abstract class Answer
{
    public int Id { get; set; }
}

public class PartialAnswer : PartialAnswerBase
{
}

[Table("PartialAnswers")]
public class PartialAnswerBase
{
    public int Id { get; set; }
    public int AnswerId { get; set; }

    [ForeignKey("AnswerId")]
    public virtual Answer Answer { get; set; }
}

public class PartialAnswerRepeating : PartialAnswer
{
}

public class MultipleAnswers : Answer
{
    public virtual ICollection<PartialAnswer> Answers { get; set; }
}

public class MultipleAnswersRepeating : Answer
{
    public virtual ICollection<PartialAnswerRepeating> Answers { get; set; }
}

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

    public DbSet<Answer> Answers { get; set; }
    public DbSet<PartialAnswerBase> PartialAnswers { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        // Answers
        builder.Entity<MultipleAnswers>()
            .HasMany(m => m.Answers)
            .WithOne(p => (MultipleAnswers)p.Answer)
            .HasForeignKey(p => p.AnswerId);
        builder.Entity<MultipleAnswersRepeating>()
            .HasMany(m => m.Answers)
            .WithOne(p => (MultipleAnswersRepeating)p.Answer)
            .HasForeignKey(p => p.AnswerId);

        // PartialAnswers
        builder.Entity<PartialAnswer>();
        builder.Entity<PartialAnswerRepeating>();
    }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            var model = context.Model;
        }
    }
}
Model: 
  EntityType: Answer Abstract
    Properties: 
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      Discriminator (no field, string) Shadow Required
    Keys: 
      Id PK
  EntityType: MultipleAnswers Base: Answer
    Navigations: 
      Answers (<Answers>k__BackingField, ICollection<PartialAnswer>) Collection ToDependent PartialAnswer
  EntityType: MultipleAnswersRepeating Base: Answer
    Navigations: 
      Answers (<Answers>k__BackingField, ICollection<PartialAnswerRepeating>) Collection ToDependent PartialAnswerRepeating Inverse: Answer
  EntityType: PartialAnswer Base: PartialAnswerBase
    Properties: 
      MultipleAnswersId (no field, Nullable<int>) Shadow FK Index
    Foreign keys: 
      PartialAnswer {'MultipleAnswersId'} -> MultipleAnswers {'Id'} ToDependent: Answers
  EntityType: PartialAnswerBase
    Properties: 
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      AnswerId (int) Required FK Index
      Discriminator (no field, string) Shadow Required
    Keys: 
      Id PK
  EntityType: PartialAnswerRepeating Base: PartialAnswer
    Navigations: 
      Answer (<Answer>k__BackingField, Answer) ToPrincipal MultipleAnswersRepeating Inverse: Answers
    Foreign keys: 
      PartialAnswerRepeating {'AnswerId'} -> MultipleAnswersRepeating {'Id'} ToDependent: Answers ToPrincipal: Answer

@AndriySvyryd
Copy link
Member

@Wouter8 For this to work PartialAnswerRepeating needs to derive from PartialAnswerBase

AndriySvyryd added a commit that referenced this issue Aug 19, 2019
Some cleanup in relationship builders

Resolves #16704
@AndriySvyryd AndriySvyryd removed their assignment Aug 19, 2019
@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 Aug 19, 2019
AndriySvyryd added a commit that referenced this issue Aug 19, 2019
Some cleanup in relationship builders

Resolves #16704
smitpatel pushed a commit that referenced this issue Aug 21, 2019
Some cleanup in relationship builders

Resolves #16704
@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
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

No branches or pull requests

3 participants