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

Abstract base class unexpectedly included in TPC model resulting in InvalidOperationException (requires a key to be defined) #3525

Closed
MatthewMWR opened this issue Oct 23, 2015 · 8 comments

Comments

@MatthewMWR
Copy link

To me seems unexpected, but I could have the wrong expectation.

Exception:

System.InvalidOperationException was unhandled
  HResult=-2146233079
  Message=The entity type 'ConsoleApplication2.ToolBase' requires a key to be defined.
  Source=EntityFramework.Core
  StackTrace:
       at Microsoft.Data.Entity.Internal.ModelValidator.ShowError(String message)
       at Microsoft.Data.Entity.Internal.ModelValidator.EnsureNonNullPrimaryKeys(IModel model)
       at Microsoft.Data.Entity.Internal.ModelValidator.Validate(IModel model)
       at Microsoft.Data.Entity.Internal.RelationalModelValidator.Validate(IModel model)
       at Microsoft.Data.Entity.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
       at Microsoft.Data.Entity.Infrastructure.ModelSource.<>c__DisplayClass8_0.<GetModel>b__0(Type k)
       at Microsoft.Data.Entity.Internal.ThreadSafeDictionaryCache`2.GetOrAdd(TKey key, Func`2 factory)
       at Microsoft.Data.Entity.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
       at Microsoft.Data.Entity.Internal.DbContextServices.CreateModel()
       at Microsoft.Data.Entity.Internal.LazyRef`1.get_Value()
       at Microsoft.Data.Entity.Internal.DbContextServices.get_Model()
       at Microsoft.Framework.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c.<AddEntityFramework>b__0_5(IServiceProvider p)
       at Microsoft.Framework.DependencyInjection.ServiceLookup.FactoryService.Invoke(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.ScopedCallSite.Invoke(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceLookup.ConstructorCallSite.Invoke(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.ScopedCallSite.Invoke(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.<>c__DisplayClass12_0.<RealizeService>b__0(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.GetService(Type serviceType)
       at Microsoft.Framework.DependencyInjection.ServiceProviderExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
       at Microsoft.Framework.DependencyInjection.ServiceProviderExtensions.GetRequiredService[T](IServiceProvider provider)
       at Microsoft.Data.Entity.Storage.DatabaseProviderServices.GetService[TService]()
       at Microsoft.Data.Entity.Storage.Internal.SqlServerDatabaseProviderServices.get_Creator()
       at Microsoft.Framework.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c.<AddEntityFramework>b__0_11(IServiceProvider p)
       at Microsoft.Framework.DependencyInjection.ServiceLookup.FactoryService.Invoke(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.ScopedCallSite.Invoke(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.<>c__DisplayClass12_0.<RealizeService>b__0(ServiceProvider provider)
       at Microsoft.Framework.DependencyInjection.ServiceProvider.GetService(Type serviceType)
       at Microsoft.Framework.DependencyInjection.ServiceProviderExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
       at Microsoft.Framework.DependencyInjection.ServiceProviderExtensions.GetRequiredService[T](IServiceProvider provider)
       at Microsoft.Data.Entity.Infrastructure.AccessorExtensions.GetService[TService](IAccessor`1 accessor)
       at Microsoft.Data.Entity.Infrastructure.DatabaseFacade.EnsureCreated()
       at ConsoleApplication2.Program.Main(String[] args) in C:\Users\matt\Documents\Visual Studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 23
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

Repro:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Data.Entity;

namespace ConsoleApplication2
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            //  build hub/spoke object graph
            var toolboxInstance = new Toolbox() {Title = "This toolbox is red"};
            var hammerInstance = new Hammer() {Description = "My favorite hammer"};
            var wrenchInstance = new Wrench() {Description = "My least favorite wrench"};
            toolboxInstance.Tools.Add(hammerInstance);
            toolboxInstance.Tools.Add(wrenchInstance);

            using (var dbc = new ToolsContext())
            {
                dbc.Database.EnsureCreated();
                dbc.Database.EnsureDeleted();
                dbc.Database.EnsureCreated();

                dbc.Add(toolboxInstance);
                dbc.Add(hammerInstance);
                dbc.Add(wrenchInstance);
                Console.WriteLine(dbc.SaveChanges());
            }

            using (var dbc = new ToolsContext())
            {
                var toolboxesBackFromDb = dbc.Toolboxes.Include(x => x.Tools);
                Console.WriteLine(toolboxesBackFromDb.Count());
                var toolbox1 = toolboxesBackFromDb.AsEnumerable().FirstOrDefault();
                Console.WriteLine(toolbox1.Tools.Any());
            }
            Thread.Sleep(10000);
        }
    }

    public class ToolsContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"server=(localdb)\MsSqlLocalDB;database=HubSpokeExperiment");
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //  using reflection here to pick up new tool types as they are created
            //  rather than maintaining static list
            foreach (var t in Assembly.GetExecutingAssembly().DefinedTypes.Where(t => t.IsSubclassOf(typeof(ToolBase))))
            {
                var eb = modelBuilder.Entity(t.AsType());
                eb.HasKey("Id");
            }
        }
        public DbSet<Toolbox> Toolboxes { get; set; } 
        public DbSet<ToolBase> Tools { get; set; }
    }

    public class Toolbox
    {
        public Toolbox()
        {
            Tools = new List<ToolBase>();
        }
        public int Id { get; set; }
        public string Title { get; set; }
        public ICollection<ToolBase> Tools { get; set; }
    }

    public abstract class ToolBase
    {
        public string Description { get; set; }
        //public int ToolboxId { get; set; }
    }

    public class Hammer : ToolBase
    {
        public int Id { get; set; }
        public string SomethingAboutHammers { get; set; }
        public Toolbox Toolbox { get; set; }
    }

    public class Wrench : ToolBase
    {
        public int Id { get; set; }
        public string SomethingAboutWrenches { get; set; }
        public Toolbox Toolbox { get; set; }
    }
}
@natemcmaster
Copy link
Contributor

@MatthewMWR Does this error occur if you don't use reflection to loop over all derived types of ToolBase?

@MatthewMWR
Copy link
Author

The ToolBase entity gets added to the model whenever DbSet Tools { get; set; } is added to the dbcontext. If I leave OnModelCreating() blank the same thing happens.

@rowanmiller
Copy link
Contributor

Same with this one, if you can retry on the nightly

@smitpatel
Copy link
Contributor

Since the entity Toolbox has collection navigation Tools with target type of ToolBase, it will be added to model. It is essentially following TPH scenario hence throwing exception that base class has no PK defined (in TPH, base class defines PK and all derived classes share it).
As of now TPC support is not fully available. Issue #3170 is tracking it.
@rowanmiller can tell more if this would be possible at present or not.

@divega
Copy link
Contributor

divega commented Oct 23, 2015

Yes, to have things work exactly like this you will need TPC support which is not planned for EF 7 RTM.

Besides going with TPH another way you can compromise is to not include ToolBase in the model in which case EF will not even reason about the fact that both Wrench and Hammer belong to the same inheritance hierarchy but if you do that then:

  1. You cannot have a single DbSet<ToolBase> in the context
  2. You cannot have one navigation property of type ICollection<ToolBase> to hold wrenches and hammers.

@divega
Copy link
Contributor

divega commented Oct 23, 2015

@rowanmiller I think this is just a dupe of #3170.

@smitpatel
Copy link
Contributor

Combining all of the bugs together here #3526 & #3527
@MatthewMWR - The EF nightly has TPH fully implemented and TPC is not fully supported yet hence above model which contains inheritance will be using TPH strategy and won't be valid model since derived types cannot have keys in TPH.

@rowanmiller
Copy link
Contributor

Closing out as this is the correct behavior given that inheritance will always mean TPH at the moment (and TPH will always be the default if nothing is specified in configuration). #3170 is tracking TPC support.

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants