Sto tentando di accedere alla proprietà Roles di navigazione del modello IdentityUser.
All'interno della funzione GetQueryable sto impostando la proprietà include
protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
Expression<Func<TEntity, bool>> filter = null,
string includeProperties = null
)
where TEntity : class, IEntity
{
IQueryable<TEntity> query = context.Set<TEntity>();
if (filter != null)
{
query = query.Where(filter);
}
if (includeProperties != null)
{
query = query.Include(includeProperties);
}
return query;
}
Se eseguo la seguente query, la proprietà dei ruoli viene popolata correttamente:
return GetQueryable<ApplicationUser>(e => e.Id == id, "Roles").SingleOrDefault();
Ma quando uso la proiezione (Seleziona) con il seguente Dto:
public class ApplicationUserDto: BaseDto
{
public string Email { get; set; }
public string Name { get; set; }
public List<IdentityUserRole<string>> Roles{ get; set; }
public static Expression<Func<ApplicationUser, ApplicationUserDto>> SelectProperties = (user) => new ApplicationUserDto {
Id = user.Id,
Email = user.Email,
Name = user.Name,
Roles = (List<IdentityUserRole<string>>)user.Roles
};
}
Quindi la seguente query si blocca:
return GetQueryable<ApplicationUser>(e => e.Id == id, "Roles").Select(ApplicationUserDto.SelectProperties).SingleOrDefault();
con il seguente errore:
System.InvalidOperationException: The type of navigation property 'Roles' on the entity type 'IdentityUser<string, IdentityUserClaim<string>, IdentityUserRole
<string>, IdentityUserLogin<string>>' is 'ICollection<IdentityUserRole<string>>' for which it was not possible to create a concrete instance. Either initialize the
property before use, add a public parameterless constructor to the type, or use a type which can be assigned a HashSet<> or List<>.
Registra anche un avviso:
warn: Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory[6]
The Include operation for navigation: 'user.Roles' was ignored because the target navigation is not reachable in the final query results.
In realtà, in EF Core 1.1 Include
viene ignorato se usiamo Select()
, fare riferimento a quanto segue:
La sezione Ignored include: https://docs.microsoft.com/en-us/ef/core/querying/related-data
Questa è la ragione per cui stava mostrando l'avvertimento:
warn: Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory[6]
The Include operation for navigation: 'user.Roles' was ignored because the target navigation is not reachable in the final query results.
e ToList()
non ha funzionato. Quindi era necessaria un'altra proiezione sulle proprietà di navigazione affinché funzionasse.
public static Expression<Func<ApplicationRole, ApplicationRoleDto>> SelectProperties = (role) => new ApplicationRoleDto
{
Id = role.Id,
Name = role.Name.Substring(0, role.Name.Length - role.TenantId.Length),
Description = role.Description,
// another projection on Claims navigation property
Claims = role.Claims.Select(claim => claim.ClaimValue).ToList(),
};
Nota: Anche in questo caso è un problema di prestazioni, dal momento che il caricamento ansioso non si verifica se utilizziamo Select () in un elenco (poiché Include () viene ignorato) genererà una query sql separata per recuperare la proprietà di navigazione di ciascun elemento nel set di risultati.
D'altra parte, se usiamo solo Include()
, fa un join come previsto e quindi si comporta meglio.
È necessario materializzare (eseguire la sottoquery, che raccoglierà ruoli in modo esplicito) per assegnarlo al proprio DTO:
public class ApplicationUserDto: BaseDto
{
public string Email { get; set; }
public string Name { get; set; }
public List<IdentityUserRole<string>> Roles{ get; set; }
public static Expression<Func<ApplicationUser, ApplicationUserDto>> SelectProperties = (user) => new ApplicationUserDto {
Id = user.Id,
Email = user.Email,
Name = user.Name,
Roles = user.Roles.ToList()
};
}
E ricorda di aggiungere: using System.Linq;
al tuo file per poter chiamare .ToList()
su ICollection
.
Come hai detto nei commenti, il tipo che vuoi è una stringa, quindi puoi fare qualsiasi cosa dopo l' user.Roles
, cioè eseguire un'ulteriore proiezione come questa:
user.Roles.Select(role=> role.RoleId).ToList()
Ricorda solo di materializzare i risultati in seguito.