.netdatabaseentity-frameworkentity-framework-corefluent-migrator

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


I am using IEntityTypeConfigurations and FluentMigrator migrations for my data access setup. Generally I thought I had a basic understanding of configuring entities and tables for relations to work out and at some point it did work.

However now my UserAccounts UserRole is always null when querying my desired account from the database. Same happens with the permissions for my roles.
Before the current snippet I have had the accounts that utilize a role as a list and I established a relationship between those, but that did not work either. I am a little bit confused and at this point I am just trying stuff out without any understanding, to be honest.

Storing the entities works perfectly, just as I want, as far as I can tell. The entities are in exactly the state I want them to be in, after changes in the context are saved to the database. Meaning all permissions are present in my role and the role is present in my account, with all the data I want.

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();
    }
}

I have tried a bunch of stuff and even referenced the configuration we use at work, where I am doing my apprenticeship - my setup is almost mirroring it - but I just can't get it to work.

I tried to make my related-to properties virtual without success, I changed the migrations and entity configurations a bunch of times on new databases. A lot of resources online only reference and present automatically generated model configurations and migrations and even those that don't did not seem to bring me any further than this.

So I have a few concrete questions


Solution

  • Are you doing lazy loading? Try this

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

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