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.5k stars 3.13k forks source link

Improve navigations on complex type exception message #33525

Open roji opened 2 months ago

roji commented 2 months ago

This fails model validation:

Unhandled exception. System.InvalidOperationException: Complex type 'Blog.ComplexContainer#ComplexContainer (ComplexContainer)' has no properties defines. Configure at least one property or don't include this type in the model.
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.<ValidatePropertyMapping>g__Validate|7_0(IConventionTypeBase typeBase, <>c__DisplayClass7_0&) in /Users/roji/projects/efcore/src/EFCore/Infrastructure/ModelValidator.cs:line 187
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidatePropertyMapping(IModel model, IDiagnosticsLogger`1 logger) in /Users/roji/projects/efcore/src/EFCore/Infrastructure/ModelValidator.cs:line 140
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger) in /Users/roji/projects/efcore/src/EFCore/Infrastructure/ModelValidator.cs:line 48
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger) in /Users/roji/projects/efcore/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:line 52

We should also fix the typo has no properties defines -> has no properties defined. Once the model validation is fixed, we should also test that saving and querying works.

Repro ```c# await using var context = new BlogContext(); await context.Database.EnsureDeletedAsync(); await context.Database.EnsureCreatedAsync(); var container = new ComplexContainer { Containee1 = new(), Containee2 = new() }; _ = await context.Blogs.Where(b => b.ComplexContainer == container).ToListAsync(); public class BlogContext : DbContext { public DbSet Blogs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false") .LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().ComplexProperty(b => b.ComplexContainer); } } public class Blog { public int Id { get; set; } public ComplexContainer ComplexContainer { get; set; } } public class ComplexContainer { public ComplexContainee1 Containee1 { get; set; } public ComplexContainee2 Containee2 { get; set; } } public class ComplexContainee1 { public int Id { get; set; } } public class ComplexContainee2 { public int Id { get; set; } } ```
roji commented 2 months ago

Oops, false alarm - forgot to configure the nested types as nested (see repro above)... Leaving open to possibly consider emitting the error about navigations on complex types (which gets thrown if the container does have a simple property):

 Unable to configure navigation 'Blog.ComplexContainer#ComplexContainer (ComplexContainer).Containee1' of type 'ComplexContainee1' as complex types don't support navigations. Ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'

Ultimately, IMHO nested complex types shouldn't need to be manually configured, but should instead be detected as complex types by convention, at which point this will error more usefully anyway.

(am OK with closing if you prefer @AndriySvyryd)