dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.79k stars 3.19k forks source link

Inheritance not identifying keys/Ids correctly #4113

Closed drbergeron closed 2 years ago

drbergeron commented 8 years ago

I Have an entity type that inherits from a class, that inherits from a class with an Id, similar to below:

public abstract class BaseSystemObject
    {
        public BaseSystemObject()
        {
            this.CreatedDate = DateTime.UtcNow;
            this.ModifiedDate = DateTime.UtcNow;
        }
        public int Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
        public bool Deleted { get; set; }
    }

I then have a class inheriting this with some properties for mapping:

public class BaseObjectAttribute : BaseSystemObject
    {
        public BaseObjectAttribute() : base()
        {
            //
        }
        public bool AllowMultipleValues { get; set; }
     //...etc
    }

And then I have the derived class that I want to map in EF:

public class OrderAttributeMap : BaseObjectAttribute
    {
        public OrderAttributeMap() : base()
        {

        }
       public int AttributeRef { get; set; }
        public DateTime Date { get; set; }
        //...etc
    }

DbContext looks as follows:

//Dbcontext class
public DbSet<OrderAttributeMap> OrderAttributeMaps { get; set; }
modelBuilder.Entity(ModelBuilders.OrderAttributeMapConfiguration.GetConfiguration());

//modelbuilder class
static class OrderAttributeMapConfiguration
    {
        public static Action<EntityTypeBuilder<OrderAttributeMap>> GetConfiguration()
        {
            Action<EntityTypeBuilder<OrderAttributeMap>> config = cm =>
            {
                 cm.ToTable(OrderAttributeMaps);
                cm.HasKey(c => c.Id);  //This line getting the error
                cm.Property(c => c.AttributeRef);
                cm.Property(c => c.Date);
                cm.Property(c => c.AllowMultiple);
                //....etc
                cm.Property(c => c.CreatedDate).IsRequired(true); ;
                cm.Property(c => c.ModifiedDate).IsRequired(true);
                cm.Property(c => c.Deleted).IsRequired(true);

            };
            return config;
        }
    }

When this is executing I'm getting a runtime error with the following message: System.InvalidOperationException occurred HResult=-2146233079 Message=The derived type 'Models.OrderAttributeMap' cannot have keys other than those declared on the root type. Source=EntityFramework.Core StackTrace: at Microsoft.Data.Entity.Metadata.Internal.EntityType.AddKey(IReadOnlyList1 properties) at Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.<>c__DisplayClass14_0.<HasKey>b__1() at Microsoft.Data.Entity.Metadata.Internal.MetadataDictionary2.GetOrAdd(Func1 getKey, Func1 createKey, Func2 createValue, Func2 onNewKeyAdded, ConfigurationSource configurationSource) at Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.HasKey(IReadOnlyList1 properties, ConfigurationSource configurationSource) at Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.PrimaryKey(IReadOnlyList1 properties, ConfigurationSource configurationSource) at Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.PrimaryKey(IReadOnlyList1 clrProperties, ConfigurationSource configurationSource) at Microsoft.Data.Entity.Metadata.Builders.EntityTypeBuilder1.HasKey(Expression1 keyExpression) at Sql.Implementations.ModelBuilders.OrderAttributeMapConfiguration.<>c.<GetConfiguration>b__0_0(EntityTypeBuilder1 cm) in C:\Programming\Solution-Dev\src\Sql.Implementations\ModelBuilders\Order\OrderAttributeMapConfiguration.cs:line 16 InnerException:

Based on the code there is only an ID on the root (BaseSystemObject). Is there something i'm missing here? I've tried to use the .HasBaseType(); but i got another error about using shadow state Entities.

Let me know if I need to provide anything else

drbergeron commented 8 years ago

Note that it will work without an error if you remove the BaseObjectAttribute class and have the OrderAttributeMap inherit directly from BaseSystemObject where the ID is, but this may not be the best solution in all cases where a more complicated inheritance is wanted.

smitpatel commented 8 years ago

Based on the exception, OrderAttributeMap class has another entity type set as its base type. (We have updated the exception message, if you use nightly build then the exception message will indicate what is set as root type too). Looking at the CLR hierarchy, if any of BaseObjectAttribute or BaseSystemObject is added as an entity type in the model then it will be set as base type for OrderAttributeMap. With the additional comment you made, it looks like BaseObjectAttribute is added as entity type. If that is not intended then verify that it is not being added to the model through conventions or explicit configuration.

rowanmiller commented 8 years ago

@drbergeron I took the code you have listed here and I wasn't able to reproduce the issue. As @smitpatel mentioned, the issue is probably because somewhere in your configuration you are doing something that is pulling BaseObjectAttribute or BaseSystemObject into the model. This could be that they are referenced from a navigation property in another type, or you are calling modelBuilder.Entity<T>() on them at some point when doing configuration.

using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Metadata.Builders;
using System;

namespace ConsoleApp1
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using (var db = new BloggingContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
            }
        }
    }

    class BloggingContext : DbContext
    {
        public DbSet<OrderAttributeMap> OrderAttributeMaps { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Sample;Trusted_Connection=True;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity(OrderAttributeMapConfiguration.GetConfiguration());
        }

        static class OrderAttributeMapConfiguration
        {
            public static Action<EntityTypeBuilder<OrderAttributeMap>> GetConfiguration()
            {
                Action<EntityTypeBuilder<OrderAttributeMap>> config = cm =>
                {
                    cm.ToTable("OrderAttributeMaps");
                    cm.HasKey(c => c.Id);
                    cm.Property(c => c.AttributeRef);
                    cm.Property(c => c.Date);
                    cm.Property(c => c.AllowMultipleValues);
                    cm.Property(c => c.CreatedDate).IsRequired(true); ;
                    cm.Property(c => c.ModifiedDate).IsRequired(true);
                    cm.Property(c => c.Deleted).IsRequired(true);

                };
                return config;
            }
        }

    }

    public abstract class BaseSystemObject
    {
        public BaseSystemObject()
        {
            this.CreatedDate = DateTime.UtcNow;
            this.ModifiedDate = DateTime.UtcNow;
        }

        public int Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
        public bool Deleted { get; set; }
    }

    public class BaseObjectAttribute : BaseSystemObject
    {
        public bool AllowMultipleValues { get; set; }
    }

    public class OrderAttributeMap : BaseObjectAttribute
    {
        public int AttributeRef { get; set; }
        public DateTime Date { get; set; }
    }
}
rowanmiller commented 8 years ago

I'm going to close this one out as I'm pretty confident that we've got to the bottom of this... but feel free to re-open if needed (please be sure to include a code listing to reproduce the issue).

drbergeron commented 8 years ago

@rowanmiller @smitpatel Thanks for the follow up. To confirm I did have the BaseObjectAttribute somewhere that was causing this error. I'll need to check TFS history and i'll update this issue a bit later with exactly what the call was. Thank you.