Entity Framework Core 5.0 如何将 LINQ 转换为多对多连接以使用交集 table 作为 ASP.NET 成员资格
Entity Framework Core 5.0 How to convert LINQ for many-to-many join to use Intersection table for ASP.NET Membership
问题:
如何转换 LINQ 查询以对子 select 执行 LEFT OUTER JOIN,INNER JOIN 两个 tables 并具有谓词?
上下文:
我正在使用 EFCore Tools
逆向工程 功能从 Entity Framework 6 (EF6)
升级到 Entity Framework Core 5 (EFCore)
。我有一个使用 LINQ 查询 ASP.NET Membership
系统上 AspNet_Users
和 AspNet_Roles
table 之间的多对多关系的查询。查询通过 AspNet_UsersInRoles
交集 table 抽象出连接。结果,在 EF6 中我有以下 LINQ:
(from r in this.DbContext.aspnet_Users
where r.UserId == dpass.UserId
select r.aspnet_Roles.Select(x => x.RoleName)
).FirstOrDefault();
生成以下 SQL 查询(使用 SQL Server Profiler 检索):
DECLARE @p__linq__0 uniqueidentifier = '<Runtime_GUID>'
SELECT
[Limit1].[UserId] AS [UserId],
[Join1].[RoleName] AS [RoleName],
CASE WHEN ([Join1].[UserId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM (SELECT TOP (1) [Extent1].[UserId] AS [UserId]
FROM [dbo].[aspnet_Users] AS [Extent1]
WHERE [Extent1].[UserId] = @p__linq__0
) AS [Limit1]
LEFT OUTER JOIN (SELECT [Extent2].[UserId] AS [UserId]
, [Extent3].[RoleName] AS [RoleName]
FROM [dbo].[aspnet_UsersInRoles] AS [Extent2]
INNER JOIN [dbo].[aspnet_Roles] AS [Extent3]
ON [Extent3].[RoleId] = [Extent2].[RoleId]
) AS [Join1]
ON [Limit1].[UserId] = [Join1].[UserId]
我已将 LINQ 转换为以下内容:
(from ur in this.DbContext.aspnet_UsersInRoles
join r in this.DbContext.aspnet_Roles
on ur.RoleId equals r.RoleId
where ur.UserId == dpass.UserId
join u in this.DbContext.aspnet_Users
on ur.UserId equals u.UserId
into UserRolesJoined from UserRoles in UserRolesJoined.DefaultIfEmpty()
select new { ur.UserId, ur.Role.RoleName }
)
生成以下 SQL 查询(使用 EFCore 的新 .ToQueryString()
方法检索):
DECLARE @__dpass_UserId_1 uniqueIdentifier = '<Runtime_GUID>'
SELECT [a].[UserId], [a2].[RoleName]
FROM [aspnet_UsersInRoles] AS [a]
INNER JOIN [aspnet_Roles] AS [a0] ON [a].[RoleId] = [a0].[RoleId]
LEFT JOIN [aspnet_Users] AS [a1] ON [a].[UserId] = [a1].[UserId]
INNER JOIN [aspnet_Roles] AS [a2] ON [a].[RoleId] = [a2].[RoleId]
WHERE [a].[UserId] = @__dpass_UserId_1
来自 EFCore 的 SQL SELECT 语句 不同于 SQL SELECT 语句 来自 EF6;但是,SQL Select 结果 是相同的。这是重写 LINQ 的正确方法,还是我弄错了?非常感谢任何帮助。
更新
一旦我运行逆向工程工具,原来的LINQ命令有以下错误:
CS1061
'aspnet_Users' 不包含 'aspnet_Roles' 的定义,并且找不到接受类型 'aspnet_Users' 的第一个参数的可访问扩展方法 'aspnet_Roles' (您是否缺少 using 指令或程序集引用?)
当我将 public IEnumerable<object> aspnet_Roles;
添加到 aspnet_users
时,出现以下错误。
CS1061
'object' 不包含 'RoleName' 的定义,并且找不到接受类型 'object' 的第一个参数的可访问扩展方法 'RoleName' (您是否缺少 using 指令或程序集引用?)
以下是 Microsoft 创建的 ASP.NET 会员系统中的相关 table。有关完整架构的参考,请参阅:https://docs.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/membership/creating-the-membership-schema-in-sql-server-cs
aspnet_UserInRoles
[Index(nameof(RoleId), Name = "aspnet_UsersInRoles_index")]
public partial class aspnet_UsersInRoles
{
[Key]
public Guid UserId { get; set; }
[Key]
public Guid RoleId { get; set; }
[ForeignKey(nameof(RoleId))]
[InverseProperty(nameof(aspnet_Roles.aspnet_UsersInRoles))]
public virtual aspnet_Roles Role { get; set; }
[ForeignKey(nameof(UserId))]
[InverseProperty(nameof(aspnet_Users.aspnet_UsersInRoles))]
public virtual aspnet_Users User { get; set; }
}
aspnet_Roles
public partial class aspnet_Roles
{
public aspnet_Roles()
{
aspnet_UsersInRoles = new HashSet<aspnet_UsersInRoles>();
}
public Guid ApplicationId { get; set; }
[Key]
public Guid RoleId { get; set; }
[Required]
[StringLength(256)]
public string RoleName { get; set; }
[Required]
[StringLength(256)]
public string LoweredRoleName { get; set; }
[StringLength(256)]
public string Description { get; set; }
[ForeignKey(nameof(ApplicationId))]
[InverseProperty(nameof(aspnet_Applications.aspnet_Roles))]
public virtual aspnet_Applications Application { get; set; }
[InverseProperty("Role")]
public virtual ICollection<aspnet_UsersInRoles> aspnet_UsersInRoles { get; set; }
}
aspnet_Users
[Index(nameof(ApplicationId), nameof(LastActivityDate), Name = "aspnet_Users_Index2")]
public partial class aspnet_Users
{
public aspnet_Users()
{
Users = new HashSet<Users>();
aspnet_PersonalizationPerUser = new HashSet<aspnet_PersonalizationPerUser>();
aspnet_UsersInRoles = new HashSet<aspnet_UsersInRoles>();
}
public Guid ApplicationId { get; set; }
[Key]
public Guid UserId { get; set; }
[Required]
[StringLength(256)]
public string UserName { get; set; }
[Required]
[StringLength(256)]
public string LoweredUserName { get; set; }
[StringLength(16)]
public string MobileAlias { get; set; }
public bool IsAnonymous { get; set; }
[Column(TypeName = "datetime")]
public DateTime LastActivityDate { get; set; }
[ForeignKey(nameof(ApplicationId))]
[InverseProperty(nameof(aspnet_Applications.aspnet_Users))]
public virtual aspnet_Applications Application { get; set; }
[InverseProperty("User")]
public virtual aspnet_Membership aspnet_Membership { get; set; }
[InverseProperty("User")]
public virtual aspnet_Profile aspnet_Profile { get; set; }
[InverseProperty("aspUser")]
public virtual ICollection<Users> Users { get; set; }
[InverseProperty("User")]
public virtual ICollection<aspnet_PersonalizationPerUser> aspnet_PersonalizationPerUser { get; set; }
[InverseProperty("User")]
public virtual ICollection<aspnet_UsersInRoles> aspnet_UsersInRoles { get; set; }
}
如果我是你,我会这样写这个查询:
var result = context.UserRoles
.Where(x => x.UserId == ID_TO_SEARCH)
.Join(
context.Roles,
ur => ur.RoleId,
r => r.Id,
(ur, role) => new
{
ur,
role
}
)
.Select(x => x.role.Name)
.FirstOrDefault();
这产生的查询,对我来说,完全没问题而且更优雅:
SELECT TOP(1) [a0].[Name]
FROM [AspNetUserRoles] AS [a]
INNER JOIN [AspNetRoles] AS [a0] ON [a].[RoleId] = [a0].[Id]
WHERE [a].[UserId] = N''
更新:
如果我理解正确,我在评论中被问到什么,那么这个查询将 select 角色名称 LIKE LEFT JOIN:
var rolesQuery = context.UserRoles
.Join(
context.Roles,
ur => ur.RoleId,
r => r.Id,
(ur, r) => new
{
ur,
r
}
);
var result = context.Users
.Where(x => x.Id == "")
.Select(u => new
{
Name = u.UserName,
Role = rolesQuery
.Where(sub=> sub.ur.UserId == u.Id)
.Select(sub=> sub.r.Name)
.FirstOrDefault()
})
.FirstOrDefault();
这导致此 SQL 语句:
SELECT TOP(1) [a1].[UserName] AS [Name], (
SELECT TOP(1) [a0].[Name]
FROM [AspNetUserRoles] AS [a]
INNER JOIN [AspNetRoles] AS [a0] ON [a].[RoleId] = [a0].[Id]
WHERE [a].[UserId] = [a1].[Id]) AS [Role]
FROM [AspNetUsers] AS [a1]
WHERE [a1].[Id] = N''
如您所见,没有 LEFT JOIN,但是 sub-select 将以与 LEFT JOIN 类似的方式 return 数据。不幸的是,基于 lambda 的查询不支持完整的 LEFT JOIN 并且编写真正的 LEFT JOIN 的唯一选项可以通过 SQL-like IQueryable
.
来丰富
我在 EF core 5 lib 中看到一个名为 LeftJoin()
的方法,但它抛出 NotImplementedException
。我认为它会在稍后发布
如果您只需要特定用户的角色名称,则应简化查询:
var query =
from r in this.DbContext.aspnet_Users
where r.UserId == dpass.UserId
from ro in r.aspnet_Roles
select ro.RoleName;
问题: 如何转换 LINQ 查询以对子 select 执行 LEFT OUTER JOIN,INNER JOIN 两个 tables 并具有谓词?
上下文:
我正在使用 EFCore Tools
逆向工程 功能从 Entity Framework 6 (EF6)
升级到 Entity Framework Core 5 (EFCore)
。我有一个使用 LINQ 查询 ASP.NET Membership
系统上 AspNet_Users
和 AspNet_Roles
table 之间的多对多关系的查询。查询通过 AspNet_UsersInRoles
交集 table 抽象出连接。结果,在 EF6 中我有以下 LINQ:
(from r in this.DbContext.aspnet_Users
where r.UserId == dpass.UserId
select r.aspnet_Roles.Select(x => x.RoleName)
).FirstOrDefault();
生成以下 SQL 查询(使用 SQL Server Profiler 检索):
DECLARE @p__linq__0 uniqueidentifier = '<Runtime_GUID>'
SELECT
[Limit1].[UserId] AS [UserId],
[Join1].[RoleName] AS [RoleName],
CASE WHEN ([Join1].[UserId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM (SELECT TOP (1) [Extent1].[UserId] AS [UserId]
FROM [dbo].[aspnet_Users] AS [Extent1]
WHERE [Extent1].[UserId] = @p__linq__0
) AS [Limit1]
LEFT OUTER JOIN (SELECT [Extent2].[UserId] AS [UserId]
, [Extent3].[RoleName] AS [RoleName]
FROM [dbo].[aspnet_UsersInRoles] AS [Extent2]
INNER JOIN [dbo].[aspnet_Roles] AS [Extent3]
ON [Extent3].[RoleId] = [Extent2].[RoleId]
) AS [Join1]
ON [Limit1].[UserId] = [Join1].[UserId]
我已将 LINQ 转换为以下内容:
(from ur in this.DbContext.aspnet_UsersInRoles
join r in this.DbContext.aspnet_Roles
on ur.RoleId equals r.RoleId
where ur.UserId == dpass.UserId
join u in this.DbContext.aspnet_Users
on ur.UserId equals u.UserId
into UserRolesJoined from UserRoles in UserRolesJoined.DefaultIfEmpty()
select new { ur.UserId, ur.Role.RoleName }
)
生成以下 SQL 查询(使用 EFCore 的新 .ToQueryString()
方法检索):
DECLARE @__dpass_UserId_1 uniqueIdentifier = '<Runtime_GUID>'
SELECT [a].[UserId], [a2].[RoleName]
FROM [aspnet_UsersInRoles] AS [a]
INNER JOIN [aspnet_Roles] AS [a0] ON [a].[RoleId] = [a0].[RoleId]
LEFT JOIN [aspnet_Users] AS [a1] ON [a].[UserId] = [a1].[UserId]
INNER JOIN [aspnet_Roles] AS [a2] ON [a].[RoleId] = [a2].[RoleId]
WHERE [a].[UserId] = @__dpass_UserId_1
来自 EFCore 的 SQL SELECT 语句 不同于 SQL SELECT 语句 来自 EF6;但是,SQL Select 结果 是相同的。这是重写 LINQ 的正确方法,还是我弄错了?非常感谢任何帮助。
更新
一旦我运行逆向工程工具,原来的LINQ命令有以下错误:
CS1061
'aspnet_Users' 不包含 'aspnet_Roles' 的定义,并且找不到接受类型 'aspnet_Users' 的第一个参数的可访问扩展方法 'aspnet_Roles' (您是否缺少 using 指令或程序集引用?)
当我将 public IEnumerable<object> aspnet_Roles;
添加到 aspnet_users
时,出现以下错误。
CS1061
'object' 不包含 'RoleName' 的定义,并且找不到接受类型 'object' 的第一个参数的可访问扩展方法 'RoleName' (您是否缺少 using 指令或程序集引用?)
以下是 Microsoft 创建的 ASP.NET 会员系统中的相关 table。有关完整架构的参考,请参阅:https://docs.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/membership/creating-the-membership-schema-in-sql-server-cs
aspnet_UserInRoles
[Index(nameof(RoleId), Name = "aspnet_UsersInRoles_index")]
public partial class aspnet_UsersInRoles
{
[Key]
public Guid UserId { get; set; }
[Key]
public Guid RoleId { get; set; }
[ForeignKey(nameof(RoleId))]
[InverseProperty(nameof(aspnet_Roles.aspnet_UsersInRoles))]
public virtual aspnet_Roles Role { get; set; }
[ForeignKey(nameof(UserId))]
[InverseProperty(nameof(aspnet_Users.aspnet_UsersInRoles))]
public virtual aspnet_Users User { get; set; }
}
aspnet_Roles
public partial class aspnet_Roles
{
public aspnet_Roles()
{
aspnet_UsersInRoles = new HashSet<aspnet_UsersInRoles>();
}
public Guid ApplicationId { get; set; }
[Key]
public Guid RoleId { get; set; }
[Required]
[StringLength(256)]
public string RoleName { get; set; }
[Required]
[StringLength(256)]
public string LoweredRoleName { get; set; }
[StringLength(256)]
public string Description { get; set; }
[ForeignKey(nameof(ApplicationId))]
[InverseProperty(nameof(aspnet_Applications.aspnet_Roles))]
public virtual aspnet_Applications Application { get; set; }
[InverseProperty("Role")]
public virtual ICollection<aspnet_UsersInRoles> aspnet_UsersInRoles { get; set; }
}
aspnet_Users
[Index(nameof(ApplicationId), nameof(LastActivityDate), Name = "aspnet_Users_Index2")]
public partial class aspnet_Users
{
public aspnet_Users()
{
Users = new HashSet<Users>();
aspnet_PersonalizationPerUser = new HashSet<aspnet_PersonalizationPerUser>();
aspnet_UsersInRoles = new HashSet<aspnet_UsersInRoles>();
}
public Guid ApplicationId { get; set; }
[Key]
public Guid UserId { get; set; }
[Required]
[StringLength(256)]
public string UserName { get; set; }
[Required]
[StringLength(256)]
public string LoweredUserName { get; set; }
[StringLength(16)]
public string MobileAlias { get; set; }
public bool IsAnonymous { get; set; }
[Column(TypeName = "datetime")]
public DateTime LastActivityDate { get; set; }
[ForeignKey(nameof(ApplicationId))]
[InverseProperty(nameof(aspnet_Applications.aspnet_Users))]
public virtual aspnet_Applications Application { get; set; }
[InverseProperty("User")]
public virtual aspnet_Membership aspnet_Membership { get; set; }
[InverseProperty("User")]
public virtual aspnet_Profile aspnet_Profile { get; set; }
[InverseProperty("aspUser")]
public virtual ICollection<Users> Users { get; set; }
[InverseProperty("User")]
public virtual ICollection<aspnet_PersonalizationPerUser> aspnet_PersonalizationPerUser { get; set; }
[InverseProperty("User")]
public virtual ICollection<aspnet_UsersInRoles> aspnet_UsersInRoles { get; set; }
}
如果我是你,我会这样写这个查询:
var result = context.UserRoles
.Where(x => x.UserId == ID_TO_SEARCH)
.Join(
context.Roles,
ur => ur.RoleId,
r => r.Id,
(ur, role) => new
{
ur,
role
}
)
.Select(x => x.role.Name)
.FirstOrDefault();
这产生的查询,对我来说,完全没问题而且更优雅:
SELECT TOP(1) [a0].[Name]
FROM [AspNetUserRoles] AS [a]
INNER JOIN [AspNetRoles] AS [a0] ON [a].[RoleId] = [a0].[Id]
WHERE [a].[UserId] = N''
更新:
如果我理解正确,我在评论中被问到什么,那么这个查询将 select 角色名称 LIKE LEFT JOIN:
var rolesQuery = context.UserRoles
.Join(
context.Roles,
ur => ur.RoleId,
r => r.Id,
(ur, r) => new
{
ur,
r
}
);
var result = context.Users
.Where(x => x.Id == "")
.Select(u => new
{
Name = u.UserName,
Role = rolesQuery
.Where(sub=> sub.ur.UserId == u.Id)
.Select(sub=> sub.r.Name)
.FirstOrDefault()
})
.FirstOrDefault();
这导致此 SQL 语句:
SELECT TOP(1) [a1].[UserName] AS [Name], (
SELECT TOP(1) [a0].[Name]
FROM [AspNetUserRoles] AS [a]
INNER JOIN [AspNetRoles] AS [a0] ON [a].[RoleId] = [a0].[Id]
WHERE [a].[UserId] = [a1].[Id]) AS [Role]
FROM [AspNetUsers] AS [a1]
WHERE [a1].[Id] = N''
如您所见,没有 LEFT JOIN,但是 sub-select 将以与 LEFT JOIN 类似的方式 return 数据。不幸的是,基于 lambda 的查询不支持完整的 LEFT JOIN 并且编写真正的 LEFT JOIN 的唯一选项可以通过 SQL-like IQueryable
.
我在 EF core 5 lib 中看到一个名为 LeftJoin()
的方法,但它抛出 NotImplementedException
。我认为它会在稍后发布
如果您只需要特定用户的角色名称,则应简化查询:
var query =
from r in this.DbContext.aspnet_Users
where r.UserId == dpass.UserId
from ro in r.aspnet_Roles
select ro.RoleName;