Динамическая фильтрация и сортировка с помощью Entity рамки


Я разрабатываю приложение, используя ASP.NET MVC 3 и рамки сущности 4.1. В этом приложении у меня есть много страничных списков. Пользователи могут фильтровать и сортировать эти списки.

В результате код, как показано ниже. Я не очень счастлива с настоящим Кодексом. Есть ли лучший способ сделать фильтрацию и сортировку в рамках организации?

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

public UsersController : Controller
{
    private const int PageSize = 25;

    public ActionResult Index(int page = 1, string sort = "", UserSearchViewModel search)
    {
        // Get an IQueryable<UserListItem>
        var users = from user in context.Users
                    select new UserListItem
                    {
                        UserId = user.UserId,
                        Email = user.Email,
                        FirstName = user.FirstName,
                        LastName = user.LastName,
                        UsertypeId = user.UsertypeId,
                        UsertypeDescription = users.Usertype.Description,
                        UsertypeSortingOrder = users.Usertype.SortingOrder
                    };

        // Filter on fields when needed
        if (!String.IsNullOrWhiteSpace(search.Name)) users = users.Where(u => u.FirstName.Contains(search.Name) || u.LastName.Contains(search.Name));
        if (!String.IsNullOrWhiteSpace(search.Email)) users = users.Where(u => u.Email.Contains(search.Email));
        if (search.UsertypeId.HasValue) users = users.Where(u => u.UsertypeId == search.UsertypeId.Value);

        // Calculate the number of pages based on the filtering
        int filteredCount = users.Count();
        int totalPages = Convert.ToInt32(Math.Ceiling((decimal)filteredCount / (decimal)PageSize));

        // Sort the items
        switch(sort.ToLower())
        {
            default:
                users = users.OrderBy(u => u.FirstName).ThenBy(u => u.LastName);
                break;
            case "namedesc":
                users = users.OrderByDescending(u => u.FirstName).ThenByDescending(u => u.LastName);
                break;
            case "emailasc":
                users = users.OrderBy(u => u.Email);
                break;
            case "emaildesc":
                users = users.OrderByDescending(u => u.Email);
                break;
            case "typeasc":
                users = users.OrderBy(u => u.UsertypeSortingOrder);
                break;
            case "typedesc":
                users = users.OrderByDescending(u => u.UsertypeSortingOrder);
                break;
        }

        // Apply the paging
        users = users.Skip(PageSize * (page - 1)).Take(PageSize);

        var viewModel = new UsersIndexViewModel
                        {
                            Users = users.ToList(),
                            TotalPages = totalPages
                        };

        return View(viewModel);
    }
}


Комментарии
6 ответов

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

public UsersController : Controller
{
private const int PageSize = 25;

public ActionResult Index(int page = 1, string sort = "", UserSearchViewModel search)
{
var users = GetUsers(search, sort);
var totalPages = GetTotalPages(users);

var viewModel = new UsersIndexViewModel
{
Users = users.Skip(PageSize * (page - 1)).Take(PageSize).ToList(),
TotalPages = totalPages
};

return View(viewModel);
}

private UserListItem GetUsers(UserSearchViewModel search, string sort)
{
var users = from user in context.Users
select new UserListItem
{
UserId = user.UserId,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
UsertypeId = user.UsertypeId,
UsertypeDescription = users.Usertype.Description,
UsertypeSortingOrder = users.Usertype.SortingOrder
};

users = FilterUsers(users, search);
users = SortUsers(users, sort);

return users;
}

private UserListItem SortUsers(object users, string sort)
{
switch (sort.ToLower())
{
default:
users = users.OrderBy(u => u.FirstName).ThenBy(u => u.LastName);
break;
case "namedesc":
users = users.OrderByDescending(u => u.FirstName).ThenByDescending(u => u.LastName);
break;
case "emailasc":
users = users.OrderBy(u => u.Email);
break;
case "emaildesc":
users = users.OrderByDescending(u => u.Email);
break;
case "typeasc":
users = users.OrderBy(u => u.UsertypeSortingOrder);
break;
case "typedesc":
users = users.OrderByDescending(u => u.UsertypeSortingOrder);
break;
}
return users;
}

private UserListItem FilterUsers(object users, UserSearchViewModel search)
{
if (!String.IsNullOrWhiteSpace(search.Name)) users = users.Where(u => u.FirstName.Contains(search.Name)
|| u.LastName.Contains(search.Name));
if (!String.IsNullOrWhiteSpace(search.Email)) users = users.Where(u => u.Email.Contains(search.Email));
if (search.UsertypeId.HasValue) users = users.Where(u => u.UsertypeId == search.UsertypeId.Value);
return users;
}

private int GetTotalPages(UserListItem users)
{
var filteredCount = users.Count();
return Convert.ToInt32(Math.Ceiling((decimal)filteredCount / (decimal)PageSize));
}
}

Затем вы можете выполнить рефакторинг этого дальнейшее перемещение этих методов в класс службы, если вы хотите.

15
ответ дан 19 сентября 2011 в 05:09 Источник Поделиться


Редактировать: я прошу прощения из моих предыдущих образцов, что не совсем компиляция. Я это исправил и добавил Более полный пример.

Вы можете связать друг с стратегии для изменения запроса. Каждая стратегия (называемая SearchFieldMutator в данном примере) проведет две вещи:


  1. Способ решить, если применить стратегию.

  2. Сама стратегия.

Первая часть-это делегат (типа сказуемого), которая возвращает значение true или false в зависимости от данных в UserSearchViewModel (или любой другой тип, так как он только определяет общий тип TSearch). Если он вернется, правда, эта стратегия применяется. Если она возвращает значение false, он не применяется. Это тип делегата:

Predicate<TSearch>

(это может также быть написано как Функ)

Вторая часть самой стратегии. Он должен мутировать запрос, применяя оператор LINQ к нему, а на самом деле просто возвращает новый запрос с оператора, и абонент должен отбросить старую запроса и сохранить новый. Так что это не мутация, но это имеет тот же эффект. Я создал новый тип делегата, поэтому его использование ясно:

public delegate IQueryable<TItem> QueryMutator<TItem, TSearch>(IQueryable<TItem> items, TSearch search);


Примечание: Я определил как Тип товара и поиска данных как универсальные типы (как TItem и TSearch, соответственно), поэтому этот код может использоваться в нескольких местах в коде. Но если вас это смущает, вы можете снять дженериков полностью, и заменить TItem с UserListItem и любой TSearch с UserSearchViewModel.

Теперь, когда мы определили два типа стратегии, мы можем создать класс, который держит их обоих, а также (имитация) мутации:

public class SearchFieldMutator<TItem, TSearch>
{
public Predicate<TSearch> Condition { get; set; }
public QueryMutator<TItem, TSearch> Mutator { get; set; }

public SearchFieldMutator(Predicate<TSearch> condition, QueryMutator<TItem, TSearch> mutator)
{
Condition = condition;
Mutator = mutator;
}

public IQueryable<TItem> Apply(TSearch search, IQueryable<TItem> query)
{
return Condition(search) ? Mutator(query, search) : query;
}
}

Этот класс содержит как условие, так и самой стратегии, и с помощью применить() метод, мы можем легко применить его в запрос, если условие выполняется.

Теперь мы можем пойти создать список стратегий. Мы определим место, чтобы держать их (на одном из ваших классов), так как они могут создаваться только один раз (они ведь без гражданства):

List<SearchFieldMutator<UserListItem, UserSearchViewModel>> SearchFieldMutators { get; set; }

Потом заполнить список:

SearchFieldMutators = new List<SearchFieldMutator<UserListItem, UserSearchViewModel>>
{
new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => !string.IsNullOrWhiteSpace(search.Name), (users, search) => users.Where(u => u.FirstName.Contains(search.Name) || u.LastName.Contains(search.Name))),
new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => !string.IsNullOrWhiteSpace(search.Email), (users, search) => users.Where(u => u.Email.Contains(search.Email))),
new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => search.UsertypeId.HasValue, (users, search) => users.Where(u => u.UsertypeId == search.UsertypeId.Value)),
new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => search.CurrentSort.ToLower() == "namedesc", (users, search) => users.OrderByDescending(u => u.FirstName).ThenByDescending(u => u.LastName)),
new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => search.CurrentSort.ToLower() == "emailasc", (users, search) => users.OrderBy(u => u.Email)),
// etc...
};

Тогда мы можем попробовать запустить его на запрос. Вместо реальной сущности рамок запрос, я буду использовать просто массив UserListItemS и добавить .ToQueryable() на нем. Он будет работать так же, если вы замените его реальный запрос к базе данных. Я также собираюсь создать простой поиск, ради примера:

// This is a mock EF query.
var usersQuery = new[]
{
new UserListItem { FirstName = "Allon", LastName = "Guralnek", Email = null, UsertypeId = 7 },
new UserListItem { FirstName = "Kristof", LastName = "Claes", Email = "whoknows@example.com", UsertypeId = null },
new UserListItem { FirstName = "Tugboat", LastName = "Captain", Email = "tugboat@ahoy.yarr", UsertypeId = 12 },
new UserListItem { FirstName = "kiev", LastName = null, Email = null, UsertypeId = 7 },
}.AsQueryable();

var searchModel = new UserSearchViewModel { UsertypeId = 7, CurrentSort = "NameDESC" };

Следующие На самом деле делает всю работу, он меняет запрос внутри usersQuery переменной, указанный на всех поисковых стратегий:

foreach (var searchFieldMutator in SearchFieldMutators)
usersQuery = searchFieldMutator.Apply(searchModel, usersQuery);

Вот оно! Это результат запроса:

Result of query

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

http://share.linqpad.net/7bud7o.linq

21
ответ дан 21 октября 2011 в 06:10 Источник Поделиться

Сортировка функциональность может быть реализована в более декларативный синтаксис. Сначала объявить ассоциативный словарь как закрытый член класса.

    private Dictionary<string, Func<IQueryable<UserListItem>, IQueryable<UserListItem>>> _sortAssoc = new Dictionary<string, Func<IQueryable<UserListItem>, IQueryable<UserListItem>>>(StringComparer.OrdinalIgnoreCase)
{
{ default(string), users => users.OrderBy(u => u.FirstName).ThenBy(u => u.LastName)},
{ "namedesc", users => users.OrderByDescending(u => u.FirstName).ThenByDescending(u => u.LastName)} ,
{ "emailasc", users => users.OrderBy(u => u.Email) },
{ "emaildesc", users => users.OrderByDescending(u => u.Email) },
//...
};

затем вы можете вызвать любой метод сортировки таким образом:

users = _sortAssoc.ContainsKey(sort) ? _sortAssoc[sort](users) : _sortAssoc[default(string)](users);

6
ответ дан 14 ноября 2012 в 08:11 Источник Поделиться

Если вы используете рамки сущности генератор ADO.NET для EF 4.1, вы можете написать код, как показано ниже.

кстати, чтобы создать строку сортировки". порядок personname АСК" будет написано как ниже
"он.personname АСК" - это "он" используется внутри эф.

string sortfield = "personname";
string sortdir= "asc";

IQueryable<vw_MyView> liststore= context.vw_MyView
.OrderBy("it." + sortfield + " " + sortdir)
.Where(c => c.IsActive == true
&& c.FundGroupId == fundGroupId
&& c.Type == 1
);

Я использовал это только для генератора ADO.NET эф. DbContext и для EF 4.3.1 не поддерживают эту функцию.

2
ответ дан 18 июля 2012 в 07:07 Источник Поделиться

Я бы рассмотреть возможность использования EntitySQL как заменить LINQ к объектам. С EntitySQL вы хотели добавить имя поля сортировки с заявлением. Хотя есть большой минус нет компиляции проверить время.

1
ответ дан 20 августа 2011 в 02:08 Источник Поделиться

Я написал кучу кода, как это, а...

Я читал о "динамические LINQ в" но это не решит проблему, когда вам нужно предложение orderby().методы thenby(). Единственное, что я сделала по-другому использует перечисления для названия поля и направления сортировки, который прекрасно работает в моем веб-формы приложения, с помощью ViewState, но я не уверен, как я буду поддерживать "состояние" в MVC ;)

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

0
ответ дан 20 июля 2011 в 09:07 Источник Поделиться