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.61k stars 3.14k forks source link

NullReferenceException when accessing navigation property with compiled models and ambiguous type names #31977

Closed devbased closed 10 months ago

devbased commented 10 months ago

Description

The issue arises when using Compiled Models. Types are not identified by their 'FullName'. Thus, if user code has a custom type with the same name as a generated type, a runtime NullReferenceException will occur when attempting to access a navigation property with lazy-loading enabled.

Steps To Reproduce

  1. Create a .NET 7 project and install packages:
    • Microsoft.EntityFrameworkCore.Design 7.0.11
    • Microsoft.EntityFrameworkCore.Proxies 7.0.11
    • Microsoft.EntityFrameworkCore.Sqlite 7.0.11
  2. Add EF Core model classes A and B with a navigation property between them
  3. Create a custom AEntityType enum in a separate namespace
  4. Add property public AEntityType EntityType { get; set; } to B
  5. Enable compiled models and lazy loading proxies in DbContext configuration
  6. Seed database and retrieve an A entity
  7. Attempt to access the B navigation property
  8. A NullReferenceException will occur when accessing B

Code Sample

using CompiledModelEntityNameAmbiguityBug.CompiledModels; 
using Microsoft.EntityFrameworkCore;
using AEntityType = Enums.AEntityType;

await using var dbContext = new TestContext();
await dbContext.Database.EnsureCreatedAsync();

var a = await dbContext.Set<A>().FirstAsync(x => x.AId == 1);

// attempt to access B - NullReferenceException  
Console.WriteLine(a.B!.EntityType);

public class TestContext : DbContext
{
  /// <inheritdoc />
  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 
  {
    base.OnConfiguring(optionsBuilder);

    optionsBuilder
      .UseSqlite("Data Source=test.db")
      .UseModel(TestContextModel.Instance)
      .UseLazyLoadingProxies();
  }

  /// <inheritdoc />
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<A>().HasData(new A { AId = 1, }); 
    modelBuilder.Entity<B>().HasData(new B { AId = 1, BId = 1, EntityId = 1, EntityType = AEntityType.Foo, });
  }
}

public class A
{
  public int AId { get; set; }

  public virtual B? B { get; set; } 
}

public class B  
{
  public int BId { get; set; }
  public int AId { get; set; }
  public int EntityId { get; set; }
  public AEntityType EntityType { get; set; } 
}

namespace Enums
{
  public enum AEntityType
  {
    Foo,
  }
}

Stack Trace

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateGetValueExpression(ParameterExpression dbDataReader, Int32 index, Boolean nullable, RelationalTypeMapping typeMapping, Type type, IPropertyBase property)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitSwitchCase(SwitchCase node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitSwitch(SwitchExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Load[TSource](IQueryable`1 source)
   at Microsoft.EntityFrameworkCore.Internal.EntityFinder`1.Load(INavigation navigation, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.ReferenceEntry.Load()
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.LazyLoader.Load(Object entity, String navigationName)
   at Microsoft.EntityFrameworkCore.Proxies.Internal.LazyLoadingInterceptor.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.AProxy.get_B()
   at Program.<Main>$(String[] args) in Program.cs:line 11
   at Program.<Main>$(String[] args) in Program.cs:line 11
   at Program.<Main>(String[] args)

General Info

EF Core version: 7.0.11 Database provider: Microsoft.EntityFrameworkCore.Sqlite

Target framework: .NET 7.0 Operating system: Windows 10 IDE: JetBrains Rider 2023.2

devbased commented 10 months ago

I thought it is a rare case, but in the project I work on, it turned out that many entities have the same name but are located in different namespaces. There are also some collisions with System (for example, we have a domain entity called TimeZone) 😔

ajcvickers commented 10 months ago

Duplicate of #27203