在LINQ-to-SQL中处理跨上下文连接

10

最初我使用了LINQ-to-SQL编写了这个查询

var result = from w in PatternDataContext.Windows
    join cf in PatternDataContext.ControlFocus on w.WindowId equals cf.WindowId
    join p in PatternDataContext.Patterns on cf.CFId equals p.CFId
    join r in ResultDataContext.Results on p.PatternId equals r.PatternId
    join fi in ResultDataContext.IclFileInfos on r.IclFileId equals fi.IclFileId
    join sp in sessionProfileDataContext.ServerProfiles on fi.ServerProfileId equals sp.ProfileId
    join u in infrastructure.Users on sp.UserId equals u.Id
    where w.Process.Equals(processName)
    select u.DistributedAppId;

当我执行代码并在QuickWatch..中查看result时,提示出现以下信息:

查询包含对另一个数据上下文中定义的项的引用

通过Google搜索,我在Stackoverflow上找到了这篇文章,学习了如何模拟跨上下文连接。根据该文章的建议,我对查询进行了一些更改,如下:

var result = from w in PatternDataContext.Windows
    join cf in PatternDataContext.ControlFocus on w.WindowId equals cf.WindowId
    join p in PatternDataContext.Patterns on cf.CFId equals p.CFId
    join r in SimulateJoinResults() on p.PatternId equals r.PatternId
    join fi in SimulateJoinIclFileInfos() on r.IclFileId equals fi.IclFileId
    join sp in SimulateJoinServerProfiles() on fi.ServerProfileId equals sp.ProfileId
    join u in SimulateJoinUsers() on sp.UserId equals u.Id
    where w.Process.Equals(processName)
    select u.DistributedAppId;

这个查询正在使用这些SimulateXyz方法:

private static IQueryable<Result> SimulateJoinResults()
{
  return from r in SessionDataProvider.Instance.ResultDataContext.Results select r;
}
private static IQueryable<IclFileInfo> SimulateJoinIclFileInfos()
{
  return from f in SessionDataProvider.Instance.ResultDataContext.IclFileInfos select f;
}
private static IQueryable<ServerProfile> SimulateJoinServerProfiles()
{
  return from sp in sessionProfileDataContext.ServerProfiles select sp;
}
private static IQueryable<User> SimulateJoinUsers()
{
  return from u in infrastructureDataContext.Users select u;
}

但即使采用这种方法,问题仍未解决。在 QuickWatch... 中我仍然会收到以下消息:

查询包含对在不同数据上下文中定义的项的引用

有没有解决此问题的方案?除了解决方案外,我还想知道为什么问题仍然存在,以及新解决方案如何精确地消除它,以便下次我可以自己解决这类问题。顺便说一句,我对 LINQ 还不太熟悉。

4个回答

7
我以前也需要这样做,有两种方法可以实现。
第一种是将所有服务器移动到单个上下文中。您可以通过将LINQ-to-SQL指向单个服务器,然后在该服务器上创建链接服务器来连接其他所有服务器。然后,您只需为您感兴趣的任何表创建视图,并将这些视图添加到您的上下文中。
第二种方法是手动进行联接,从一个上下文中提取数据,并仅使用您需要加入另一个上下文的属性。例如,
int[] patternIds = SessionDataProvider.Instance.ResultDataContext.Results.Select(o => o.patternId).ToArray();
var results = from p in PatternDataContext.Patterns
              where patternIds.Contains(p.PatternId)
              select p;

虽然第一种方法更容易使用,但它也存在一些问题。问题在于你要依赖SQL Server与链接服务器的高效性,而这正是SQL Server擅长做得不好的地方。例如,考虑以下查询语句:

var results = from p in DataContext.Patterns
              join r in DataContext.LinkedServerResults on p.PatternId equals r.PatternId
              where r.userId = 10;

当您枚举此查询时,将会发生以下情况(我们将常规服务器和链接服务器分别称为 MyServer MyLinkedServer ):
  1. MyServer 请求MyLinkedServer的结果
  2. MyLinkedServer将结果发送回MyServer
  3. MyServer获取这些结果,以模式表为基础进行连接,并仅返回Results.userId = 10的内容。
因此,现在的问题是:筛选是在MyServer还是在MyLinkedServer上完成?根据我的经验,对于如此简单的查询,通常会在MyLinkedServer上执行。但是,一旦查询变得更加复杂,您会突然发现MyServer正在请求从MyLinkedServer获取整个结果表并在连接之后进行筛选!这会浪费带宽,并且如果结果表足够大,则可能会将50ms的查询转换为50秒的查询!
您可以使用存储过程来修复效率低下的跨服务器连接,但如果您执行大量复杂的跨服务器连接,则最终可能需要为大多数查询编写存储过程,这是很多工作,并且会破坏使用L2SQL的初衷(不需要编写大量SQL)。
相比之下,以下代码将始终在包含结果表的服务器上执行筛选:
int[] patternIds = (from r in SessionDataProvider.Instance.ResultDataContext.Results
                    where r.userId = 10
                    select r.PatternId).ToArray();
var results = from p in PatternDataContext.Patterns
              where patternIds.Contains(p.PatternId)
              select p;

哪种方案最适合你的情况取决于你的判断。


请注意有第三种潜在解决方案我没有提到,因为它并不是真正的程序员解决方案:您可以要求服务器管理员设置一个复制任务,每天/每周/每月从MyLinkedServer 复制必要的数据到 MyServer。这只是一种选项,如果:

  • 您的程序可以使用来自MyLinkedServer稍微陈旧的数据
  • 您只需要从MyLinkedServer读取,从不写入
  • 您需要的来自MyLinkedServers的表不是过于巨大的
  • 您有足够的空间/带宽
  • 您的数据库管理员不吝啬/懒惰

请注意,如果patternId数组超过2000个参数,SQL服务器将向您抛出异常。 - NoLifeKing

3
你的SimulateJoins无法工作,因为它们返回。你当前的解决方案与以前的解决方案完全相同,这就是你得到相同异常的原因。如果你再次查看链接的问题,你会发现他们的辅助方法返回,这是进行跨上下文操作的唯一方式。正如你可能已经知道的那样,这意味着join将在应用程序服务器上的内存中执行,而不是在数据库服务器上 = 它将从你的部分查询中提取所有数据,并将join作为linq-to-objects执行。
在数据库级别上进行跨上下文连接在我看来是不可能的。你可以有不同的连接、不同的连接字符串和不同的服务器等。Linq-to-sql不能处理这个问题。

1
你可以通过在第二个上下文中“逃离”Linq to SQL来解决这个问题,例如,在ResultDataContext.ResultsResultDataContext.IclFileInfos上调用.ToList(),使得你的查询最终看起来像这样:

var result = from w in PatternDataContext.Windows
    join cf in PatternDataContext.ControlFocus on w.WindowId equals cf.WindowId
    join p in PatternDataContext.Patterns on cf.CFId equals p.CFId
    join r in ResultDataContext.Results.ToList() 
        on p.PatternId equals r.PatternId
    join fi in ResultDataContext.IclFileInfos.ToList() 
        on r.IclFileId equals fi.IclFileId
    join sp in sessionProfileDataContext.ServerProfiles on 
        fi.ServerProfileId equals sp.ProfileId
    join u in infrastructure.Users on sp.UserId equals u.Id
    where w.Process.Equals(processName)
    select u.DistributedAppId;

或者使用AsEnumerable(),只要你从Linq to SQL转到Linq to Objects处理“有问题”的上下文。

0

虽然这是一个老问题,但我碰巧也遇到了同样的问题,我的解决方案是通过第一个上下文的ExecuteQuery方法直接将手动编写的T-SQL跨服务器查询(带有链接服务器)传递给提供程序:

db.ExecuteQuery(Of cTechSupportCall)(strSql).ToList

这样可以避免在服务器端创建视图,而且 Linq to SQL 仍然可以将结果映射到正确的类型。当有一个查询在 Linq 中无法得到很好的表达时,这非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接