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)? А именно, ссылаясь на столбцы вложенной таблицы вместо вашего производного имени.