Фильтрация данных с несколькими условиями if - PullRequest
4 голосов
/ 25 апреля 2020

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

    public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string conditionName)
    {
        var values = await GetProducts();

        IOrderedEnumerable<Product> dataSorted =null;
        if (conditionName == "TitleASC")
        {
            dataSorted = values.OrderBy(c => c.Title);
        }

        if (conditionName == "TitleDESC")
        {
            dataSorted = values.OrderByDescending(c => c.CategoriesTitle);
        }

        if (conditionName == "DateTimeASC")
        {
            dataSorted =values.OrderBy(c => c.DateTime);
        }

        if (conditionName == "DateTimeDESC")
        {
            dataSorted = values.OrderByDescending(c => c.DateTime);
        }

        if (conditionName == "CategoryASC")
        {
            dataSorted = values.OrderBy(c => c.CategoriesTitle);
        }
        if (conditionName == "CategoryDESC")
        {
            dataSorted = values.OrderByDescending(c => c.CategoriesTitle);
        }

        return Ok(dataSorted);
    }

Ответы [ 7 ]

3 голосов
/ 25 апреля 2020

вы можете использовать System.Linq.Dynami c .Core ;

 int i = conditionName.Contains("DESC") ? conditionName.IndexOf("DESC") : conditionName.IndexOf("ASC");
 //exp will be like Title desc
 var exp = conditionName.Substring(0, i) + " " + conditionName.Substring(i);
 var result = dataSorted.Orderby(exp).Tolist();

Linq.Dynami c позволяет express LINQ-запросов с использованием методов расширения, которые принимать строковые аргументы.

3 голосов
/ 25 апреля 2020

Важный отказ от ответственности

В компьютерной науке всегда есть всегда .

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

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

Зачем использовать Если каскад является проблемой

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

Короче говоря, идея состоит в том, что код, подобный тому, который вы показали, подвержен постоянным изменениям, потому что каждый раз, когда вы вводите новое условие (или удаляете существующее), вы должны изменить существующий код путем добавления (или удаления) нового оператора if. Это нежелательный сценарий, потому что изменение существующего кода, который работает, может привести к ошибкам и является дорогостоящей операцией с точки зрения времени и направленности. В желаемом сценарии есть фрагмент кода, который отлично работает, хорошо протестирован и может оставаться стабильным в течение времени. Чтобы выполнить новые требования, мы хотели бы написать новые модули (например, новые классы), не затрагивая ни один из существующих и работающих модулей.

Является ли решение каскада if оператором switch решением?

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

Разработка шаблонов для спасения.

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

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

// define an interface encapsualting the desired behavior

IProductsProvider 
{
  bool CanHandleCondition(string condition);
  Task<IEnumerable<Products>> GetProducts(string condition);
}

// write an implementation of your interface for each possible condition that you have

class TitleAscendingProvider : IProductsProvider 
{
  private readonly IApiClient _apiClient;

  public TitleAscendingProvider(IApiClient apiClient)
  {
    _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
  }

  public bool CanHandleCondition(string condition) => condition == "TitleASC";

  public async Task<IEnumerable<Products>> GetProducts(string condition)
  {
    var products = await _apiClient.GetProducts();
    return products.OrderBy(c => c.Title);
  }
}

class TitleDescendingProvider : IProductsProvider 
{
  private readonly IApiClient _apiClient;

  public TitleAscendingProvider(IApiClient apiClient)
  {
    _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
  }

  public bool CanHandleCondition(string condition) => condition == "TitleDESC";

  public async Task<IEnumerable<Products>> GetProducts(string condition)
  {
    var products = await _apiClient.GetProducts();
    return products.OrderByDescending(c => c.CategoriesTitle);
  }
}

// this is the implementation of the interface that you will register with your DI container
// inject all the other implementations of the IProductsProvider interface

class CompositeProvider : IProductsProvider 
{
  private readonly IProductsProvider[] _providers;

  public TitleAscendingProvider(IEnumerable<IProductsProvider> providers)
  {
    if (providers is null) 
    {
      throw new ArgumentNullException(nameof(providers));
    }

    _providers = providers.ToArray();
  }

  public bool CanHandleCondition(string condition) => _providers.Any(p => p.CanHandleCondition(condition));

  public Task<IEnumerable<Products>> GetProducts(string condition)
  {
    var provider = _providers.FirstOrDefault(p => p.CanHandleCondition(condition));
    if (provider == null) 
    {
      throw new InvalidOperationException("Unable to find a proper provider for the condition '{condition}'")
    }

    return provider.GetProducts(condition);
  }
}

// remember to register the class CompositeProvider as the implementation of the interface IProductsProvider in the DI container
// let the DI container to inject the implementation of IProductsProvider in your controller

private readonly IProductsProvider _provider;

public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string conditionName)
{
    var products = await _provider.GetProducts(conditionName);
    return Ok(products);
}

Другой возможный подход, основанный на деревьях выражений

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

Основная идея этого подхода заключается в том, чтобы запросить у пользователя название поля, которое он хочет использовать для сортировки товаров, и направление сортировки (по возрастанию или по убыванию). Затем вы можете построить дерево выражений, представляющее некоторый код, который, учитывая экземпляр класса продукта, получает доступ к свойству, которое должно использоваться для сортировки продуктов. Наконец, вы можете скомпилировать дерево выражений, чтобы получить экземпляр делегата, который вы можете передать в метод расширения LINQ to object OrderBy.

Это решение не в полной мере применимо к вашей проблеме , потому что оно требует вызова метода generi c, и вы не можете вывести аргумент типа generi c из входных данных, поступающих из пользователь (имя свойства, используемого для сортировки продуктов), но я думаю, что деревья выражений стоит упомянуть здесь. Считайте эту часть моего ответа триггером для дальнейших исследований в мире деревьев выражений.

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

static class Program
  {
    public static void Main(string[] args)
    {
      var people = new Person[]
      {
        new Person { Name = "Bob", Age = 11 },
        new Person { Name = "Alice", Age = 8 },
        new Person { Name = "Tony", Age = 1 }
      };

      //imagine this comes from the user...
      var propertyName = nameof(Person.Age);          

      // the issue is that here you need to pass the right generic type argument and based on my understanding you can't infer it from the propertyName variable
      var expression = GetPropertyExpression<int>(propertyName);
      var func = expression.Compile();

      var sorted = people.OrderBy(func).ToArray();
    }

    private static Expression<Func<Person, T>> GetPropertyExpression<T>(string propertyName)
    {
      var parameter = Expression.Parameter(typeof(Person), "model");
      var property = Expression.Property(parameter, propertyName);
      var expression = Expression.Lambda<Func<Person, T>>(property, parameter);
      return expression;
    }

    public class Person 
    {
      public string Name { get; set; }
      public int  Age { get; set; }
    }
  }

Любой член сообщества, который лучше меня понимает в дереве выражений topi c, может улучшить эту идею или объяснить, если и почему деревья выражений не подходят для сценария этого вопроса.

Здесь вы можете найти другой пример построения динамического c запроса с использованием дерева выражений.

Правка 26 апреля 2020 года

Как указано Из других ответов на те же вопросы стоит усилий, чтобы взглянуть на библиотеку System.Linq.Dynami c .Core , которая позволяет создавать динамические c запросы на основе текста.

Это, пожалуй, самый безопасный способ использовать деревья выражений для решения вашей проблемы. Если вы не являетесь экспертом в области дерева выражений, всегда лучше использовать надлежащие библиотеки и избегать производства с домашними решениями для «бедняков».

3 голосов
/ 25 апреля 2020

Вы можете упростить свое решение так:

public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string 
conditionName)
{
    var values = await GetProducts();
    IOrderedEnumerable<Product> dataSorted =null;

    switch (conditionName)
    {
        case "TitleASC":
            dataSorted = values.OrderBy(c => c.Title);
            break;
        case "TitleDESC":
            dataSorted = values.OrderByDescending(c => c.CategoriesTitle);
            break;
        case "DateTimeASC":
            dataSorted =values.OrderBy(c => c.DateTime).ThenBy(c => c.CategoriesTitle);
            break;
        case "DateTimeDESC":
            dataSorted = values.OrderByDescending(c => c.DateTime).ThenByDescending(c => c.CategoriesTitle);
            break;
        default:
            // if you want you can add default order here
            break;
    }
    return Ok(dataSorted);
}
1 голос
/ 25 апреля 2020

А как же выражение переключателя ?

public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string conditionName)
{
    var values = await GetProducts();

    IOrderedEnumerable<Product> dataSorted = conditionName switch
            {
                "TitleASC"     => values.OrderBy(c => c.Title),
                "TitleDESC"    => values.OrderByDescending(c => c.CategoriesTitle),
                "DateTimeASC"  => values.OrderBy(c => c.DateTime),
                "DateTimeDESC" => values.OrderByDescending(c => c.DateTime),
                "CategoryASC"  => values.OrderBy(c => c.CategoriesTitle),
                "CategoryDESC" => values.OrderByDescending(c => c.CategoriesTitle),
                _              => null
            };

    return Ok(dataSorted);
}
0 голосов
/ 25 апреля 2020

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

// Helper methods:
public static class LinqExtensions
{ 
    public static IEnumerable<TSource> OrderByFlag<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool descending = false)
    {
        return descending
            ? source.OrderByDescending(keySelector)
            : source.OrderBy(keySelector);
    } 
}

// Then, in the controller:
public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string conditionName, bool desc = false)
{
    var products = await GetProducts();

    switch (conditionName)
    {
        case "Title":
            products = products.OrderByFlag(p => p.Title, desc);
            break;             

        case "DateTime":
            products = products.OrderByFlag(p => p.DateTime, desc);
            break;

        case "Category":
            products = products.OrderByFlag(p => p.CategoriesTitle, desc);
            break;

        default:
            throw new ArgumentException(nameof(conditionName), "Unknown sorting criteria");
    }

    return Ok(products);
}
0 голосов
/ 25 апреля 2020

Один из вариантов - абстрагирование функциональности. Что-то вроде ...

public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string conditionName)
    {
        var values = await GetProducts();

        IOrderedEnumerable<Product> dataSorted =null;

        val dataSorted = SortFunction(conditionName,values)
    }

И функция SortFunction делает логи c. В этой функции используйте оператор Case / switch для различных опций. Вы можете использовать оператор switch, предложенный выше

0 голосов
/ 25 апреля 2020

Здесь могут помочь оператор switch или выражение switch . и сделайте ваш код более читабельным.

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