LINQ Outer присоединяется к динамическому заказу

В заявлении Linq вроде следующего (сужено до соответствующих частей для вопроса):

var sites = from s in DataContext.Sites
            join add in DataContext.Address 
              on s.PrimaryAddress equals add into sa
    from a in sa.DefaultIfEmpty()
            select new {
                        s.Id,
                        s.SiteName,
                        PrimaryAddress = a
                       };

Проблема, с которой мы столкнулись, заключается в том, что элемент управления, в конечном счете основанный на комбинации GridView/LinqDataSource, не может правильно сортироваться в присоединенном классе PrimaryAddress (игнорирует его). Мы видим такое же поведение для всех соединенных классов, как это. Есть ли способ для GridView справиться с этим? Или, альтернативно, есть ли способ с помощью выражений, что мы можем обрабатывать его в коде в динамическом OrderBy?

Дополнительные примечания: при добавлении .OrderBy(s => s.PrimaryAddress) мы получаем Cannot order by type 'Address'при выполнении .OrderBy(gs => gs.PrimaryBusinessAddress.AddressLine1) мы получаем Specified method is not supported. Выполняя сортировку на самом LinqDataSource.OrderBy, уже слишком поздно... он сортирует только записи на текущей странице, а не весь набор.

Для ясности, это формат адреса в сетке:

public partial class Address
{
  public override string ToString()
  {
    return string.Format("{0} {1}{2} {3}",
    AddressLine1,
    City,
    !string.IsNullOrEmpty(State) && State != "None" ? ", " + State : string.Empty,
    !string.IsNullOrEmpty(Country) ? string.Format("({0})", Country) : string.Empty);
  }
}

Если бы мы могли отсортировать по AddressLine1 + City + State + Country, это было бы достаточно хорошо, но я не уверен, как это сделать с помощью дерева выражений... независимо от того, какой OrderBy мы указываем с помощью выражений, он возвращается к сортировке по s.SiteName (сортировка по умолчанию в сетке). В нашем элементе управления сеткой есть очень ограниченное количество таких классов, как это, и наличие переключателя и регистра Expression для каждого не будет проблемой вообще. Есть мысли по поводу решения или совершенно другой подход?

3 ответа

Решение

Я привык использовать Linq to Entities в коде, но я использую LINQ to SQL в LINQPad достаточно часто, и я довольно хорошо с ним ознакомился. Я не совсем уверен, что понимаю, с какими трудностями вы сталкиваетесь, но я думаю, что должно работать следующее:

var sites = from s in DataContext.Sites
            orderby s.PrimaryAddress.AddressLine1,
                     s.PrimaryAddress.City,
                     s.PrimaryAddress.State,
                     s.PrimaryAddress.Country
            select new 
            {
                s.Id,
                s.SiteName,
                s.PrimaryAddress
            };

Дайте мне знать, если есть что-то, чего я не понимаю.

Обновить

Я не уверен, почему это не работает для вас. Я просто сделал следующее в LINQPad (режим LINQ to SQL):

from p in person
orderby p.clue_type.clue_type_id,
        p.clue_type.clue_type
select new
{
    p.person_id, p.clue_type
}

Все результаты имели clue_type = null. LINQ to SQL просто обрабатывает нулевые ссылки как значения с полностью нулевыми свойствами. Вот сгенерированный SQL:

SELECT TOP (10) [t0].[person_id], [t2].[test], [t2].[clue_type_id], [t2].[clue_type]
FROM [person] AS [t0]
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t1].[clue_type_id], [t1].[clue_type]
    FROM [clue_types] AS [t1]
    ) AS [t2] ON [t2].[clue_type_id] = [t0].[clue_type_id]
ORDER BY [t2].[clue_type_id], [t2].[clue_type]

Обратите внимание на левое внешнее соединение. Разве это не сделает то, что вы просите?

Обновление 2

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

public class SiteDisplayInfo
{
    public int Id {get;set;}
    public string SiteName {get;set;}
    public string PrimaryAddress {get;set;}

    public static readonly Dictionary<string, Func<IQueryable<Site>, IOrderedQueryable<Site>>> OrderByFuncs = 
    new Dictionary<string, Func<IQueryable<Site>, IOrderedQueryable<Site>>>
    {
        {"Id", q => q.OrderBy(s => s.Id)},
        {"SiteName", q => q.OrderBy(s => s.SiteName)},
        {"PrimaryAddress", 
        q => q.OrderBy(s => s.PrimaryAddress.AddressLine1)
                             .ThenBy(s => s.PrimaryAddress.City)}
    };
}

...

public IEnumerable<SiteDisplayInfo> GetSites(string orderByString)
{
    IQueryable<Site> sites = DataBase.Sites;
    if (orderByString != null && SiteDisplayInfo.OrderByFuncs.ContainsKey(orderByString))
    {
        sites = SiteDisplayInfo.OrderByFuncs[orderByString](sites);
    }
    var query = from s in sites
                select new SiteDisplayInfo
                {
                    Id = s.Id,
                    SiteName = s.SiteName,
                    PrimaryAddress = s.PrimaryAddress.AddressLine1 + s.PrimaryAddress.City
                };
    return query.ToList();
}

Есть несколько других способов сделать что-то подобное, но это дает вам общее представление.

Я только что попробовал аналогичную настройку, что у вас есть, это должно работать:

var sites = from s in dc.Sites
    join addr in dc.Addresses
        on s.PrimaryAddress equals addr into sa
    from a in sa.DefaultIfEmpty()
    orderby a.AddressLine1, a.City, a.State, a.Country /* insert orderby here */
    select new
        {
            s.Id,
            s.SiteName,
            PrimaryAddress = a
        };

Если вы хотите изменить ориентацию порядка, просто добавьте по убыванию в каждый столбец:

orderby a.AddressLine1 descending, a.City descending, a.State descending, a.Country descending

Хотя этот подход работает, он не совсем динамический. Вам придется переписать запрос для сортировки в другой ориентации или порядке столбцов.

Если это то, что вам нужно, я бы посоветовал использовать метод с подходом с некоторыми дополнительными функциями или полностью динамический подход с использованием динамического OrderBy, который позволяет указывать имя столбца как литерал. Для последнего проверьте эту тему и этот пост.

При добавлении.OrderBy(s => s.PrimaryAddress) мы получаем Cannot order by type 'Address'

Это потому, что PrimaryAddress существует только в коде. Оператор LINQ будет собираться и получать все строки для создания адреса, а затем объединять их вместе в коде. Вы пробовали какую-либо форму.OrderBy(a => a.Address)? А именно, ссылаясь на столбцы вложенной таблицы вместо вашего производного имени.

Другие вопросы по тегам