Работа с перекрестными коннектиками в LINQ-to-SQL

Первоначально я написал этот запрос, используя 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;

И когда я его выполнил и увидел result в QuickWatch.., он показал это сообщение:

запрос содержит ссылки на элементы, определенные в другом контексте данных

В googling я нашел этот раздел в 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.

Ответ 1

Мне приходилось делать это раньше, и есть два способа сделать это.

Во-первых, переместить все серверы в один контекст. Вы делаете это, указывая LINQ-to-SQL на один сервер, а затем на этом сервере создайте задача репликации, чтобы скопировать необходимые данные из MyLinkedServer в MyServer один раз в день/неделю/месяц. Это только вариант, если:

  • Ваша программа может работать со слегка устаревшими данными из MyLinkedServer
  • Вам нужно читать, никогда не писать, MyLinkedServer
  • Таблицы, которые вам нужны от MyLinkedServers, не являются чрезмерно большими
  • У вас есть доступное пространство/пропускная способность
  • Администраторы базы данных не скупые/ленивые

Ответ 2

Ваш SimulateJoins не может работать, потому что они возвращают IQueryable. Ваше текущее решение в точности совпадает с вашим предыдущим, и именно поэтому вы получаете то же исключение. Если вы снова проверите связанный вопрос, вы увидите, что их вспомогательные методы возвращают IEnumerable, что является единственным способом выполнения кросс-контекстных операций. Как вы, наверное, уже знаете, это означает, что соединение будет выполняться в памяти на сервере приложений вместо сервера базы данных = он вытащит все данные из ваших частичных запросов и выполнит объединение как linq-to-objects.

Перекрестное контекстное соединение на уровне базы данных IMO невозможно. У вас могут быть разные подключения, разные строки подключения с разными серверами и т.д. Linq-to-sql не справляется с этим.

Ответ 3

Вы можете обойти это путем "ускользания от" Linq к SQL во втором контексте, то есть вызова экземпляра .ToList() на ResultDataContext.Results и ResultDataContext.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 в SQL и в Linq к объектам для контекста "оскорбления".

Ответ 4

Старый вопрос, но поскольку у меня была такая же проблема, моим решением было передать связанный вручную кросс-серверный запрос T-SQL (со связанными серверами) непосредственно поставщику с помощью метода ExecuteQuery в первом контексте:

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

Это просто избавляет вас от необходимости создавать сервер на стороне просмотра, а Linq to SQL по-прежнему сопоставляет результаты с соответствующим типом. Это полезно, когда есть один запрос, который просто невозможно сформулировать в Linq.