Linq-to-sql不会产生多个外连接

本文关键字:连接 Linq-to-sql | 更新日期: 2023-09-27 18:07:55

我有一个奇怪的问题,关于linq-to-sql,我真的试着搜索它。我正在设计一个sql数据库,最近刚刚试图从它检索对象。

问题在于多个连接。我所有的表都使用标识列作为主键。

Db设计如下:

MasterTable: Id(主键,标识列,int), MasterColumn1 (nvarchar(50))

Slave1: Id(主键,标识列,int), MasterId (int,主键-> MasterTable Id), SlaveCol1

Slave2: Id(主键,标识列,int), MasterId (int,主键-> MasterTable Id), SlaveColumn2

代码:

var db = new TestDbDataContext() { Log = Console.Out };
var res = from f in db.MasterTables
          where f.MasterColumn1 == "wtf"
          select new
                     {
                         f.Id, 
                         SlaveCols1 = f.Slave1s.Select(s => s.SlaveCol1),
                         SlaveCols2 = f.Slave2s.Select(s => s.SlaveColumn2)
                     };
foreach (var re in res)
{
    Console.Out.WriteLine(
        re.Id + " "
      + string.Join(", ", re.SlaveCols1.ToArray()) + " "
      + string.Join(", ", re.SlaveCols2.ToArray())
    );
}

日志是:

SELECT [t0].[Id], [t1].[SlaveCol1], (
   SELECT COUNT(*)
   FROM [FR].[Slave1] AS [t2]
   WHERE [t2].[MasterId] = [t0].[Id]
   ) AS [value]
FROM [FR].[MasterTable] AS [t0]
LEFT OUTER JOIN [FR].[Slave1] AS [t1] ON [t1].[MasterId] = [t0].[Id]
WHERE [t0].[MasterColumn1] = @p0
ORDER BY [t0].[Id], [t1].[Id]
-- @p0: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [wtf]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.5420
SELECT [t0].[SlaveColumn2]
   FROM [FR].[Slave2] AS [t0]
   WHERE [t0].[MasterId] = @x1
-- @x1: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.5420
1 SlaveCol1Wtf SlaveCol2Wtf

为什么不做两个外连接呢?我真的很关心这一点,因为我有一个更大的数据库,其中有许多表引用同一个表(都有一对多关系),并且有20个选择往返于databaseserver并不是最佳的!

作为旁注。我可以通过使用显式外连接来产生想要的结果,如下所示:

var db = new TestDbDataContext() { Log = Console.Out };
var res = from f in db.MasterTables
          join s1 in db.Slave1s on f.Id equals s1.MasterId into s1Tbl
          from s1 in s1Tbl.DefaultIfEmpty()
          join s2 in db.Slave2s on f.Id equals s2.MasterId into s2Tbl
          from s2 in s2Tbl.DefaultIfEmpty()
          where f.MasterColumn1 == "wtf"
          select new { f.Id, s1.SlaveCol1, s2.SlaveColumn2 };
foreach (var re in res)
{
    Console.Out.WriteLine(re.Id + " " + re.SlaveCol1 + " " + re.SlaveColumn2);
}

但是我想使用Linq-To-Sql提供的引用,而不是手动连接!如何?

----------- edit -----------------

我也试过这样预取:

using (new DbConnectionScope())
{
    var db = new TestDbDataContext() { Log = Console.Out };
    DataLoadOptions loadOptions = new DataLoadOptions();
    loadOptions.LoadWith<MasterTable>(c => c.Slave1s);
    loadOptions.LoadWith<MasterTable>(c => c.Slave2s);
    db.LoadOptions = loadOptions;
    var res = from f in db.MasterTables
              where f.MasterColumn1 == "wtf"
              select f;
    foreach (var re in res)
    {
        Console.Out.WriteLine(re.Id + " " + 
            string.Join(", ", re.Slave1s.Select(s => s.SlaveCol1).ToArray()) + " " + 
            string.Join(", ", re.Slave2s.Select(s => s.SlaveColumn2).ToArray()));
    }
}

相同的结果=

Linq-to-sql不会产生多个外连接

使用LoadOptions和遍历关联而不是显式连接的预取选项是正确的,但是由于您试图从MasterTable进行多个1-M导航,因此您将有效地在Slave1和Slave2记录之间创建笛卡尔积。因此,LINQ to SQL会忽略您的加载选项,并延迟加载每个子记录。

你可以通过删除第二个子加载选项来稍微优化一下。生成的查询现在将执行单个请求,返回MasterTable和Slave1s,但随后延迟加载每个Slave2s。如果执行以下操作,应该会看到相同的结果:

var res = from f in db.MasterTables
          where f.MasterColun1 == "wtf"
          select new 
          {
             f.Id,
             Cols1 = f.Slave1s.Select(s => s.SlaveCol1).ToArray()
             Cols2 = f.Slave2s.Select(s => s.SlaveColumn2).ToArray()
          }

您应该看到MasterTables和Slave1s之间的左连接,然后延迟加载Slave2s,以避免在SQL的扁平结果中Slave1和Slave2之间的笛卡尔积。

至于"为什么",Linq-to-SQL可能认为通过避免多个外部连接可以使您的查询更好。

假设您从主表中提取20个条目,并且每个从表中的每个条目在主表中有20个条目。使用外连接,您将在一次往返中通过网络拉入8000个条目,而不是两次往返,每次400个条目。有时往返两趟会更便宜。在这种特殊情况下,它可能不正确,但如果您以这种方式连接许多表,并且每个表提取大量数据,则很可能会非常容易地改变天平。

您可能还想了解LINQ to SQL可能在一次往返中使用多个结果集执行两个select的可能性。在这种情况下,双语句方法可能比双外部连接快得多。

更新

经过更多的测试后,很明显Jim Wooley的答案是正确的:显然Linq to SQL只是决定不主动加载任何属性,除了您指定的第一个属性。这也很奇怪,因为它也不是完全惰性加载它。它在单独的往返中加载每个属性,作为查询初始计算的一部分。对我来说,这似乎是LINQ to SQL的一个相当重要的限制。

最有可能的是,它正在执行初始查询,然后在第一个查询之后执行投影,然后触发下一组查询。

我认为你需要预先加载那些已连接的表。

查看这些链接:

  • LINQ:从第二个表预取数据

  • http://www.west-wind.com/weblog/posts/2009/Oct/12/LINQ-to-SQL-Lazy-Loading-and-Prefetching(在"DataLoadOptions for prefetch "标题下)

  • http://www.davidhayden.com/blog/dave/archive/2007/08/05/LINQToSQLLazyLoadingPropertiesSpecifyingPreFetchWhenNeededPerformance.aspx

尝试:

var res = from f in db.MasterTables
          where f.MasterColumn1 == "wtf"
          let s1 = f.Slave1s.Select(s => s.SlaveCol1)
          let s2 = f.Slave2s.Select(s => s.SlaveColumn2)
          select new {
                         f.Id, 
                         SlaveCols1 = s1,
                         SlaveCols2 = s2
                     };