简体   繁体   English

Linq查询与嵌套选择

[英]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 the MessageId values of a sub-selection of all Notification 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查询更易于理解和维护。

Direct Solution 直接解决方案

(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 . 我不确定NotificationsOrgUsersOrgRelations之间的关系。 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)您不想要完整的消息,只想要MessageIdComment

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语句不会大大提高性能,我怀疑它是否会提高可读性和可维护性。

Solution using possibilities of entity framework 使用实体框架的可能性的解决方案

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 . 似乎MessageNotification之间存在一对多的关系:每个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 . 这是所有实体框架需要知道的,您计划了MessagesNotifications之间的一对多关系。 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. 重要的是具有从MessageNotification的重要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 . 我不确定NotificationsOrgUsersOrgRelations之间的关系。 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个不同部分,如下所示-

  1. Filter out the OrgUserIds first 首先过滤掉OrgUserIds
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();
  1. Using the filteredOrgUserIds to filter out the notifications. 使用filteredOrgUserIds过滤掉通知。
var filteredNotifications = dc.Notifications.Where(n => 
     n.NotificationTypeId == 7 && filteredOrgUserIds.Contains(n.OrgUserId))
     .Select(m => m.messageid)
     .Distinct()
     .ToList();
  1. Lastly filter out the messages by looking at the filteredNotifications . 最后,通过查看filteredNotifications筛选出消息
    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的食谱中:

  1. Translate subselects as separately declared variables. 将子选择转换为单独声明的变量。
  2. Translate each clause in LINQ clause order, translating monadic and aggregate operators ( DISTINCT , TOP , MIN , MAX etc) into functions applied to the whole LINQ query. 以LINQ子句顺序转换每个子句,将单子运算符和集合运算符( DISTINCTTOPMINMAX等)转换为应用于整个LINQ查询的函数。
  3. SELECT fields must be replaced with select new { ... } creating an anonymous object with all the desired fields or expressions. SELECT字段必须替换为select new { ... }创建具有所有所需字段或表达式的匿名对象。
  4. Translate 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM