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

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

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

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

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);
    }
}
32 голоса | спросил Kristof Claes 20 J000000Wednesday11 2011, 16:40:05

7 ответов


15

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

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));
    }
}

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

ответил shuniar 19 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowMon, 19 Sep 2011 21:06:52 +0400 2011, 21:06:52
20
  

РЕДАКТИРОВАТЬ: Прошу прощения за предыдущий образец, который не совсем скомпилирован. Я исправил его и добавил более полный пример.

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

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

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

false

(он также может быть записан как Predicate<TSearch> )

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

Func<TSearch, bool>
  

ПРИМЕЧАНИЕ. Я определил как тип элемента, так и данные поиска как общие типы (как 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; } } мы можем легко применить его к запросу, если условие выполнено.

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

Apply()

Затем мы запишем список:

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

Затем мы можем попытаться запустить его в запросе. Вместо фактического запроса Entity Framework я собираюсь использовать простой массив 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... }; s и добавить к нему UserListItem. Он будет работать так же, если вы замените его фактическим запросом базы данных. Я также собираюсь создать простой поиск для примера:

.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 = "[email protected]", UsertypeId = null }, new UserListItem { FirstName = "Tugboat", LastName = "Captain", Email = "[email protected]", UsertypeId = 12 }, new UserListItem { FirstName = "kiev", LastName = null, Email = null, UsertypeId = 7 }, }.AsQueryable(); var searchModel = new UserSearchViewModel { UsertypeId = 7, CurrentSort = "NameDESC" }; на тот, который указан всеми стратегиями поиска:

usersQuery

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

 Результат запроса

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

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

ответил Allon Guralnek 21 +04002011-10-21T22:19:04+04:00312011bEurope/MoscowFri, 21 Oct 2011 22:19:04 +0400 2011, 22:19:04
6

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

    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);
ответил Danil 14 32012vEurope/Moscow11bEurope/MoscowWed, 14 Nov 2012 12:43:24 +0400 2012, 12:43:24
2

Если вы использовали ADO.NET Entity Framework Generator для EF 4.1, вы можете написать свой код, как показано ниже.

способ построить строку сортировки. «order by personname asc» будет написан следующим образом: «it.personname asc» - « it » используется внутри EF.

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
                                );

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

ответил DAEMYO 18 J000000Wednesday12 2012, 11:10:37
1

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

ответил Eugene Wechsler 20 PM00000060000000431 2011, 18:57:04
0

Я написал TON такого кода, как это ...

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

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

ответил Mike Curtis 21 J000000Thursday11 2011, 01:51:38
0

Другая опция:

  • Создайте объект домена, который принимает параметры, необходимые для фильтра
  • Создайте функцию в репозитории, которая принимает объект домена , а внутри этой функции репозитория обрабатывает фильтрацию /сортировку /подкачку
  • Вызов этой функции из ваших верхних уровней (будь то бизнес-уровень /служба или контроллер MVC), проходящий в объекте фильтр

Преимущества:

  1. Если вы хотите увеличить фильтр, вам нужно добавить только к объекту домена
  2. И измените репозиторий для обработки этого нового фильтра
  3. В любом месте, где вы хотите получить этот набор данных, вызывающему абоненту необходимо заполнить фильтр по мере необходимости в этой ситуации

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

ответил Michael 25 Mayam15 2015, 11:13:31

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132