aspnetcore: Error: "The entity type 'DataProtectionKey' requires a primary key" when using .Net 7 previews

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I have implemented DataProtectionKey storage using an EFCore DB context, and it has been working fine for nearly a year.

In June, I upgraded my application to .Net 7 preview 5 and EFCore 7 preview 5, and now see the following exception:

Migrations failed with exception: System.InvalidOperationException: The entity type 'DataProtectionKey' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.

The webserver then terminates within IHost.Run with the following (similar) exception:

[22:03:19.707-0001-INF] Damselfly Webserver terminated with exception: The entity type 'DataProtectionKey' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.

I am using the SQLite DB provider.

This only happens on Linux (deployed in an Ubuntu docker container). If I run the same code, against the same .db file, on OSX, the migrations complete without issue. I have not tried Windows.

The error suggests using the [Keyless] attribute to resolve this error, but I don’t know enough about DataProtectionKeys to know if it would cause any issues.

Note that the problem persists with .Net 7 preview 6 and EFCore preview 6.

Expected Behavior

The expected behaviour is that the db.Database.Migrate(); call completes without issue and the application starts normally.

Steps To Reproduce

I declare my DataProtectionKeys in my DbContext here: https://github.com/Webreaper/Damselfly/blob/develop/Damselfly.Core/Models/ImageContext.cs#L43

public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }

I add configure the DataProtection here: https://github.com/Webreaper/Damselfly/blob/master/Damselfly.Web/Startup.cs#L73

services.AddDataProtection().PersistKeysToDbContext<ImageContext>();

I can’t find anywhere in the documentation that suggests I need to do something explicit to create a key.

The DataProtectionKeys were added in 2021, in this Migration: https://github.com/Webreaper/Damselfly/blob/master/Damselfly.Migrations.Sqlite/Migrations/20210922112630_AddDataProtectionKeys.cs:

   modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
                {
                    b.Property<int>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("INTEGER");

                    b.Property<string>("FriendlyName")
                        .HasColumnType("TEXT");

                    b.Property<string>("Xml")
                        .HasColumnType("TEXT");

                    b.HasKey("Id");

                    b.ToTable("DataProtectionKeys");
                });

Exceptions (if any)

Migrations failed with exception: System.InvalidOperationException: The entity type 'DataProtectionKey' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal.SqliteModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite, RuntimeResolverContext, ServiceProviderEngineScope, RuntimeResolverLock)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite, RuntimeResolverContext, ServiceProviderEngineScope, RuntimeResolverLock)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite, RuntimeResolverContext, ServiceProviderEngineScope, RuntimeResolverLock)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite, RuntimeResolverContext, ServiceProviderEngineScope, RuntimeResolverLock)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite, RuntimeResolverContext, ServiceProviderEngineScope, RuntimeResolverLock)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite, RuntimeResolverContext, ServiceProviderEngineScope, RuntimeResolverLock)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite, RuntimeResolverContext)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite, TArgument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type, ServiceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider, Type)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider)

.NET Version

.Net 7 preview 5 and preview 6

Anything else?

The issue only happens when running the containerised deployment on Linux. Running exactly the same code on OSX works fine.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 38 (18 by maintainers)

Most upvoted comments

@Webreaper What happens if you remove this line:

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

And replace it with:

Console.WriteLine(new DataProtectionKey().Id);

Or anything else that references the Id property without configuring it in EF model? Does the code still work like it does when using dpk.HasKey(x => x.Id);? If so, then that implies that just referencing the Id property is enough to have EF find it. If it doesn’t then, we’re going to have to start reducing the complexity in the repro until we can figure out the root cause.

The problem here is nothing in the DataProtection EntityFramework library references DataProtectionKey.Id.

There are ~two~ three solutions:

  1. DataProtection EntityFramework dynamically includes DataProtectionKey.Id to stop it from being trimmed.
  2. EntityFramework adds annotations to prevent properties from being trimmed from entities. e.g. DbSet.Add (and other EF methods that interact with entities) adds [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)].
  3. DataProtection EntityFramework explicitly specifies the key using HasKey(u => u.Id) in OnModelCreating. I’m guessing EF has a convention that if no key is specified, then an appropriate Id property on the entity is used.

Number 2 is more complicated, but it’s the better fix. Because EntityFramework isn’t annotated, all trimmed libraries that use EF are going to have to ensure entities aren’t trimmed manually (i.e. fix number 1). Ideally, EF should be annotated to do it for you.

Generally, EF entities are defined in user apps and aren’t trimmed. But people who enable full trimming of all assemblies will also see problems like this.

Correctly annotating EF will take a lot of time. Here is a short-term fix (number 1) for DataProtection EntityFramework - https://github.com/dotnet/aspnetcore/pull/43204. There is only one entity, so it’s simple to fix.

@Webreaper You should be able to remove your workaround after upgrading to .NET 7 RC1.

This is all something I’m planning to explore, though I don’t think keys should be handled in a special way here: basically public properties on entity types shouldn’t get trimmed, we should be able to do that by annotating modelBuilder.Entity, DbSet<>, etc.

We do plan to make the experience better when trimming. For example, we could annotate EF APIs to preserve all public properties on entity CLR types. We’re not sure we’ll be able to do significant trimming work for 7.0, but at least some guidance/documentation does make sense.

this can be fixed by explicitly mapping that the Id property as a key

That was directed at you @Webreaper.

@Webreaper Are you sure Microsoft.AspNetCore.DataProtection.EntityFrameworkCore is not being trimmed?

@Webreaper Sounds like this could be related to trimming. I suspect when the Id property is not referenced directly, then on platforms that turn on trimming by default it is getting trimmed out.