【问题标题】:LINQ: Having trouble navigating across multiple jeft joinLINQ:在多个左连接之间导航时遇到问题
【发布时间】:2016-11-25 14:36:58
【问题描述】:

我在 SQL 中尝试做的事情如下所示:

SELECT
    ....
    ct.FirstName + ' ' + ct.LastName
    ....
FROM
    Leads l
    LEFT JOIN LeadContacts lc ON lc.LeadID = l.LeadID 
    LEFT JOIN Contacts ct on ct.ContactID = lc.ContactID

在领先模型中:

public class Lead
{
    ....
    public virtual ICollection<LeadContact> LeadContacts { get; set; }
    ....
}

在 LeadContact 模型中:

public class LeadContact
{
    ....
    [ForeignKey(nameof(LeadID))]
    public virtual Lead Lead { get; set; }

    [ForeignKey(nameof(ContactID))]
    public virtual Contact Contact { get; set; }
    ....
}

现在,我正在尝试从以下实例构建一个对象:

leads = IQueryable<Lead>...

对于我来说,我不知道如何导航到联系人表。

var results = leads.Select(l => new QuoteSearchItem
{
    ....
    SomeProperty = l.SomeProperty,
    LeadSales = l.LeadContacts. ?????
    SomeOtherProperty = l.SomeOtherProperty
    ....
 });

QuoteSearchItem.LeadSales 是一个字符串。它必须是:

Contacts.FirstName + " " + Contacts.LastName

由于关系类型,l.LeadContacts.Contacts 不是一个选项。

我需要做什么才能做到这一点?

【问题讨论】:

  • LeadContact 中真的有比 Lead 和 Contact 更多的字段吗?如果没有,EF 将简化您的访问,您可以直接在 Lead 类中创建 public virtual ICollection&lt;Contact&gt; Contacts {get;set;}(也可以在 Contact 类中创建 public virtual ICollection&lt;Lead&gt; Leads {get;set;})。

标签: c# sql asp.net entity-framework linq


【解决方案1】:

如果您使用理解语法(当有多个 froms 时使用 SelectMany),您可以轻松做到这一点:

var query = from l in Leads
            from lc in l.LeadContacts.DefaultIfEmpty()
            from ct in lc.Contacts.DefaultIfEmpty()
            select new
            {
               //....
               ContactName = ct.FirstName + ' ' + ct.LastName
               //....
            };

如果你愿意,在 LinqPad 中运行它,你可以获得 lambda 版本 + 如果那是 Linq To SQL SQL 本身。

编辑:您的课程暗示每个 LeadContact 有一个联系人,那么您可以缩短此:

var query = from l in Leads
            from lc in l.LeadContacts.DefaultIfEmpty()
            select new
            {
                //....
                ContactName = lc.Contact.FirstName + ' ' + lc.Contact.LastName
                //....
            };

它几乎映射到这个使用 Northwind 示例数据库的示例:

var data = from c in Customers
               from o in c.Orders.DefaultIfEmpty()
               select new {
                 CustomerId = c.CustomerID,
                 OrderId = (int?)o.OrderID,
                 Employee = o.Employee.FirstName + ' ' + o.Employee.LastName
               };

产生这个 SQL:

-- Region Parameters
DECLARE @p0 NChar(1) = ' '
-- EndRegion
SELECT [t0].[CustomerID] AS [CustomerId], [t1].[OrderID] AS [OrderId], ([t2].[FirstName] + @p0) + [t2].[LastName] AS [Employee]
FROM [Customers] AS [t0]
LEFT OUTER JOIN [Orders] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
LEFT OUTER JOIN [Employees] AS [t2] ON [t2].[EmployeeID] = [t1].[EmployeeID]

编辑:我在说什么,而不是:

var results = leads.Select(l => new QuoteSearchItem
{
    ....
    LeadSales = l.LeadContacts. ?????
    ....
});

这样做:

var results = from l in leads 
              from lc in l.LeadContacts.DefaultIfEmpty() 
              select new  QuoteSearchItem
              {
                ....
                LeadSales = lc.Contact.FirstName + " " + lc.Contact.LastName
               ....
              };

无论是(方法)lambda 形式还是理解语法,最终结果都是相同的。当需要 selectMany 时,我发现理解语法更容易。正如我所说,如果你渴望方法语法,请尝试在 LinqPad 中运行它,它会给你对应的 lambda,类似于:

var result = leads
   .SelectMany (
      l => l.LeadContacts.DefaultIfEmpty (), 
      (l, lc) => 
         new QuoteSearchItem
         {
            //... 
            LeadSales = lc.Contact.FirstName + " " + lc.Contact.LastName
         }
   );

【讨论】:

  • 好的 Cetin。谢谢你!所以,你能帮我多一点吗?我将如何在现有的 Leads.select(...) 语句中执行此操作?请参阅 OP。
  • 我试着写到这里,但是因为格式化而变得丑陋。改为编辑我的答案。
  • 问题:您的示例在“Do it like”下是否支持 LEFT JOIN?
  • 是的,它确实支持左连接(DefaultIfEmpty() 提供了)。检查为 Norhwind 示例生成的 SQL。 --- 哎呀,那里缺少 DefaultIfEmpty(),正在纠正。
【解决方案2】:

首先,您的 LeadContact 表中需要一个外键。我会假设你的 Lead 表有一个 LeadId 字段:

//Foreign key for Standard
public int LeadId { get; set; }

[ForeignKey("LeadId")]
public Lead Lead { get; set; }

这表示 Lead 和 Lead Contact 之间的关系,它将记录限制为您想要的记录。 LeadContact 是一个交叉引用,因此我确信它已经有一个“LeadId”字段(或其他名称)映射到 Lead 表中的 Lead Id 字段,因此请指明这种关系,如上所示。

假设 LeadSales 的类型为 IEnumerable&lt;string&gt;,那么您可以这样做:

var results = leads.Select(l => new QuoteSearchItem
{
    ....
    LeadSales = l.LeadContacts.Select(lc => lc.Contact.FirstName + " " + lc.Contact.LastName);
    ....
});

【讨论】:

  • 好的,是的,你是对的。 LeadContact 表中有一个 LeadId,并且该外键已经在模型中。我编辑了 OP 以反映。
  • 那就这样吧。当你做 l.LeadContacts 时,你只会得到满足关系的潜在客户联系人记录。当您对此进行选择时,您将获得每个 LeadContact 记录的联系人,如答案中所示。你还有什么要找的吗?
  • 我知道你现在想要得到什么。我不知道 QuoteSearchItem 的结构,但看起来 LeadSales 应该是 IEnumerable 类型。然后您可以执行以下操作:LeadSales = l.LeadContacts.Select(lc => lc.Contact.FirstName + " " + lc.Contact.LastName)。我相应地编辑了答案。
【解决方案3】:

你可以这样做:

l.LeadContacts.SelectMany(x =&gt; x.Contacts)

但是,您的 Contact 属性不是您的 LeadContact 类中的列表。所以要么把它做成一个列表,要么你可以像这样访问它:

l.LeadContacts.Select(x => x.Contact)

【讨论】:

  • 好吧好吧....我开始看图片了。我无法编辑 LeadContact 类中的 Contact 属性。其他地方用的太多了。
  • 无需编辑 LeadContact 类。了解这种关系是多对一的,多是 LeadLocations,One 是联系人。这就是为什么您没有联系人集合的原因。 l.LeadContacts.Select(x => x.Contact) 应该可以满足您的需求,如@Jinish 所示。
  • 好的,我将如何确保我只获得 LeadContacts WHERE LeadId 等于“lead”中记录的 LeadId?或者换句话说:LEFT JOIN LeadContacts lc ON lc.LeadID = l.LeadID
  • 还有,我如何获得 Contact.FirstName + ' ' Contact.LastName?
  • var contact = l.LeadContacts.Select(x => x.Contact) 作为联系人;然后你可以访问你的联系人对象的属性
【解决方案4】:

你在使用上下文吗?

var result = await context.SearchLead.AsNoTracking()
.Where(l => l.LeadContacts.Any(lc => lc.Contact == QuoteSearchItem))
.ToListAsync();

【讨论】:

  • 好的......答案是“不”,但我想去那里。我很想回过头来做这个异步。但首先,我只需要按原样工作即可。
  • 好吧,好吧,我的错。查看 Microsoft 上的这篇文章,您只需要进行一些调整即可。 msdn.microsoft.com/en-us/library/bb397895.aspx。题外话,但当你有机会时,你应该大胆地检查实体框架。大量的数据库查询选项变得简单。
猜你喜欢
  • 1970-01-01
  • 2023-01-31
  • 2012-01-08
  • 1970-01-01
  • 2020-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-26
相关资源
最近更新 更多