null значение при обращении к свойствам второй таблицы через JOIN
При попытке сделать JOIN запрос, свойства Tokens оказываются null:
var user = await dbContext.Tokens
.Where(t => t.refresh_token == hashRefreshToken)
.Join(dbContext.Users, token => token.user_id, user => user.id, (token, user) => user)
.FirstOrDefaultAsync();
Хотя если сделать 2 отдельных запроса, значения null в token не будет:
var token = await dbContext.Tokens.FirstOrDefaultAsync(t => t.refresh_token == hashRefreshToken);
var user = await dbContext.Users.FindAsync(token.user_id);
Вот модель токена:
[Table("tokens")]
public class TokenModel
{
[Key]
public int token_id { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? refresh_token { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public DateTime? expiry_date { get; set; }
[ForeignKey("user_id")]
public int user_id { get; set; }
[JsonIgnore]
public UserModel User { get; set; }
}
Модель юзера:
[Table("users")]
public class UserModel
{
[Key]
[Required]
public int id { get; set; }
[Required]
public string username { get; set; }
[Required]
public string role { get; set; }
[EmailAddress]
[Required]
public string email { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string password_hash { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? api_key { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public UserQuotasModel Quotas { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public UserKeyModel Keys { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ICollection<UserFileModel> Files { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public TokenModel Tokens { get; set; }
//[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
//public ApiModel API { get; set; }
}
Так настроены отношеия в modelBuilder:
modelBuilder.Entity<UserModel>()
.HasOne(u => u.Tokens)
.WithOne(t => t.User)
.HasForeignKey<TokenModel>(t => t.user_id)
.OnDelete(DeleteBehavior.Cascade);
Ну DbSet:
public DbSet<TokenModel> Tokens { get; set; }
Вот вариант middleware в котором используется Join и свойства токена == null
public async Task Invoke(HttpContext context, FileCryptDbContext dbContext, ITokenService tokenService)
{
if(context.Request.Cookies.TryGetValue("JwtToken", out string? JwtToken))
{
context.Request.Headers.Add("Authorization", $"Bearer {JwtToken}");
}
else
{
if(context.Request.Cookies.TryGetValue("RefreshToken", out string? RefreshToken))
{
var hashRefreshToken = tokenService.HashingToken(RefreshToken);
//var token = await dbContext.Tokens.FirstOrDefaultAsync(t => t.refresh_token == hashRefreshToken);
var user = await dbContext.Tokens
.Where(t => t.refresh_token == hashRefreshToken)
.Join(dbContext.Users, token => token.user_id, user => user.id, (token, user) => user)
.FirstOrDefaultAsync();
if (user is not null && user.Tokens.expiry_date.HasValue)
{
if (user.Tokens.expiry_date > DateTime.UtcNow)
{
//var user = await dbContext.Users.FindAsync(token.user_id);
var userModel = new UserModel { id = user.id, username = user.username, email = user.email, role = user.role };
string NewJwtToken = tokenService.GenerateJwtToken(userModel, 20);
var JwtCookieOptions = tokenService.SetCookieOptions(TimeSpan.FromMinutes(20));
context.Response.Cookies.Append("JwtToken", NewJwtToken, JwtCookieOptions);
context.Request.Headers.Add("Authorization", $"Bearer {NewJwtToken}");
}
}
}
}
await _next(context);
}
Ну и вот вариант при котором используются 2 отдельных запроса, и все работает:
public async Task Invoke(HttpContext context, FileCryptDbContext dbContext, ITokenService tokenService)
{
if(context.Request.Cookies.TryGetValue("JwtToken", out string? JwtToken))
{
context.Request.Headers.Add("Authorization", $"Bearer {JwtToken}");
}
else
{
if(context.Request.Cookies.TryGetValue("RefreshToken", out string? RefreshToken))
{
var hashRefreshToken = tokenService.HashingToken(RefreshToken);
var token = await dbContext.Tokens.FirstOrDefaultAsync(t => t.refresh_token == hashRefreshToken);
if (token is not null && token.expiry_date.HasValue)
{
if (token.expiry_date > DateTime.UtcNow)
{
var user = await dbContext.Users.FindAsync(token.user_id);
var userModel = new UserModel { id = user.id, username = user.username, email = user.email, role = user.role };
string NewJwtToken = tokenService.GenerateJwtToken(userModel, 20);
var JwtCookieOptions = tokenService.SetCookieOptions(TimeSpan.FromMinutes(20));
context.Response.Cookies.Append("JwtToken", NewJwtToken, JwtCookieOptions);
context.Request.Headers.Add("Authorization", $"Bearer {NewJwtToken}");
}
}
}
}
await _next(context);
}
Заметил что если использовать Include, то все работает:
var user = await dbContext.Tokens
.Where(t => t.refresh_token == hashRefreshToken)
.Include(t => t.User)
.FirstOrDefaultAsync();
Ответы (2 шт):
Ну так правильно. Include подгружает связные данные док. Если вы хотите получить пользователя
var user = await dbContext.Tokens
.Where(t => t.refresh_token == hashRefreshToken)
.Include(t => t.User)
.Select(t => t.User)
.FirstOrDefaultAsync();
или
var user = await dbContext.Users
.FirstOrDefaultAsync(t => t.Tokens.refresh_token == hashRefreshToken);
У вас в запросе с Join явно указано вернуть лишь юзера:
(token, user) => user
Исправьте resultSelector на следующее выражение:
(token, user) => new { token, user }
и вернутся оба типа.
Переменная будет анонимного типа. Я бы назвал её tokenAndUser. Она будет иметь два свойства. Думаю, разберётесь.
Включите логирование сгенерированных SQL-запросов, чтобы можно было легче разбираться.
P.S. Нейминг у вас ужасный! Исправляйте.