EF Core IEntityTypeConfigurations:从 DB 提取数据后,相关对象为 null

EF Core IEntityTypeConfigurations: Related object is null after drawing data from DB

提问人:Nico1395 提问时间:8/18/2023 更新时间:8/19/2023 访问量:56

问:

我正在使用 FluentMigrator 迁移进行数据访问设置。总的来说,我认为我对配置实体和表以建立关系有基本的了解,并且在某些时候它确实有效。IEntityTypeConfigurations

但是,现在我总是在从数据库中查询我想要的帐户时。我的角色的权限也会发生同样的情况。
在当前代码段之前,我已经拥有使用角色作为列表的帐户,并在这些帐户之间建立了关系,但这也不起作用。我有点困惑,老实说,在这一点上,我只是在没有任何理解的情况下尝试一些东西。
UserAccountsUserRolenull

据我所知,存储实体可以完美地工作,就像我想要的那样。在将上下文中的更改保存到数据库后,这些实体完全处于我希望它们处于的状态。这意味着所有权限都存在于我的角色中,并且该角色存在于我的帐户中,其中包含我想要的所有数据。

public class UserAccount
{
    public int Id { get; set; }
    public string Email { get; set; } = string.Empty;
    public string PhoneNumber { get; set; } = string.Empty;
    public string LoginName { get; set; } = string.Empty;
    public string PasswordHash { get; set; } = string.Empty;
    public DateTime PasswordChanged { get; set; } = DateTime.Now;
    public int RoleId? { get; set; }
    public virtual UserRole? Role { get; set; }
}

public class UserAccountEntityMap : IEntityTypeConfiguration<UserAccount>
{
    public void Configure(EntityTypeBuilder<UserAccount> builder)
    {
        builder.ToTable("UserAccounts");
        builder.HasKey(x => x.Id);
        
        builder.Property(u => u.Id).HasColumnName("Id").UseIdentityColumn();
        builder.Property(u => u.Email).HasColumnName("Email");
        builder.Property(u => u.PhoneNumber).HasColumnName("PhoneNumber");
        builder.Property(u => u.LoginName).HasColumnName("LoginName");
        builder.Property(u => u.PasswordHash).HasColumnName("PasswordHash");
        builder.Property(u => u.PasswordChanged).HasColumnName("PasswordChanged");
        
        builder.HasOne(u => u.Role)
            .WithMany()
            .HasForeignKey(u => u.RoleId);
    }
}

// Migration creating the table
public override void Up()
{
    if (!Schema.Table("UserAccounts").Exists())
    {
        Create.Table("UserAccounts")
            .WithColumn("Id")
                .AsInt32()
                .NotNullable()
                .PrimaryKey()
                .Identity()
            .WithColumn("Email")
                .AsString()
            .WithColumn("PhoneNumber")
                .AsString()
            .WithColumn("LoginName")
                .AsString()
            .WithColumn("PasswordHash")
                .AsString()
            .WithColumn("PasswordChanged")
                .AsDateTime2()
            .WithColumn("RoleId")
                .AsInt32()
                .ForeignKey();
        
        Create.ForeignKey("FK_UserAccount_UserRole")
            .FromTable("UserAccounts")
                .ForeignColumn("RoleId")
            .ToTable("UserRoles")
                .PrimaryColumn("Id");
    }
}
public class UserRole
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public DateTime LastChanged { get; set; } = DateTime.Now;
    public virtual List<UserRolePermission> Permissions { get; set; } = new();
}

public class UserRoleEntityMap : IEntityTypeConfiguration<UserRole>
{
    public void Configure(EntityTypeBuilder<UserRole> builder)
    {
        builder.ToTable("UserRoles");
        builder.HasKey(t => t.Id);
        
        builder.Property(r => r.Id).HasColumnName("Id").UseIdentityColumn();
        builder.Property(r => r.Name).HasColumnName("Name");
        builder.Property(r => r.Description).HasColumnName("Description");
        builder.Property(r => r.LastChanged).HasColumnName("LastChanged");
        
        builder.HasMany(r => r.Permissions)
            .WithOne()
            .HasForeignKey(p => p.RoleId);
    }
}

// Migration creating the table
public override void Up()
{
    if (!Schema.Table("UserRoles").Exists())
    {
        Create.Table("UserRoles")
            .WithColumn("Id")
                .AsInt32()
                .NotNullable()
                .PrimaryKey()
                .Identity()
            .WithColumn("Name")
                .AsString()
            .WithColumn("Description")
                .AsString()
            .WithColumn("LastChanged")
                .AsDateTime2();
    }
}

我尝试了很多东西,甚至参考了我们在工作中使用的配置,我正在做学徒 - 我的设置几乎是镜像它 - 但我就是无法让它工作。

我试图使我的相关属性虚拟,但没有成功,我在新数据库上多次更改了迁移和实体配置。网上很多资源只引用和呈现自动生成的模型配置和迁移,即使是那些没有的资源,似乎也没有给我带来任何进一步的帮助。

所以我有几个具体的问题

  • 外键几乎是描述与数据库对象相关的表和列的“引用”。多列可以组成一个外键。我的观点是对的,还是我误解了我迄今为止收集的信息?

  • 主键几乎是标识器,显然是唯一的,可以由多个列组成,这些列是唯一描述数据库条目所必需的。我说得对吗?

  • 可以在没有密钥的情况下在实体配置中设置实体,有哪些用例?

  • 显然:我在上面的尝试中有什么问题?我不明白什么,我需要调整什么?

.NET 数据库 实体 框架核心 fluent-migrator

评论

1赞 Ivan Stoev 8/18/2023
“导航属性始终为空”听起来像是数据加载问题,而不是配置。导航属性不会自动填充,您必须使用加载相关数据中描述的一些方法
0赞 Gert Arnold 8/19/2023
您应该显示获取 的代码,并指示您期望加载的位置。UserAccountRole
0赞 Nico1395 8/19/2023
谢谢你们俩,我不知道延迟加载,我肯定会阅读更多内容。

答:

1赞 Danny 8/19/2023 #1

你在做延迟加载吗?试试这个

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy

评论

1赞 Gert Arnold 8/19/2023
延迟加载不是轻易实现的。IMO 这几乎是一种反模式。
0赞 Nico1395 8/19/2023
是的,这奏效了!谢谢你把我指点在那里。我完全没有意识到这一点。在工作中,一些这样的操作是手动完成的,但我认为必须能够自动完成。但是,我仍然对为什么延迟加载应该是一种反模式非常感兴趣。我现在肯定会读到它,但另一种意见不会有什么坏处!