[英]Linq query with nested select(s)
Trying to figure out the right way to express the following SQL in a linq statement and getting nowhere. 试图找出在linq语句中表达以下SQL的正确方法,却无济于事。
select messageid, comment
from Message
where isactive = 1 and messageid in
(
select messageid
from org.Notification
where NotificationTypeId = 7 and orguserid in
(
select OrgUserId
from org.OrgUser
where OrgId in
(
select ChildOrgId from OrgRelation
where ParentOrgId = 10001
)
)
group by messageid
)
This works as-is in SQL Server, but I want to use this query in C# (EF) code, and I seem to be running around in circles. 这在SQL Server中按原样工作,但是我想在C#(EF)代码中使用此查询,而且我似乎在圈子里乱跑。
Apparently Notification
objects have a messageId
. 显然, Notification
对象具有messageId
。 Several Notification
objects may have the same value of MessageId
. 几个Notification
对象可能具有相同的MessageId
值。
I want the
Messages
that have an Id that equal one of theMessageId
values of a sub-selection of allNotification
objects. 我想要ID等于所有Notification
对象的子选择的MessageId
值之一的Messages
。
First I'll show you a LINQ query that solves your problem, then I'll tell you something about entity framework that would make this kind of LINQ queries easier to understand and maintain. 首先,我将向您展示解决您的问题的LINQ查询,然后再介绍一些有关实体框架的知识,这些实体框架将使这种LINQ查询更易于理解和维护。
(1) Select the notifications of whom the messages should be fetched: (1)选择应从中获取消息的通知:
IQueryable<Notification> notificationsToUse = org.Notifications
.Where(notification => notification.TypeId == 7
&& ....);
This is your inner select. 这是您内在的选择。 I'm not sure about the relations between Notifications
, OrgUsers
and OrgRelations
. 我不确定Notifications
, OrgUsers
和OrgRelations
之间的关系。 But that is outside this question. 但这不属于这个问题。
(2) Extract all used MessageIds
of these Notifications
(2)提取这些Notifications
所有使用的MessageIds
IQueryable<int> messageIdsUsedByNotificationsToUse = notificationsToUse
.Select(notification => notification.MessageId)
// remove duplicates:
.Distinct();
(3) Fetch all active messages with an Id in `messageIdsUsedByNotificationsToUse (3)在`messageIdsUsedByNotificationsToUse中使用ID提取所有活动消息
IQueryable<Message> fetchedActiveMessages = org.Messages
.Where(message => message.IsActive
&& messageIdsUsedByNotificationsToUse.Contains(message.Id));
(4) You don't want the complete message, you only want the MessageId
and the Comment
: (4)您不想要完整的消息,只想要MessageId
和Comment
:
var result = fetchedActiveMessages.Select(message => new
{
MessageId = message.Id,
Comment = message.Comment,
});
TODO: if desired: make one big LINQ statement. 待办事项:如果需要的话:发表一条大型LINQ声明。
Until now you haven't accessed the database yet. 到目前为止,您还没有访问数据库。 I only changed the Expression in the IQueryable. 我只更改了IQueryable中的表达式。 Making it one big LINQ statement won't increase performance very much and I doubt whether it would improve readability and maintainability. 使其成为一条重要的LINQ语句不会大大提高性能,我怀疑它是否会提高可读性和可维护性。
It seems there is a one-to-many relation between Message
and Notification
: Every Message
has zero or more Notifications
, every Notification
belongs to exactly one Message
, using the foreign key MessageId
. 似乎Message
与Notification
之间存在一对多的关系:每个Message
都有零个或多个Notifications
,每个Notification
使用外键MessageId
恰好属于一个Message
。
If you stuck to the entity framework code-first conventions, you designed your classes similar to the following. 如果您坚持使用实体框架的代码优先约定,则可以设计与以下类似的类。 (emphasizing the one-to-many relation): (强调一对多关系):
class Message
{
public int Id {get; set;}
// every Message has zero or more Notifications (one-to-many)
public virtual ICollection<Notification> Notifications {get; set;}
... // other properties
}
class Notification
{
public int Id {get; set;}
// every Notifications belongs to exactly one Message using foreign key
public int MessageId {get; set;}
public virtual Message Message {get; set;}
... // other properties
}
class MyDbContext : DbContext
{
public DbSet<Message> Messages {get; set;}
public DbSet<Notification> Notifications {get; set;}
}
This is all entity framework needs to know that you planned a one-to-many relation between Messages
and Notifications
. 这是所有实体框架需要知道的,您计划了Messages
和Notifications
之间的一对多关系。 Entity framework knows which properties you intended to be the primary keys and the foreign keys, and it knows about the relation between the tables. 实体框架知道您打算将哪些属性用作主键和外键,并且知道表之间的关系。
Sometimes there are good reasons to deviate from the conventions. 有时,有充分的理由偏离惯例。 This needs to be solved using attributes or fluent API. 这需要使用属性或流畅的API来解决。
The important thing is the structure with the virutal ICollection from Message
to Notification
and the virtual reference back from Notification
to the Message
that it belongs to. 重要的是具有从Message
到Notification
的重要ICollection以及从Notification
到其所属的Message
的虚拟引用的结构。
If you've designed your classes like this, your query will be a piece of cake: 如果您是这样设计类的,那么您的查询就轻松了:
(1) Select the notifications you want to use: (1)选择要使用的通知:
IQueryable<Notification> notificationsToUse = ... same as above
(2) Now you can select the messages belonging to these Notifications directly: (2)现在,您可以直接选择属于这些通知的消息:
var result = notificationsToUse.Select(notification => notification.Message)
Because every notification belongs to exactly one message, I'm certain there are no duplicates. 因为每个通知仅属于一条消息,所以我确定没有重复项。
Continuing: only the MessageId
and the Comment
of the active messages 继续:仅MessageId
和活动消息的Comment
.Where(message => message.IsActive)
.Select(message => new
{
MessageId = message.Id,
Comment = message.Comment,
});
I wasn't sure about the relations between Notifications
, OrgUsers
and OrgRelations
. 我不确定Notifications
, OrgUsers
和OrgRelations
之间的关系。 If you design your classes such that they represent a proper one-to-many or many-to-many relation, then even expression (1) will be much simpler. 如果您将类设计为代表正确的一对多或多对多关系,则即使表达式(1)也要简单得多。
You can break down your query into 3 different parts as shown below - 您可以将查询分为3个不同部分,如下所示-
var filteredOrgUserIds = dc.OrgUsers.Where(u => dc.OrgRelations.Where(o
=>o.ParentOrgId == 10001).Select(d =>
d.ChildOrgId).ToList().Contains(u.OrgId))
.Select(uid => uid.OrgUserId)
.Distinct()
.ToList();
var filteredNotifications = dc.Notifications.Where(n =>
n.NotificationTypeId == 7 && filteredOrgUserIds.Contains(n.OrgUserId))
.Select(m => m.messageid)
.Distinct()
.ToList();
m.isactive == 1 && filteredNotifications.Contains(m.messageid))
.Select(m => new { message = m.messageid, comment = m.comment })
.Distinct()
.ToList();
This should work.Please try this once from your end and let us know if this helps. 这应该可行。请从头开始尝试一次,并告诉我们是否有帮助。
From my Recipe for converting SQL to LINQ to SQL: 从我的将SQL转换为LINQ到SQL的食谱中:
DISTINCT
, TOP
, MIN
, MAX
etc) into functions applied to the whole LINQ query. 以LINQ子句顺序转换每个子句,将单子运算符和集合运算符( DISTINCT
, TOP
, MIN
, MAX
等)转换为应用于整个LINQ查询的函数。 SELECT
fields must be replaced with select new {
... }
creating an anonymous object with all the desired fields or expressions. SELECT
字段必须替换为select new {
... }
创建具有所有所需字段或表达式的匿名对象。 IN
to .Contains()
and NOT IN
to !
翻译IN
到.Contains()
和NOT IN
来!
... Contains()
. ... Contains()
Noting that your SQL query is using GROUP BY
when it should use DISTINCT
, and then applying the recipe rules: 注意您的SQL查询在应使用DISTINCT
时使用GROUP BY
,然后应用配方规则:
var childOrgs = from or in OrgRelation where or.ParentOrgId == 1001 select or.ChildOrgId;
var orgUsers = from ou in OrgUser where childOrgs.Contains(ou.OrgId) select ou.OrgUserId;
var notificationMessages = (from n in Notification
where n.NotificationTypeId == 7 && orgUsers.Contains(n.orguserid)
select n.messageid).Distinct();
var ans = from m in Message
where m.isactive == 1 && notificationMessages.Contains(m.messageid)
select new { m.messageid, m.comment };
如果需要使用IN子句,则应使用Contains方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.