EF Core - 无法访问Select()中的导航属性

时间:2017-02-24 10:24:20

标签: c# entity-framework-core

我正在尝试访问IdentityUser模型的导航Roles属性。

在GetQueryable函数内部我设置了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;
    }

如果我执行以下查询,则会成功填充roles属性:

return GetQueryable<ApplicationUser>(e => e.Id == id, "Roles").SingleOrDefault();

但是当我使用跟随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
    };
}

然后以下查询崩溃:

return GetQueryable<ApplicationUser>(e => e.Id == id, "Roles").Select(ApplicationUserDto.SelectProperties).SingleOrDefault();

出现以下错误:

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<>.

它还会记录警告:

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. 

2 个答案:

答案 0 :(得分:1)

您需要实现(执行子查询,它将显式收集角色),以便将其分配给您的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()
    };
}

请记得添加: using System.Linq; 到您的文件,以便能够在.ToList()上致电ICollection

正如你在评论中所说,你想要的类型是一个字符串,所以你可以在user.Roles之后做任何事情,即进行如下的进一步投影:

user.Roles.Select(role=> role.RoleId).ToList()

请记住事后实现结果。

答案 1 :(得分:0)

实际上,如果我们使用Include,则会忽略EF Core 1.1 Select(),请参阅以下内容:

忽略包含部分: https://docs.microsoft.com/en-us/ef/core/querying/related-data

这就是它显示警告的原因:

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.

ToList()也无效。因此,导航属性需要另一个投影才能工作。

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

注意:在这种情况下,它们也是性能问题,因为如果我们在列表上使用Select(),则不会发生急切加载(因为忽略了Include())它会生成一个单独的sql查询以获取结果集中每个元素的导航属性。

另一方面,如果我们使用Include(),它会按预期进行连接,因此效果会更好。