【问题标题】:Linq and Lambda expression for a complex sql query involving joins涉及连接的复杂 sql 查询的 Linq 和 Lambda 表达式
【发布时间】:2012-05-12 21:32:43
【问题描述】:

在 MVC 3 项目中使用 Linq to Entity(实体框架)。

我的模特:

表 - 用户
用户 ID (PK)
...

表 - 客户
客户 ID (PK)

表格 - 属性项
属性项 ID (PK)

表 - MemberContactPreference(包含用户选择的 PropertyItems - 多对多)
用户 ID(FK)
PropertyItemID(FK)

表 ClientProperties(包含属于 Clients 的 PropertyItems - 多对多)
客户 ID (FK)
属性项 ID (FK)

我想列出所有选择了客户选择的所有属性的不同用户。

我的方法:

我得到了一个特定客户的所有属性列表

Iqueryable<ClientProperty> clientProperties  = GetClientProperties(ClientID)

Iqueryable<User> UsersMatchingClientProperties = GetAllUsers();



foreach (ClientProperty property in clientproperties)
{

 UsersMatchingClientProperties = (from uem in UsersMatchingClientProperties
                                  join ucp in GetAllMemberContactPreferences on 
                                  ucp.UserID == uem.UserID
                                  where uem.MemberContactPreferences.SelectMany(      
                                  mcp => mcp.PropertyItemID == property.PropertyItemID)
                                  select uem).Distinct;
}

它只在第一次给出正确的结果。因为它不会在每次迭代时减少 UsersMatchingClientProperties 中的项目数。实际上它用新的结果集替换了集合。我想在每次迭代中过滤掉这个集合。

此外,任何建议在 Lambda 表达式中执行此操作而不使用 Linq。

谢谢

【问题讨论】:

  • 我不太确定我是否理解您要执行的操作,但我注意到的一件事是您在取回该集合时并未枚举该集合。这意味着它保持可查询状态,使用GetAllUsers().ToArray()GetClientProperties(ClientID).ToArray() 确保这些集合在下一次查询之前在内存中,因为目前这将在数据库上执行,我认为这不是你想要的

标签: linq entity-framework lambda


【解决方案1】:

在 for 循环中生成 iqueryable 似乎是一件危险的事情,最终可能会导致一次执行的怪物 sql join。

无论如何,我认为你不需要那个。这样的事情怎么样?

// for a given client, find all users 
// that selected ALL properties this client also selected

Iqueryable<ClientProperty> clientProperties  = GetClientProperties(ClientID)

Iqueryable<User> allUsers= GetAllUsers();

Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();


Iqueryable<User> UsersMatchingClientProperties = allUsers
.Where(user => allMemberContactProperties
               .Where(membP => membP.UserID==user.UserID)
               .All(membP => clientProperties
                           .Select(clientP => clientP.PropertyID)
                           .Contains(membP.PropertyID)
               )
);

如果您希望用户为给定客户端选择任何属性,这是一个替代查询

// for a given client, find all users 
// that selected ANY properties this client also selected

Iqueryable<ClientProperty> clientProperties  = GetClientProperties(ClientID)

Iqueryable<User> allUsers= GetAllUsers();

Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();


Iqueryable<User> UsersMatchingClientProperties = clientproperties
.Join(allMembersContactProperties, // join clientproperties with memberproperties
      clientP => clientP.PropertyItemID, 
      membP   => membP.PropertyItemID,
      (clientP, membP) => membP)) // after the join, ignore the clientproperties, keeping only memberproperties
.Distinct()                       // distinct is optional here. but perhaps faster with it?
.Join(allUsers,                   //join memberproperties with users
      membP => membP.UserID,
      user  => user.UserID,
      (membP, user) => user))     // after the join, ignore the member properties, keeping only users 
.Distinct();

【讨论】:

    【解决方案2】:

    我相信 Hugo 在建议改进查询的方法方面做得很好 (+1)。但这还不能解释问题的原因,即修改后的闭包陷阱。

    我认为,在您的循环之后,有一些代码实际上会在UsersMatchingClientProperties 中执行查询。在那一刻,查询被执行循环变量property的最后一个值! (循环变量是在迭代中创建的每个查询委托中的闭包,并且每次迭代都会对其进行修改。

    像这样改变循环:

    foreach (ClientProperty property in clientproperties)
    {
        var property1 = property;
        ...
    

    并在查询中使用 property1。那应该可以解决问题的原因。但如前所述,看起来整个过程都可以改进。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多