Как я могу запросить эти иерархические данные с помощью LINQ? - PullRequest
5 голосов
/ 25 июня 2009

У меня есть 3 вида объектов: Агентство, BusinessUnit и Клиент (каждый со своей соответствующей таблицей)

С точки зрения иерархии, агентства имеют собственные BusinessUnits, а BusinessUnits - собственные клиенты.

У меня есть 3 объекта C # POCO для их представления (я обычно выбираю в них новые {}, а не использую сгенерированные LINQ классы):

public class Agency
{
    public IEnumerable<BusinessUnit> BusinessUnits { get; set; }
}

public class BusinessUnit
{
    public IEnumerable<Client> Clients { get; set; }
}

public class Client
{
    public int NumberOfAccounts { get; set; }
    public Decimal AmountOfPlacement { get; set; }
    public Decimal AvgBalance { get; set; }
    public Double NeuPlacementScore { get; set; }
}

Вы можете видеть, что агентства содержат список BusinessUnits, а BusinessUnits - список клиентов.

У меня также есть таблица сопоставления с именем BAC_Map в базе данных, которая говорит, кому принадлежит, и это выглядит примерно так:

alt text

Как я могу построить запрос, чтобы я мог запрашивать и возвращать список агентств? Это означает, что я хочу, чтобы у каждого агентства был установлен список объектов BusinessUnit, и я хочу, чтобы в списке BusinessObjects был установлен список клиентов.

Я могу выполнять базовые запросы LINQ, но это немного над моей головой в отношении таблицы Map и множественного числа? запросы.

Как можно создать метод, такой как GetAllAgencies (), который будет запрашивать не только все агентства, но и заполнять его BusinessUnits, которыми владеет агентство, и клиентами, которыми владеет BusinessUnits?


Редактировать: Любые советы или информация приветствуется. Нужно ли делать объединения? Нужно ли использовать несколько запросов для возврата списка Агентства с заполненными его членами?

Ответы [ 3 ]

4 голосов
/ 25 июня 2009

Если вы отбросите все четыре таблицы («Агентство», «BusinessUnit», «Клиент», «Карта») в конструкторе linq to sql и нарисуете взаимосвязи между картой и другими тремя, на карте появятся некоторые полезные свойства.

  //construct a query to fetch the row/column shaped results.
var query = 
  from m in db.map
  //where m.... ?
  let a = m.Agency
  let b = m.BusinessUnit
  let c = m.Client
  // where something about a or b or c ?
  select new {
    AgencyID = a.AgencyID,
    AgencyName = a.Name,
    BusinessUnitID = b.BusinessUnitID,
    ClientID = c.ClientID,
    NumberOfAccounts = c.NumberOfAccounts,
    Score = c.Score
  };
  //hit the database
var rawRecords = query.ToList();

  //shape the results further into a hierarchy.    
List<Agency> results = rawRecords
  .GroupBy(x => x.AgencyID)
  .Select(g => new Agency()
  {
    Name = g.First().AgencyName,
    BusinessUnits = g
    .GroupBy(y => y.BusinessUnitID)
    .Select(g2 => new BusinessUnit()
    {
      Clients = g2
      .Select(z => new Client()
      {
        NumberOfAccounts = z.NumberOfAccounts,
        Score = z.Score
      })
    })
  })
  .ToList();

Если предоставляются соответствующие фильтры (см. Закомментированные предложения where), то в память будут извлечены только необходимые части таблиц. Это стандартное соединение SQL на работе здесь.

2 голосов
/ 25 июня 2009

Я создал ваши таблицы в базе данных SQL Server и попытался воссоздать ваш сценарий в LinqPad. В итоге я получил следующие операторы LINQ, которые в основном приводят к той же структуре ваших классов POCO:

var map =   from bac in BAC_Maps
            join a in Agencies on bac.Agency_ID equals a.Agency_ID
            join b in BusinessUnits on bac.Business_Unit_ID equals b.Business_Unit_ID
            join c in Clients on bac.Client_ID equals c.Client_ID
            select new 
            { 
                AgencyID        =   a.Agency_ID,
                BusinessUnitID  =   b.Business_Unit_ID,
                Client          =   c
            };

var results =   from m in map.ToList()
                group m by m.AgencyID into g
                select new 
                {
                    BusinessUnits = from m2 in g
                                    group m2 by m2.BusinessUnitID into g2
                                    select new
                                    {
                                        Clients = from m3 in g2
                                                select m3.Client
                                    }
                };

results.Dump();

Обратите внимание, что я вызвал map.ToList () во втором запросе. Это фактически привело к одному эффективному запросу. Моя первоначальная попытка не включала .ToList () и привела к девяти отдельным запросам для получения одинаковых результатов. Запрос, сгенерированный версией .ToList (), выглядит следующим образом:

SELECT [t1].[Agency_ID] AS [AgencyID], [t2].[Business_Unit_ID] AS [BusinessUnitID], [t3].[Client_ID], [t3].[NumberOfAccounts], [t3].[AmountOfPlacement], [t3].[AvgBalance], [t3].[NeuPlacementScore]
FROM [BAC_Map] AS [t0]
INNER JOIN [Agencies] AS [t1] ON [t0].[Agency_ID] = [t1].[Agency_ID]
INNER JOIN [BusinessUnits] AS [t2] ON [t0].[Business_Unit_ID] = [t2].[Business_Unit_ID]
INNER JOIN [Clients] AS [t3] ON [t0].[Client_ID] = [t3].[Client_ID]

Вот скриншот с результатами:

альтернативный текст http://img411.imageshack.us/img411/5003/agencybusinessunitclien.png

0 голосов
/ 25 июня 2009

Если вы делаете это с прямым LINQ to SQL, невозможно сделать это без какой-либо рекурсии, независимо от того, делаете ли вы это самостоятельно или скрываете это за методом расширения. Рекурсивный SQL очень плох (много циклов, много отдельных запросов).

Здесь есть два варианта. Один из них - вытащить всю таблицу (таблицы) с иерархией в память и использовать в ней LINQ to Objects. Оставьте таблицы «подробностей» в SQL. Если у вас менее нескольких тысяч объектов, это, вероятно, самый эффективный способ. Вы можете хранить одну копию таблиц в кэше и обновлять их при необходимости. Когда вам нужно извлечь более подробные данные из БД для отдельной записи, вы можете повторно присоединить эту сущность из вашей кэшированной иерархии к новому DataContext и извлечь его.

Другой вариант - использовать более сложную модель отношений в вашей базе данных. Хранение родителя только по своей природе требует рекурсии, но вы можете использовать модель списка смежности для создания одного запроса, который может охватывать много уровней наследования. Это будет означать, что ваши запросы LINQ to SQL станут менее интуитивными (запросы к Entity.Right и Entity.Left не так хороши, как Parent или Children ...), но вы можете сделать в одном запросе то, что может занять сотни или тысячи в буквальном рекурсивном подходе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...