在 LINQ-to-SQL 中使用跨上下文联接

发布于 2024-10-26 01:54:16 字数 2397 浏览 0 评论 0原文

最初我使用 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.. 中看到结果时,它显示了以下消息:

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

在谷歌搜索上,我发现这个主题< /a> 在 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 新手。

Initially I had written this query using 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;

And when I executed it, and saw result in the QuickWatch.., it showed this message:

the query contains references to items defined on a different data context

On googling, I found this topic at Stackoverflow itself, where I learned simulating cross context joins and as suggested there, I changed my query a bit to this:

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;

This query is using these SimulateXyz methods:

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;
}

But even this approach didn't solve the problem. I'm still getting this message in QuickWatch...:

the query contains references to items defined on a different data context

Any solution for this problem? Along with the solution, I would also want to know why the problem still exists, and how exactly the new solution removes it, so that from next time I could solve such problems myself. I'm new to LINQ, by the way.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

‘画卷フ 2024-11-02 01:54:16

我以前必须这样做,有两种方法可以做到。

第一个是将所有服务器移至单个上下文中。为此,您可以将 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 来提高链接服务器的性能,而这是众所周知的它的不擅长之处。例如,考虑以下查询:

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

当您枚举此查询时,将发生以下情况(让我们分别将普通服务器和链接服务器称为 MyServerMyLinkedServer

  1. MyServerMyLinkedServer 请求结果
  2. MyLinkedServer 将结果发送回 MyServer
  3. MyServer 获取这些结果,加入它们位于 Patterns 表中,并且仅返回 Results.userId = 10 的结果。

现在的问题是:过滤何时完成 - 在 MyServerMyLinkedServer 上?根据我的经验,对于这样一个简单的查询,通常会在 MyLinkedServer 上完成。但是,一旦查询变得更加复杂,您会突然发现 MyServer 正在从 MyLinkedServer 请求整个结果表并执行过滤< em>加入后!这会浪费带宽,并且如果结果表足够大,可能会将 50 毫秒的查询变成 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 获取的表并不是太大
  • 您有可用的空间/带宽
  • 您的数据库管理员并不吝啬/懒惰

I've had to do this before, and there are two ways to do it.

The first is to move all the servers into a single context. You do this by pointing LINQ-to-SQL to a single server, then, in that server, create linked servers to all the other servers. Then you just create views for any tables you're interested from the other servers, and add those views to your context.

The second is to manually do the joins yourself, by pulling in data from one context, and using just the properties you need to join into another context. For example,

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;

Though the first is easier to work with, it does have its share of problems. The problem is that you're relying on SQL Server to be performant with linked servers, something it is notoriously bad at. For example, consider this query:

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

When you enumerate this query, the following will occur (let's call the normal and linked servers MyServer and MyLinkedServer, respectively)

  1. MyServer asks MyLinkedServer for the Results
  2. MyLinkedServer sends the Results back to MyServer
  3. MyServer takes those Results, joins them on the Patterns table, and returns only the ones with Results.userId = 10

So now the question is: When is the filtering done - on MyServer or MyLinkedServer? In my experience, for such a simple query, it will usually be done on MyLinkedServer. However, once the query gets more complicated, you'll suddenly find that MyServer is requesting the entire Results table from MyLinkedServer and doing the filtering after the join! This wastes bandwidth, and, if the Results tables is large enough, could turn a 50ms query into a 50 second query!

You could fix unperformant cross-server joins using stored procedures, but if you do a lot of complex cross-server joins, you may end up writing stored procedures for most of your queries, which is a lot of work and defeats part of the purpose of using L2SQL in the first place (not having to write a lot of SQL).

In comparison, the following code would always perform the filtering on the server containing the Results table:

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;

Which is best for your situation is up to your best judgement.


Note that there is a third potential solution which I did not mention, as it is not really a programmer-solution: you could ask your server admins to set up a replication task to copy the necessary data from MyLinkedServer to MyServer once a day/week/month. This is only an option if:

  • Your program can work with slightly stale data from MyLinkedServer
  • You only need to read, never write, to MyLinkedServer
  • The tables you need from MyLinkedServers are not exorbitantly huge
  • You have the space/bandwidth available
  • Your database admins are not stingy/lazy
欲拥i 2024-11-02 01:54:16

您的 SimulateJoins 无法工作,因为它们返回 IQueryable。您当前的解决方案与以前的解决方案完全相同,这就是您得到相同异常的原因。如果您再次检查链接的问题,您将看到它们的辅助方法返回 IEnumerable ,这是进行跨上下文操作的唯一方法。您可能已经知道,这意味着联接将在应用程序服务器而不是数据库服务器的内存中执行=它将从部分查询中提取所有数据并作为 linq-to-objects 执行联接。

在我看来,数据库级别的跨上下文连接是不可能的。您可以对不同的服务器有不同的连接、不同的连接字符串等。Linq-to-sql 不处理此问题。

Your SimulateJoins can't work because they return IQueryable. Your current solution is exactly the same as your former one and that is the reason why you get the same exception. If you check the linked question again you will see that their helper methods return IEnumerable which is the only way to make cross context operations. As you probably already know it means that join will be performed in memory on the application server instead of the database server = it will pull all data from your partial queries and execute join as linq-to-objects.

Cross context join on database level is IMO not possible. You can have different connections, different connection strings with different servers, etc. Linq-to-sql does not handle this.

落墨 2024-11-02 01:54:16

您可以通过在第二个上下文上“转义”Linq to SQL 来解决此问题,即在 ResultDataContext.ResultsResultDataContext 上调用 .ToList() 实例.IclFileInfos 以便您的查询最终看起来像:

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 以获得“有问题的”上下文。

You could work around it by "escaping from" Linq to SQL on the second context, i.e., calling for instance .ToList() on ResultDataContext.Results and ResultDataContext.IclFileInfos so that your query ended up looking like:

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;

Or AsEnumerable() as long as you "get out" of Linq to SQL and into Linq to Objects for the "offending" context.

吻安 2024-11-02 01:54:16

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

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

这只是节省了你由于必须创建视图服务器端,Linq to SQL 仍然将结果映射到正确的类型。当存在一个无法在 Linq 中制定的查询时,这非常有用。

Old question, but as I happened to have the same problem, my solution was to pass the manually crafted T-SQL cross-server query (with linked servers) directly to the provider through the ExecuteQuery method of the first context:

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

This just saves you from having to create a view server side, and Linq to SQL still maps the results to the proper type. This is useful when there is that one query that is just impossible to formulate in Linq.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文