如何从LINQ获得一对多的SingleOrDefault值?

时间:2014-11-23 15:27:09

标签: c# asp.net linq asp.net-identity

我有两个ASP.NET标识使用的类。一个是用户列表的支持类,另一个是角色。在数据库中有三个表。 AspNetUsers,AspNetRoles和AspNetUserRoles。由于它们在实体框架中的映射方式,这三个映射到两个类:

public partial class AspNetUser
{
    public AspNetUser()
    {
        this.AspNetUserClaims = new List<AspNetUserClaim>();
        this.AspNetUserLogins = new List<AspNetUserLogin>();
        this.AspNetRoles = new List<AspNetRole>();
    }
    public int Id { get; set; }
    public virtual ICollection<AspNetRole> AspNetRoles { get; set; }
    public virtual ICollection<AspNetUserClaim> AspNetUserClaims { get; set; }
    public virtual ICollection<AspNetUserLogin> AspNetUserLogins { get; set; }
}

public partial class AspNetRole
{
    public AspNetRole()
    {
        this.AspNetUsers = new List<AspNetUser>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<AspNetUser> AspNetUsers { get; set; }
}

我有这个LINQ语句用于报告数据。请注意,我只想获得与用户关联的第一个角色。这是我试过的:

        var user = await db.AspNetUsers
            .Where(u => u.AspNetRoles.Any(r => r.Id == roleId) || roleId == 0)
            .Include(u => u.AspNetRoles)
            .Select(u => new UserDTO
            {
                Email = u.Email,
                RoleSingleId = u.AspNetRoles.SingleOrDefault(r => r.Id),
                UserId = u.Id,
                UserName = u.UserName
            })
            .ToListAsync();

但是这给了我两个错误,在r.Id

下有一条蓝线

错误4无法将lambda表达式转换为委托类型'System.Func',因为某些返回类型 在块中不能隐式转换为委托返回类型

错误5无法将类型'int'隐式转换为'bool'C:\ App \ ab41 \ Admin1 \ WebRole1 \ Controllers \ Web API - Data \ UserController.cs 169 71 WebRole1

注意RoleSingleId声明为int,AspNetRoles的Id也是int。

2 个答案:

答案 0 :(得分:5)

有一些问题。让我们从给出编译时错误的那个开始。

RoleSingleId = u.AspNetRoles.SingleOrDefault(r => r.Id),

SingleOrDefault采用谓词,而不是投射。您作为参数传递的是一个条件,它告诉您是否应该考虑指定的AspNetRole。它是u.AspNetRoles.Where(r => r.Id).SingleOrDefault()的缩写。

这不是你想要的。投影是使用Select完成的,而不是Where,因此可以通过将其写为u.AspNetRoles.Select(r => r.Id).SingleOrDefault()来修复该部分。

一旦你解决了这个问题,下一个问题就是SingleOrDefault()被设计为在找到多个项目时抛出异常。这不是你想要的。您说您希望返回其中一个角色ID。那是FirstOrDefault()

第三个问题是,SingleOrDefault()通常在查询中通常不受支持。它将进行编译,但无论用户拥有多少角色,它都会在运行时抛出异常,告诉您它只是不理解如何执行此操作,并且您可能希望使用FirstOrDefault()。由于FirstOrDefault()是你应该使用的,所以这不是问题。

注意:您可能还想将Select(r => r.Id)更改为Select(r => (int?)r.Id):前者返回IEnumerable<int>,后者返回IEnumerable<int?>。如果用户没有角色(如果它完全有效),FirstOrDefault()将返回前者0,后者nullnullRoleSingleId {{1}}这里可能更有意义。您需要以相同的方式调整{{1}}的类型。

答案 1 :(得分:1)

如果您使用SelectMany而不是Select,那么您的返回将像传统的SQL语句一样扁平化。即,将为该用户的每个角色重复用户详细信息。

获取单个或默认用户/角色将如下所示:

var user = await db.AspNetUsers
        .Where(u => u.AspNetRoles.Any(r => r.Id == roleId) || roleId == 0)
        .Include(u => u.AspNetRoles)
        .SelectMany(u => new UserDTO
        {
            Email = u.Email,
            RoleSingleId = u.AspNetRoles.Id,
            UserId = u.Id,
            UserName = u.UserName
        })
        .SingleOrDefault();

当然,如果您正在尝试获取针对每个用户的第一个角色列表,那么您将需要GroupBy用户ID,用户名和电子邮件,以某种方式聚合角色ID(例如获取最大值)每个用户的价值)。

var user = await db.AspNetUsers
        .Where(u => u.AspNetRoles.Any(r => r.Id == roleId) || roleId == 0)
        .Include(u => u.AspNetRoles)
        .SelectMany(u => new UserDTO
        {
            Email = u.Email,
            RoleSingleId = u.AspNetRoles.Id,
            UserId = u.Id,
            UserName = u.UserName
        })
        .GroupBy(key => new { key.Email, key.UserId, key.UserName },
        (key, agg) => new UserDTO 
            {
                Email = key.Email,
                UserId = key.Id,
                UserName = key.UserName,
                RoleSingleId = agg.Max(a => a.RoleSingleId),
            })
        .ToListAsync();