Entity Framework Core get relation from database

asp.net-core c# entity-framework-core

Question

Perhaps I have set up my models incorrectly, or I'm expecting EF to do more work than it actually does, but...

I have a User model and a UserRole model. There is a 1 to 1 relation between User and UserRole. When I log in, the User's UserRole is null but the UserRoleId is not null. I have since set up the login function to manually get the UserRole that matches the UserRoleId, but shouldn't that relation be made implicitly?

AuthController.cs
public class AuthController : Controller
{
    private readonly IAuthRepository _repo;
    private readonly IConfiguration _config;
    private readonly IMapper _mapper;

    public AuthController(IAuthRepository repo, IConfiguration config, IMapper mapper)
    {
        _mapper = mapper;
        _config = config;
        _repo = repo;
    }

   [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] UserForLoginDto userForLoginDto)
    {
        var userFromRepo = await _repo.Login(userForLoginDto.Username.ToLower(), userForLoginDto.Password);

       //<!<!<! userFromRepo has a NULL UserRole property !>!>!>

        if (userFromRepo == null)
            return Unauthorized();

        // generate token...

        var user = _mapper.Map<UserForListDto>(userFromRepo);
        return Ok(new {tokenString, user}
    }
Models\User.cs
public class User
{
    public int Id { get; set; }

    public string Username { get; set; }

    public int? UserRoleId { get; set; }
    public UserRole UserRole { get; set; }

    //other user attributes
}
Models\UserRole.cs
public class UserRole
{
    public int Id { get; set; }

    public string Role { get; set; }
}
Data\AuthRepository.cs
public class AuthRepository : IAuthRepository
{
    private readonly DataContext _context;

    public AuthRepository(DataContext context)
    {
        _context = context;
    }

    public async Task<User> Login(string username, string password)
    {
        var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == username);

        if (user == null)
            return null;

        if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
            return null;

        //this is how I'm currently linking the User to it's UserRole
        user.UserRole = await _context.UserRoles.FirstOrDefaultAsync(x => x.Id == user.UserRoleId);

        // auth successful
        return user;
    }
Data\DataContext.cs
public class DataContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<UserRole> UserRoles { get; set; }

    //I do not have an explicit relation between Users and UserRoles in OnModelCreating
}
1
1
7/9/2018 5:40:42 PM

Accepted Answer

The reason is explained in the Loading Related Data section of the EF Core documentation.

The first behavior is because EF Core currently does not support lazy loading, so normally you'll get null for navigation properties until you specifically load them via eager or explicit loading. However, the Eager loading section contains the following:

Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

which explains why the navigation property is not null in the second case.

Now, I'm not sure which of the two behaviors do you want to fix, so will try to address both.

The first behavior can be "fixed" by using one of the currently available methods for loading related data, for instance eager loading:

var mutants = db.Mutants.Include(m => m.OriginalCode).ToList();

The second behavior is "by design" and cannot be controlled. If you want to avoid it, make sure to use fresh new DbContext instance just for executing a single query to retry the data needed.

Update: Starting with v2.1, EF Core supports Lazy Loading. However it's not enabled by default, so in order to utilize it one should mark all navigation properties virtual, install Microsoft.EntityFrameworkCore.Proxies and enable it via UseLazyLoadingProxies call, or utilize Lazy-loading without proxies - both explained with examples in the EF Core documentation.

46
7/9/2018 6:02:40 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow