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

InMemory: TypeMapping.Converter is unexpectedly null for required enum when adding entity to DbSet #35506

Open
joakimriedel opened this issue Jan 21, 2025 · 2 comments

Comments

@joakimriedel
Copy link
Contributor

joakimriedel commented Jan 21, 2025

Bug description

I have just upgraded a large project to .NET 9. All seems to work well, except for our test suite that works on the InMemory provider. For all entities that have a non-nullable enum, an exception is thrown when an entity is added DbSet.Add(). It does not matter which entity or which DbSet, as long as it is an enum, and that it is required (non-nullable). This is where the exception is thrown:

Image

Image

As can be seen, the TypeMapper.Converter for my enum is null, but the forced null forgiving ! makes me believe it was very unexpected for it to be null at this point.

Image

I have created a minimal repro to try to get the same error in a freshly created .NET 9 project, but it does not (of course) crash there. This leads me to believe something I've done wrong upgrading the .NET 8 project to .NET 9. Please help me point out how I can debug this. I have invested in thousands of InMemory tests and would be sad to have to let them go.

Workaround: If I change my enum to be nullable, the test will run successfully. So it is only for non-nullable enum properties it fails.

Your code

This repro works fine, but demonstrates what is not working in my real project.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

ctx.BillingGroups.Add(new BillingGroup() { Plan = BillingPlan.Personal });

public class BlogContext : DbContext
{
    public DbSet<BillingGroup> BillingGroups { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public enum BillingPlan : int
{
    Personal = 1,
    Business = 2
}

public class BillingGroup
{
    public int Id { get; set; }
    public required BillingPlan Plan { get; set; }
}

Main differences from real project;

  1. BillingGroup is of course a much more complex entity
  2. I am using migrations
  3. It is run from a test method from a separate test project

Stack traces

>	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.RuntimeProperty.Sentinel.get() Line 341	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.ExpressionExtensions.MakeHasSentinel(System.Linq.Expressions.Expression currentValueExpression, Microsoft.EntityFrameworkCore.Metadata.IReadOnlyPropertyBase propertyBase) Line 27	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.CreateCurrentValueGetter<TrastxWebApp.Blueprints.Models.BillingPlan>(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase propertyBase, bool useStoreGeneratedValues) Line 147	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.CreateExpressions<TrastxWebApp.Blueprints.Models.BillingPlan>(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase propertyBase, out System.Linq.Expressions.Expression<System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry, TrastxWebApp.Blueprints.Models.BillingPlan>> currentValueGetter, out System.Linq.Expressions.Expression<System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry, TrastxWebApp.Blueprints.Models.BillingPlan>> preStoreGeneratedCurrentValueGetter, out System.Linq.Expressions.Expression<System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry, TrastxWebApp.Blueprints.Models.BillingPlan>> originalValueGetter, out System.Linq.Expressions.Expression<System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry, TrastxWebApp.Blueprints.Models.BillingPlan>> relationshipSnapshotGetter, out System.Linq.Expressions.Expression<System.Func<Microsoft.EntityFrameworkCore.Storage.ValueBuffer, object>> valueBufferGetter) Line 109	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.CreateGeneric<TrastxWebApp.Blueprints.Models.BillingPlan>(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase propertyBase) Line 48	C#
 	[Lightweight Function]	
 	System.Private.CoreLib.dll!System.Reflection.MethodBaseInvoker.InvokeWithOneArg(object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, object[] parameters, System.Globalization.CultureInfo culture) Line 95	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessorsFactory.Create(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase propertyBase) Line 38	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase.Microsoft.EntityFrameworkCore.Metadata.Internal.IRuntimePropertyBase.get_Accessors.AnonymousMethod__36_0(Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase property) Line 198	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized<Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase, Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessors>(ref Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessors target, Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase param, System.Func<Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase, Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyAccessors> valueFactory) Line 25	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.RuntimePropertyBase.Microsoft.EntityFrameworkCore.Metadata.Internal.IRuntimePropertyBase.Accessors.get() Line 196	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Metadata.Internal.PropertyBaseExtensions.GetPropertyAccessors(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase propertyBase) Line 66	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.GetCurrentValue<TrastxWebApp.Blueprints.Models.BillingPlan>(Microsoft.EntityFrameworkCore.Metadata.IPropertyBase propertyBase) Line 976	C#
 	[Lightweight Function]	
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.OriginalValues.OriginalValues(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 12	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.EnsureOriginalValues() Line 1206	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntrySubscriber.SnapshotAndSubscribe(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 31	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry) Line 583	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(Microsoft.EntityFrameworkCore.EntityState oldState, Microsoft.EntityFrameworkCore.EntityState newState, bool acceptChanges, bool modifyProperties) Line 400	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(Microsoft.EntityFrameworkCore.EntityState entityState, bool acceptChanges, bool modifyProperties, Microsoft.EntityFrameworkCore.EntityState? forceStateWhenUnknownKey, Microsoft.EntityFrameworkCore.EntityState? fallbackState) Line 205	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<(Microsoft.EntityFrameworkCore.EntityState TargetState, Microsoft.EntityFrameworkCore.EntityState StoreGenTargetState, bool Force)> node) Line 123	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<(Microsoft.EntityFrameworkCore.EntityState, Microsoft.EntityFrameworkCore.EntityState, bool)>(Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<(Microsoft.EntityFrameworkCore.EntityState, Microsoft.EntityFrameworkCore.EntityState, bool)> node, System.Func<Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntryGraphNode<(Microsoft.EntityFrameworkCore.EntityState, Microsoft.EntityFrameworkCore.EntityState, bool)>, bool> handleNode) Line 26	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry rootEntry, Microsoft.EntityFrameworkCore.EntityState targetState, Microsoft.EntityFrameworkCore.EntityState storeGeneratedWithKeySetTargetState, bool forceStateWhenUnknownKey) Line 43	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.InternalDbSet<System.__Canon>.SetEntityState(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry entry, Microsoft.EntityFrameworkCore.EntityState entityState) Line 581	C#
 	Microsoft.EntityFrameworkCore.dll!Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TrastxWebApp.Blueprints.Models.BillingGroup>.Add(TrastxWebApp.Blueprints.Models.BillingGroup entity) Line 193	C#
 	TrastxWebApp.Tests.dll!TrastxWebApp.Tests.Extensions.VerificationExtensionsShould.FilterQueryableCorrectly() Line 220	C#
 	[Lightweight Function]	
 	System.Private.CoreLib.dll!System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(object obj, System.Reflection.BindingFlags invokeAttr) Line 57	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestInvoker<System.__Canon>.CallTestMethod(object testClassInstance) Line 149	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestInvoker<Xunit.Sdk.IXunitTestCase>.InvokeTestMethodAsync.AnonymousMethod__1() Line 256	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.ExecutionTimer.AggregateAsync(System.Func<System.Threading.Tasks.Task> asyncAction) Line 48	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestInvoker<System.__Canon>.InvokeTestMethodAsync.AnonymousMethod__0() Line 215	C#
 	xunit.core.dll!Xunit.Sdk.ExceptionAggregator.RunAsync(System.Func<System.Threading.Tasks.Task> code) Line 90	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestInvoker<Xunit.Sdk.IXunitTestCase>.InvokeTestMethodAsync(object testClassInstance) Line 214	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestInvoker.InvokeTestMethodAsync(object testClassInstance) Line 112	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestInvoker<Xunit.Sdk.IXunitTestCase>.RunAsync.AnonymousMethod__46_0() Line 180	C#
 	xunit.core.dll!Xunit.Sdk.ExceptionAggregator.RunAsync<decimal>(System.Func<System.Threading.Tasks.Task<decimal>> code) Line 107	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestInvoker<System.__Canon>.RunAsync() Line 163	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestRunner.InvokeTestMethodAsync(Xunit.Sdk.ExceptionAggregator aggregator) Line 88	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestRunner.InvokeTestAsync(Xunit.Sdk.ExceptionAggregator aggregator) Line 70	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestRunner<System.__Canon>.RunAsync.AnonymousMethod__0() Line 149	C#
 	xunit.core.dll!Xunit.Sdk.ExceptionAggregator.RunAsync<System.Tuple<decimal, string>>(System.Func<System.Threading.Tasks.Task<System.Tuple<decimal, string>>> code) Line 107	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestRunner<Xunit.Sdk.IXunitTestCase>.RunAsync() Line 149	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestCaseRunner.RunTestAsync() Line 140	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestCaseRunner<Xunit.Sdk.IXunitTestCase>.RunAsync() Line 82	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestCase.RunAsync(Xunit.Abstractions.IMessageSink diagnosticMessageSink, Xunit.Sdk.IMessageBus messageBus, object[] constructorArguments, Xunit.Sdk.ExceptionAggregator aggregator, System.Threading.CancellationTokenSource cancellationTokenSource) Line 170	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestMethodRunner.RunTestCaseAsync(Xunit.Sdk.IXunitTestCase testCase) Line 45	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestMethodRunner<Xunit.Sdk.IXunitTestCase>.RunTestCasesAsync() Line 136	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestMethodRunner<Xunit.Sdk.IXunitTestCase>.RunAsync() Line 106	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestClassRunner.RunTestMethodAsync(Xunit.Abstractions.ITestMethod testMethod, Xunit.Abstractions.IReflectionMethodInfo method, System.Collections.Generic.IEnumerable<Xunit.Sdk.IXunitTestCase> testCases, object[] constructorArguments) Line 206	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestClassRunner<Xunit.Sdk.IXunitTestCase>.RunTestMethodsAsync() Line 233	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestClassRunner<Xunit.Sdk.IXunitTestCase>.RunAsync() Line 175	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestCollectionRunner.RunTestClassAsync(Xunit.Abstractions.ITestClass testClass, Xunit.Abstractions.IReflectionTypeInfo class, System.Collections.Generic.IEnumerable<Xunit.Sdk.IXunitTestCase> testCases) Line 185	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestCollectionRunner<Xunit.Sdk.IXunitTestCase>.RunTestClassesAsync() Line 136	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.TestCollectionRunner<Xunit.Sdk.IXunitTestCase>.RunAsync() Line 101	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestAssemblyRunner.RunTestCollectionAsync(Xunit.Sdk.IMessageBus messageBus, Xunit.Abstractions.ITestCollection testCollection, System.Collections.Generic.IEnumerable<Xunit.Sdk.IXunitTestCase> testCases, System.Threading.CancellationTokenSource cancellationTokenSource) Line 346	C#
 	xunit.execution.dotnet.dll!Xunit.Sdk.XunitTestAssemblyRunner.RunTestCollectionsAsync.AnonymousMethod__2() Line 250	C#
 	System.Private.CoreLib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.Task<Xunit.Sdk.RunSummary>>.InnerInvoke() Line 490	C#
 	System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread threadPoolThread, System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Line 264	C#
 	System.Private.CoreLib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot, System.Threading.Thread threadPoolThread) Line 2346	C#
 	System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Line 1120	C#
 	System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() Line 128	C#

Verbose output


EF Core version

9.0.1

Database provider

InMemory

Target framework

.NET 9

Operating system

No response

IDE

No response

@AndriySvyryd
Copy link
Member

Do you use a compiled model by any chance? You'd need to generate a different compiled model for InMemory. In EF 8 it might have worked by accident.

@joakimriedel
Copy link
Contributor Author

@AndriySvyryd yes this is it, thank you.

I do have a compiled model, but I also tried commenting out .UseModel() when constructing the InMemory. I remember this was necessary in .NET 8, but it seems .NET 9 picks up the model anyway without the .UseModel() call?

When I deleted the Model folder entirely, the problem went away. I will do some verification to see if I can live without the compiled model. .NET 9 seems faster overall initially during my testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants