我可以强制自动生成的Linq-to-SQL类使用OUTER JOIN吗?

时间:2010-03-14 22:01:30

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

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

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

因为我总是希望每当处理订单时都有销售人员数据,所以我使用SalesPerson2来指定在填充订单实例时填充两个销售人员字段,如下所示:

DataLoadOptions.LoadWith

问题我遇到的是Linq to SQL正在使用类似下面的SQL来加载订单:

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

如果总是有两个销售员记录,但是因为有时没有第二个销售人员(SELECT ... FROM Order O INNER JOIN SalesPerson SP1 ON SP1.salesPersonId = O.firstSalesPersonId INNER JOIN SalesPerson SP2 ON SP2.salesPersonId = O.secondSalesPersonId secondSalesPersonId),这将是有意义的,NULL会导致查询不返回任何记录在那种情况下。

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

(请注意,因为我几乎只使用生成的类,所以如果可以避免的话,我宁愿不为这一个案例添加一些内容。)


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

2 个答案:

答案 0 :(得分: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,以便有人可以尝试重现您所看到的行为。

答案 1 :(得分:0)

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

<强>更新 由于该字段可以为空,因此您的问题可能是明确声明dataLoadOptions.LoadWith<>()。我在我当前有订单的项目中运行类似情况,但订单经历了多个阶段。每个阶段对应一个单独的表,其中包含与该阶段相关的数据。我只是检索订单,如果存在,则跟随相应的数据。我根本不使用dataLoadOptions,它完成了我需要做的事情。例如,如果订单具有采购订单记录,但没有发票记录,则Order.PurchaseOrder将包含采购订单数据,Order.Invoice将为空。我的查询看起来像这样:

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

我尽量不对LINQ-to-SQL进行微观管理......它可以直接开箱即用95%。

更新2: 我发现this post讨论了如何使用DefaultIfEmpty()来填充子实体(如果它们不存在)。我在我的数据库上使用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&lt;&gt;()的原因是因为查询语句中的表未被引用,因此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)

如果尚未建立该关联,则表示该记录的ListCompany_ID表中的MailingList字段等于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