Работа с кросс-контекстными объединениями в LINQ-to-SQL - PullRequest
10 голосов
/ 25 марта 2011

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

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

При поиске я нашел эту тему в самом 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 1022 *

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 голосов
/ 28 марта 2011

Я должен был сделать это раньше, и есть два способа сделать это.

Первый - переместить все серверы в один контекст.Вы делаете это, указывая 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;

Когда вы перечислите этот запрос, произойдет следующее (давайте назовем обычный и связанный серверы MyServer и MyLinkedServer соответственно)

  1. MyServer запрашивает MyLinkedServer о результатах
  2. MyLinkedServer отправляет результаты обратно на MyServer
  3. MyServer принимает эти результаты, присоединяетсяих в таблице Patterns, и возвращает только те, у которых Results.userId = 10

Итак, теперь возникает вопрос: когда выполняется фильтрация - на MyServer или MyLinkedServer?По моему опыту, для такого простого запроса он обычно выполняется на MyLinkedServer.Однако, как только запрос усложняется, вы внезапно обнаружите, что MyServer запрашивает всю таблицу результатов из MyLinkedServer и выполняет фильтрацию после объединения!Это приводит к бесполезной трате пропускной способности, и, если таблицы результатов достаточно велики, можно превратить запрос 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, не слишком велики
  • У вас есть свободное пространство / пропускная способность
  • Администраторы вашей базы данных не скупы / ленивы
3 голосов
/ 28 марта 2011

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

Межконтекстное объединение на уровне базы данных IMO невозможно. Вы можете иметь разные соединения, разные строки соединения с разными серверами и т. Д. Linq-to-sql не справляется с этим.

1 голос
/ 28 марта 2011

Вы могли бы обойти это, «сбежав» из 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 в Objects для «оскорбительного» контекста.

0 голосов
/ 10 апреля 2014

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

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

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

...