Sto usando EF Core per lavorare contro un database preesistente. È un modello piuttosto comune che utilizza molte tabelle di join, specifiche per il mio caso, una tabella di join per gli users
ai roles
. La tabella UserRole
ha solo due colonne: un UserId
e un RoleId
.
Il codice seguente mostra il mio ultimo tentativo di raccogliere tutti i dati insieme in un singolo oggetto DTO, con un'eccezione: non riesco a caricare i Roles
associati ai RoleId
visualizzati nella raccolta UserRoles
.
Qualche suggerimento su come posso caricare la raccolta ruoli associata? Devo scorrere la raccolta Ruolo per aggiungere il nome descrittivo del ruolo alle affermazioni dell'utente e, al momento, sto per finire con un grosso nullo.
var validatedUser = _context.User
.Where(x => string.Equals(x.UserName, username, StringComparison.CurrentCultureIgnoreCase) && x.Active)
.Include(x => x.UserRole)
.ThenInclude(x => x.Role)
.Select(result => new ValidatedUser
{
Id = result.Id,
Email = result.Email,
FirstName = result.FirstName,
LastName = result.LastName,
PhoneNumber = result.PhoneNumber,
Username = result.UserName,
UserRoles = result.UserRole,
//Roles = result.Role, // this is where failure happens
PasswordHash = result.PasswordHash
})
.FirstOrDefault();
Modifica 1 - mostra il modelBuilder per UserRole
modelBuilder.Entity<UserRole>(entity =>
{
entity.HasKey(e => new { e.UserId, e.RoleId });
entity.Property(e => e.UserId).HasColumnName("UserID");
entity.Property(e => e.RoleId).HasColumnName("RoleID");
entity.HasOne(d => d.Role)
.WithMany(p => p.UserRole)
.HasForeignKey(d => d.RoleId)
.HasConstraintName("FK_UserRole_RoleID");
entity.HasOne(d => d.User)
.WithMany(p => p.UserRole)
.HasForeignKey(d => d.UserId)
.HasConstraintName("FK_UserRole_UserID");
});
Ecco la classe ValidatedUser:
public class ValidatedUser
{
public Guid Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PasswordHash { get; set; }
public string FullName => $"{FirstName} {LastName}";
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string FormattedPhoneNumber
{
get
{
if (string.IsNullOrEmpty(PhoneNumber)) return PhoneNumber;
switch (PhoneNumber.Length)
{
case 10:
return $"({PhoneNumber.Substring(0, 3)}) {PhoneNumber.Substring(3, 3)}-{PhoneNumber.Substring(6, 4)}";
default:
return PhoneNumber;
}
}
}
public ICollection<UserRole> UserRoles { get; set; }
public ICollection<Role> Roles { get; set; }
}
Innanzitutto le istruzioni Include & ThenInclude non sono necessarie poiché si sta eseguendo un'istruzione select che verrà valutata dal lato server.
Per la query, che dire di questo:
var validatedUser =
_context.User
.Where(x => string.Equals(x.UserName, username, StringComparison.CurrentCultureIgnoreCase) && x.Active)
.Select(result => new ValidatedUser
{
Id = result.Id,
Email = result.Email,
FirstName = result.FirstName,
LastName = result.LastName,
PhoneNumber = result.PhoneNumber,
Username = result.UserName,
Roles = result.UserRoles.Select(ur => ur.Role),
PasswordHash = result.PasswordHash
}).FirstOrDefault();
Se questo non funziona, puoi sempre mappare i ruoli lato client (in questo caso hai bisogno di Include / thenInclude ():
var validatedUser =
_context.User
.Include(u => u.UserRoles).ThenInclude(ur => ur.Role)
.Where(x => string.Equals(x.UserName, username, StringComparison.CurrentCultureIgnoreCase) && x.Active)
.ToList() // Force execution of query here...
.Select(result => new ValidatedUser // From here on it's client side
{
Id = result.Id,
Email = result.Email,
FirstName = result.FirstName,
LastName = result.LastName,
PhoneNumber = result.PhoneNumber,
Username = result.UserName,
UserRoles = result.UserRoles.ToList(),
Roles = result.UserRoles.Select(ur => ur.Role).ToList(),
PasswordHash = result.PasswordHash
}).FirstOrDefault();