Efcore: The derived type 'UserRole' cannot have KeyAttribute on property 'Id' since primary key can only be declared on the root type

Created on 22 Sep 2016  路  12Comments  路  Source: dotnet/efcore

Hi everyone!

Steps to reproduce

  • Create ASP.NET Core application from yo generator-aspnetcore-spa with 'Angular 2' template (for example, with name 'Web.Client.Spa').
  • Create .NET Core class library project (for example, with name 'Core.DAL.Real.Database') for EF DbContext (and create in this project EF DbContext).
  • Create .NET Core class library project for domain models (for example, 'Core.DomainModels' and 'Core.DomainModels.Identity' - first project for domain models classes and second project for the ASP.NET Identity models [for EF CodeFirst approach]).

In project 'Core.DomainModels' add this file:
_IEntity.cs_:

    public interface IEntity<T>
    {
        T Id { get; set; }
    }

    public interface IEntity : IEntity<Guid>
    {

    }

And in project 'Core.DomainModels.Identity' add this classes:

Abstract & interfaces:
_IUser.cs_

    public interface IUser<T> : IEntity<T>
    {
        DateTime RegisterDate { get; set; }
    }

    public interface IUser : IUser<Guid>
    {

    }

_IRole.cs_

    public interface IRole<T> : IEntity<T>
    {
        string Name { get; set; }
    }

    public interface IRole : IRole<Guid>
    {

    }

_IUserRole.cs_

    public interface IUserRole<T>// : IEntity<T>
    {
        T UserId { get; set; }
        T RoleId { get; set; }

        IUser User { get; set; }
        IRole Role { get; set; }
    }

    public interface IUserRole : IUserRole<Guid>
    {

    }

Real models:
_User.cs_

    public class User : IdentityUser<Guid>, IUser<Guid>
    {
        public static User CreateEmptyInstance()
        {
            return new User();
        }

        #region Implementation of IUser<Guid>

        public DateTime RegisterDate { get; set; }

        #endregion

        [NotMapped]
        public bool IsAuthenticated { get; set; }

    }

_Role.cs_

    public class Role : IdentityRole<Guid>, IRole<Guid>
    {

    }

_UserRole.cs_

    public class UserRole : IdentityUserRole<Guid>, IUserRole
    {

        #region Implementation of IEntity<Guid>

        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public Guid Id { get; set; }

        #endregion
        #region Implementation of IUserRole<Guid>

        [NotMapped]
        public IUser User { get; set; }

        [NotMapped]
        public IRole Role { get; set; }

        #endregion
    }
  • Add packages ASP.NET Identity, EF Core (with Tools and Design), Npgsql to MVC App (I'm used PostgreSQL database).
  • Add packages EF Core (with Tools) to Core.DAL.Real.Database project.
  • In Package Manager Console run command (for startup project Core.DAL.Real.Database):
Add-Migration InitDev -Context EfDbMigrations -Environment Development
  • ...And I have an error:
System.InvalidOperationException: The derived type 'UserRole' cannot have KeyAttribute on property 'Id' since primary key can only be declared on the root type.    at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.KeyAttributeConvention.Apply(InternalModelBuilder modelBuilder)
    at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelBuilt(InternalModelBuilder modelBuilder) 
    at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator) 
    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
    at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel() 
    at Microsoft.EntityFrameworkCore.Internal.LazyRef`1.get_Value() 
    at Microsoft.Extensions.DependencyInjection.ServiceProvider.ScopedCallSite.Invoke(ServiceProvider provider) 
    at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider) 
    at Microsoft.Extensions.DependencyInjection.ServiceProvider.TransientCallSite.Invoke(ServiceProvider provider) 
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ConstructorCallSite.Invoke(ServiceProvider provider) 
    at Microsoft.Extensions.DependencyInjection.ServiceProvider.TransientCallSite.Invoke(ServiceProvider provider) 
    at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) 
    at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider) 
    at Microsoft.EntityFrameworkCore.Design.MigrationsOperations.AddMigration(String name, String outputDir, String contextType) 
    at Microsoft.EntityFrameworkCore.Tools.Cli.MigrationsAddCommand.Execute(CommonOptions commonOptions, String name, String outputDir, String context, String environment, Action`1 reporter) 
    at Microsoft.EntityFrameworkCore.Tools.Cli.MigrationsAddCommand.<>c__DisplayClass0_0.<Configure>b__0() 
    at Microsoft.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args) 
    at Microsoft.EntityFrameworkCore.Tools.Cli.Program.Main(String[] args)
 The derived type 'UserRole' cannot have KeyAttribute on property 'Id' since primary key can only be declared on the root type. 

My project.json in Core.DAL.Real.Database project:

{
  "version": "1.0.0-*",

  "dependencies": {
    "NETStandard.Library": "1.6.0",
    "Microsoft.EntityFrameworkCore": "1.0.1",
    "Microsoft.EntityFrameworkCore.Tools": {
      "version": "1.0.0-preview2-final",
      "type": "build"
    },
    "Npgsql.EntityFrameworkCore.PostgreSQL": "1.0.1",
    "System.Linq": "4.1.0",
    "Core.DomainModels.Identity": "1.0.0-*",
    "Core.DomainModels": "1.0.0-*"
  },

  "frameworks": {
    "netcoreapp1.0": {      
      "imports": "dnxcore50"
    }
  },

  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  } 
}

The issue

The KeyAttribute on property _Id_ in my _UserRole_ class must not throw this error, right? Or I do something wrong?

I found the same issue #5898 but that issue is closed by @AndriySvyryd and appropriate changes (by commit https://github.com/aspnet/EntityFramework/commit/5ce5b86d37859a1410d9e343489f8b2d3fbb9527) are included in 1.0.1 version of EF Core which I used. I'm confused because of this error. And because of this, I cannot create my first (init) migration...

Please help solve this problem...

Further technical details

EF Core version: 1.0.1
Operating system: Windows 10
Visual Studio version: VS 2015 Update 3

closed-by-design

Most helpful comment

@Cubelaster Something like this:

```C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ConfigureBase(modelBuilder.Entity());
}

protected EntityTypeBuilder ConfigureBase(EntityTypeBuilder entityType)
where T : MyBase
{
ConfigureTranslations(entityType.Property(d => d.MyTranslations));
return entityType;
}

protected PropertyBuilder ConfigureTranslations(PropertyBuilder property)
=> property.HasConversion(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject(v))
.HasColumnType("json");

```C#
private class MyBase
{
    public string Id { get; set; }
    public Translations MyTranslations { get; set; }
}

private class MyDerived : MyBase
{
}

private class Translations
{
}

All 12 comments

@Valeriy1991 In this case the exception is by design.

UserRole derives from IdentityUserRole<Guid> that is already included in the model, so the key needs to be configured on it, as it's the base class. By default IdentityDbContext configures its key as {UserId, RoleId}.

Since UserRole represents a many-to-many relationship there's no single property that could be used as the implementation of IEntity.Id. Could you redesign the model so UserRole doesn't need to implement IEntity?

Closing as there was no response. Feel free to reopen providing more information if you are still experiencing this issue.

@AndriySvyryd hi! Now, unfortunately, I have no way to reproduce the problem (I'm busy on another project). If a problem appears, I will write about it here (I think in a month). Thanks!

I also have the same/similar problem. I tried to implement a Global Query filter. All my Db Models inherit from Abstract class and when I tried to implement the Global Query Filter, this error started happening. I don't know why exactly I used Abstract class instead of interface, but this should not even be happening, because I don't even have an Id on base class...

@Cubelaster - Query filter cannot produce that issue. Whatever changes you made must have brought your abstract type as entity type in model and then you run into all above issues. Most likely it would be a navigation pointing to abstract type on some entity type. If you cannot resolve, please file a new issue with full repro code for us to look into.

@abakumov-v I have the same problem and using this approach :
modelBuilder.Entity<UserRole>() .ToTable("UserRoles") .HasKey(ur => ur.Id);

I just want to say that this happened again :D
A bit differently now.
I am upgrading one of the projects from 2.2 to 3.1 and all my models are using the same base class.
Now EF says that all my models that have a [Key] on them can not have it, since they have a base class.
Now, I know this is not usually true, since I always use this approach.
So, why is it saying anything then?
I just want to say that perhaps the reason for it is the fact that EF 3.1 didn't know how to handle a relationship that existed before so I actually used the base class in "onModelCreating" for determining relations.
I am guessing this is what triggers the error, since EF suddenly becomes aware of the base class?
Has this any sense?

@Cubelaster Avoid using the base type in OnModelCreating if you don't want it included in the model.

Figured as much, however, there are a LOT of use cases where I see benefits of having a single NOT MAPPED model that contains the same logic for a family of tables.
For instance Audit metadata orJson fields.
Both of those require manual table per table configuration while setting it up on single Base class would be the desired way.
I'll try to formulate an example.
Example
Ok, so I want to use a single base class to define a Json field that EVERY table has.
Next is not supported for (I presume) the same reason as this one.
And then I have to implement it for EVERY table in solution.

Maybe I'm doing something wrong, but this seems logical to me.

``` foreach (var entityType in builder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetNavigations())
{
if (property.ClrType == typeof(Translations))
{
builder.Entity(entityType.Name)
.Property(property.Name)
.HasConversion(v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject(v))
.HasColumnType("json");
}
}
}

@Cubelaster This will be handled by https://github.com/dotnet/efcore/issues/6787. For now the best you can do is to extract the common configuration to a method.

@Cubelaster This will be handled by #6787. For now the best you can do is to extract the common configuration to a method.

Ok, sounds nice.
Would you be willing to provide an example or a link to one?
Thank you

@Cubelaster Something like this:

```C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ConfigureBase(modelBuilder.Entity());
}

protected EntityTypeBuilder ConfigureBase(EntityTypeBuilder entityType)
where T : MyBase
{
ConfigureTranslations(entityType.Property(d => d.MyTranslations));
return entityType;
}

protected PropertyBuilder ConfigureTranslations(PropertyBuilder property)
=> property.HasConversion(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject(v))
.HasColumnType("json");

```C#
private class MyBase
{
    public string Id { get; set; }
    public Translations MyTranslations { get; set; }
}

private class MyDerived : MyBase
{
}

private class Translations
{
}
Was this page helpful?
0 / 5 - 0 ratings