【问题标题】:Can I force the auto-generated Linq-to-SQL classes to use an OUTER JOIN?我可以强制自动生成的 Linq-to-SQL 类使用 OUTER JOIN 吗?
【发布时间】:2010-03-14 22:01:29
【问题描述】:

假设我有一个Order 表,其中有一个FirstSalesPersonId 字段和一个SecondSalesPersonId 字段。这两个都是引用SalesPerson 表的外键。对于任何给定的订单,一个或两个销售人员都可能被记入该订单。换句话说,FirstSalesPersonId 永远不可能是NULL,但SecondSalesPersonId 可以NULL

当我将OrderSalesPerson 表放到“Linq to SQL 类”设计图面上时,类构建器会发现从Order 表到SalesPerson 表的两个FK 关系,因此生成的Order 类有一个SalesPerson 字段和一个SalesPerson1 字段(我可以将其重命名为SalesPerson1SalesPerson2 以避免混淆)。

因为我总是希望在处理订单时获得销售人员数据,所以我使用DataLoadOptions.LoadWith 指定在填充订单实例时填充两个销售人员字段,如下所示:

dataLoadOptions.LoadWith<Order>(o => o.SalesPerson1);
dataLoadOptions.LoadWith<Order>(o => o.SalesPerson2);

问题我遇到的是 Linq to SQL 使用类似以下 SQL 的东西来加载订单:

SELECT ...
FROM Order O
INNER JOIN SalesPerson SP1 ON SP1.salesPersonId = O.firstSalesPersonId
INNER JOIN SalesPerson SP2 ON SP2.salesPersonId = O.secondSalesPersonId

如果总是有两个销售人员记录,这将是有意义的,但由于有时没有第二个销售人员(secondSalesPersonIdNULL),INNER JOIN 导致查询在这种情况下不返回任何记录。

我实际上想要的是将第二个INNER JOIN 更改为LEFT OUTER JOIN。有没有办法通过类生成器的 UI 来做到这一点?如果没有,我还能如何做到这一点?

(请注意,因为我几乎只使用生成的类,所以如果可以避免的话,我宁愿不要在这种情况下添加一些东西)。


编辑:根据我的评论回复,SecondSalesPersonId 字段可为空的(在数据库中和生成的类中)。

【问题讨论】:

    标签: visual-studio visual-studio-2008 linq-to-sql


    【解决方案1】:

    默认行为实际上LEFT JOIN,假设您已正确设置模型。

    这是我刚刚在我自己的一个数据库上测试过的一个稍微匿名的例子:

    class Program
    {
        static void Main(string[] args)
        {
            using (TestDataContext context = new TestDataContext())
            {
                DataLoadOptions dlo = new DataLoadOptions();
                dlo.LoadWith<Place>(p => p.Address);
                context.LoadOptions = dlo;
    
                var places = context.Places.Where(p => p.ID >= 100 && p.ID <= 200);
                foreach (var place in places)
                {
                    Console.WriteLine(p.ID, p.AddressID);
                }
            }
        }
    }
    

    这只是一个简单的测试,它会打印出地点列表及其地址 ID。以下是分析器中显示的查询文本:

    SELECT [t0].[ID], [t0].[Name], [t0].[AddressID], ...
    FROM [dbo].[Places] AS [t0]
    LEFT OUTER JOIN (
        SELECT 1 AS [test], [t1].[AddressID],
            [t1].[StreetLine1], [t1].[StreetLine2],
            [t1].[City], [t1].[Region], [t1].[Country], [t1].[PostalCode]
        FROM [dbo].[Addresses] AS [t1]
    ) AS [t2] ON [t2].[AddressID] = [t0].[AddressID]
    WHERE ([t0].[PlaceID] >= @p0) AND ([t0].[PlaceID] <= @p1)
    

    这不是一个非常漂亮的查询(您的猜测与我的猜测一样好,1 as [test] 是关于什么的),但它绝对是一个LEFT JOIN,并且没有表现出您似乎遇到的问题有。而且这只是使用生成的类,我没有做任何更改。

    请注意,我还在双重关系上进行了测试(即单个 Place 有两个 Address 引用,一个可以为空,一个不能),我得到了完全相同的结果。第一个(不可为空)变成INNER JOIN,第二个变成LEFT JOIN

    它必须是模型中的某些内容,例如更改第二个引用的可空性。我知道你说它被配置为可空,但也许你需要仔细检查?如果它绝对可以为空,那么我建议您发布您的完整架构和 DBML,以便有人可以尝试重现您所看到的行为。

    【讨论】:

      【解决方案2】:

      如果您使数据库表中的secondSalesPersonId 字段可以为空,则LINQ-to-SQL 应正确构造关联对象,以便生成的SQL 语句将执行LEFT OUTER JOIN

      更新: 由于该字段可以为空,因此您的问题可能在于明确声明 dataLoadOptions.LoadWith&lt;&gt;()。我在我当前的项目中遇到了类似的情况,我有一个订单,但订单经历了多个阶段。每个阶段对应一个单独的表,其中包含与该阶段相关的数据。我只需检索订单,然后相应的数据(如果存在)随之而来。我根本不使用dataLoadOptions,它可以满足我的需要。例如,如果订单有采购订单记录,但没有发票记录,Order.PurchaseOrder 将包含采购订单数据,Order.Invoice 将为空。我的查询看起来像这样:

      DC.Orders.Where(a => a.Order_ID == id).SingleOrDefault();
      

      我尽量不对 LINQ-to-SQL 进行微观管理...它可以直接完成我需要的 95% 的工作。

      更新 2: 我发现this post 讨论了 DefaultIfEmpty() 的使用,以便在子实体不存在时用 null 填充它们。我在我的数据库上使用 LINQPad 进行了尝试,并将该示例转换为 lambda 语法(因为这是我使用的):

      ParentTable.GroupJoin
      (
          ChildTable, 
          p => p.ParentTable_ID, 
          c => c.ChildTable_ID, 
          (p, aggregate) => new { p = p, aggregate = aggregate }
      )
      .SelectMany (a => a.aggregate.DefaultIfEmpty (), 
          (a, c) => new 
          { 
              ParentTableEntity = a.p, 
              ChildTableEntity = c
          }
      )
      

      从我可以从该语句中看出,GroupJoin 表达式关联父表和子表,而 SelectMany 表达式聚合相关的子记录。关键似乎是使用 DefaultIfEmpty,即使没有相关的子记录,它也会强制包含父实体记录。 (感谢您强迫我进一步深入研究……我想我可能已经找到了一些有用的东西来帮助我在我的管道上获得一个相当大的报告……)

      更新 3: 如果目标是保持简单,那么看起来您将不得不直接在 Select() 表达式中引用这些销售人员字段。您必须首先使用 LoadWith() 的原因是因为在查询语句中的任何地方都没有引用这些表,因此 LINQ 引擎不会自动提取该信息。

      举个例子,给定这个结构:

      MailingList               ListCompany
      ===========               ===========
      List_ID (PK)              ListCompany_ID (PK)
      ListCompany_ID (FK)       FullName (string)
      

      我想获取与特定邮件列表关联的公司名称:

      MailingLists.Where(a => a.List_ID == 2).Select(a => a.ListCompany.FullName)
      

      如果该关联尚未建立,这意味着该记录的MailingList 表中的ListCompany_ID 字段等于null,这是LINQ 引擎生成的结果SQL:

      SELECT [t1].[FullName]
      FROM [MailingLists] AS [t0]
      LEFT OUTER JOIN [ListCompanies] AS [t1] ON [t1].[ListCompany_ID] = [t0].[ListCompany_ID]
      WHERE [t0].[List_ID] = @p0
      

      【讨论】:

      • @Neil:它可为空的(甚至在“链接到 SQL 类”用户界面中显示为可为空)。
      • @Neil:是的,如果我不使用 LoadWith,那么它只会按需加载销售人员数据。但是,在我的情况下,我将在一个循环中处理数百个订单记录,这将意味着数百次访问数据库以单独获取每个销售人员记录。这正是我试图通过 LoadWith 避免的。它适用于第一个销售人员记录;我正试图让它与第二个一样好。
      • @Neil:谢谢,感谢您的宝贵时间,但您有点偏离了我想要达到的目标,即当我实例化一个 Order 对象时,Order .SalesPerson1 和 Order.SalesPerson2 字段在同一数据库查询中同时填充。我不想编写 SQL 来完成它(或 linq 伪 SQL),我只想让它像在简单情况下那样“工作”。这似乎不是不合理的,因为这并不是一个复杂的场景。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多