【问题标题】:Unable to determine the relationship represented by navigation * of type *. Either manually configure the relationship, or ignore this property using无法确定 * 类型的导航 * 表示的关系。手动配置关系,或使用忽略此属性
【发布时间】:2020-11-19 06:11:25
【问题描述】:

我有使用 Microsoft.EntityFrameworkCore.Sqlite 5.0.0.NET 5.0.0 控制台应用程序。我已经安装了 Microsoft.EntityFrameworkCore.Sqlite 5.0.0、Microsoft.AspNetCore.Identity.EntityFrameworkCore 5.0.0(对于 IdentityUser)和 Microsoft.EntityFrameworkCore.Design 5.0.0(对于 dotnet-ef 迁移 命令)nuget 包。

我的模型代表一组测试,其中用户选择其他用户,在其他一些测试中用户做同样的事情并留下一些评论(为了测试目的,模型被大大简化了)。所以我的 TestAnswer 类使用三级继承:

public class TestAnswerBase
{
   public Int64 Id { get; set; }
   [Required]
   public Int64 UserId { get; set; }
   public virtual User User { get; set; }
}


public partial class TestAnswerChild : TestAnswerBase
{
   [Required]
   public Int64 ChosenId { get; set; }
   public virtual User Chosen { get; set; }
}

//If I use this TestAnswerGrandChild variant - it shows an error while creating a migration:
//"Unable to determine the relationship represented by navigation 'TestAnswerChild.Chosen' 
//of type 'User'. Either manually configure the relationship, or ignore this property using 
//the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'."
public partial class TestAnswerGrandChild : TestAnswerChild
{
   public string Comment { get; set; }
}    

我的 User 类(它包含用户选择某人的测试集合和用户被某人选择的集合):

public partial class User : IdentityUser<Int64>
{
    public User()
    {
        TestAnswerChildChosen = new HashSet<TestAnswerChild>();
        TestAnswerChildUser = new HashSet<TestAnswerChild>();
        TestAnswerGrandChildChosen = new HashSet<TestAnswerGrandChild>();
        TestAnswerGrandChildUser = new HashSet<TestAnswerGrandChild>();
        
    }

    [Required]
    public string Name { get; set; }

    [InverseProperty(nameof(TestAnswerChild.Chosen))]
    public virtual ICollection<TestAnswerChild> TestAnswerChildChosen { get; set; }

    [InverseProperty(nameof(TestAnswerChild.User))]
    public virtual ICollection<TestAnswerChild> TestAnswerChildUser { get; set; }

    [InverseProperty(nameof(TestAnswerGrandChild.Chosen))]
    public virtual ICollection<TestAnswerGrandChild> TestAnswerGrandChildChosen { get; set; }

    [InverseProperty(nameof(TestAnswerGrandChild.User))]
    public virtual ICollection<TestAnswerGrandChild> TestAnswerGrandChildUser { get; set; }
}

我的 DbContext 相当简单,但我在这里包含了它的代码:

public partial class MyDbContext : IdentityDbContext<User, IdentityRole<Int64>, Int64>
{
    public MyDbContext()
    {
    }

    public MyDbContext(DbContextOptions<MyDbContext> options)
        : base(options)
    {
    }
   
    public virtual DbSet<TestAnswerChild> TestAnswersChild { get; set; }
    public virtual DbSet<TestAnswerGrandChild> TestAnswersGrandChild { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlite("data source=test.db");
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //is necessary because -> "The entity type 'IdentityUserLogin<long>' requires a primary key to be defined"
        base.OnModelCreating(modelBuilder);
    }
}

所以当我调用 dotnet-ef migrations add InitialCreate 命令示例时,我得到一个错误:

无法确定导航所代表的关系 “用户”类型的“TestAnswerChild.Chosen”。要么手动配置 关系,或使用“[NotMapped]”忽略此属性 属性或在“OnModelCreating”中使用“EntityTypeBuilder.Ignore”。

如果我像这样更改 TestAnswerGrandChild 代码(只有两级继承) - 它会创建一个没有错误的迁移:

//If I use that TestAnswerGrandChild variant - migration is created with no errors
    
public partial class TestAnswerGrandChild : TestAnswerBase
{
   [Required]
   public Int64 ChosenId { get; set; }
   public virtual User Chosen { get; set; }
   public string Comment { get; set; }
}

那么我的问题是什么以及如何解决它(我需要多级继承 - 测试项目大大简化了)?

我已经上传了我的测试项目here(运行update migrations.cmd 得到一个错误)

提前谢谢你。

【问题讨论】:

    标签: c# sqlite .net-core entity-framework-core .net-5


    【解决方案1】:

    那么我的问题是什么以及如何解决它

    这个问题是由几个因素和一些当前 EF Core 的缺点和实现细节共同造成的。

    主要问题是默认情况下,当某些基类用作 entity 时,EF Core 假定它应该使用可用的database inheritance 策略之一映射到数据库(当前为 TPH 和 TPT )。但是,据我所知,您不希望这样 - 您使用对象层次结构只是为了代码可重用性,并最终进行一些多态/泛型类型处理,但您希望它们完全映射到单独的表。

    部分问题在于没有数据注释。您必须流利地指定:

    modelBuilder.Entity<TestAnswerGrandChild>()
        .HasBaseType((Type)null);
    

    但是,约定和数据注释在OnModelCreating 之前应用。而且由于在这种情况下通过导航属性和注释定义的关系对于任何现有的数据库继承都无效,EF Core 会简单地忽略它们并生成异常,要求您手动执行此操作。

    不幸的是,这就是你应该做的——除了上述之外,之后,手动指定和配对导航属性,从而完全重新定义关系:

    modelBuilder.Entity<TestAnswerGrandChild>()
        .HasBaseType((Type)null);
    
    modelBuilder.Entity<TestAnswerChild>()
        .HasOne(e => e.Chosen)
        .WithMany(e => e.TestAnswerChildChosen);
    
    modelBuilder.Entity<TestAnswerChild>()
        .HasOne(e => e.User)
        .WithMany(e => e.TestAnswerChildUser);
    
    modelBuilder.Entity<TestAnswerGrandChild>()
        .HasOne(e => e.Chosen)
        .WithMany(e => e.TestAnswerGrandChildChosen);
    
    modelBuilder.Entity<TestAnswerGrandChild>()
        .HasOne(e => e.User)
        .WithMany(e => e.TestAnswerGrandChildUser);
    

    【讨论】:

    • 但是,据我所知,您不希望这样 - 您使用对象层次结构只是为了代码可重用性,并最终进行一些多态/泛型类型处理,但您希望它们完全映射到一个单独的表格。 - 你是绝对正确的。感谢您提供详细的解释和解决方案 - 它现在按我的预期工作..
    猜你喜欢
    • 2019-10-17
    • 2020-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-01
    • 2021-06-08
    • 1970-01-01
    相关资源
    最近更新 更多