Есть ли способ повторно использовать оператор LINQ, но изменить свойство и метод, вызываемый для этого свойства? - PullRequest
1 голос
/ 23 апреля 2020

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

public static void Main()
{
    IEnumerable<Person> dataSet = new List<Person>
    {
        new Person{ ID = "1", PrimaryName = "Prim", SecondaryName = "Sec"},
        new Person{ ID = "2", PrimaryName = "test", SecondaryName = "Sec2"},
        new Person{ ID = "3", PrimaryName = "test", SecondaryName = "Sec3"}
    };
    string attribute = "LastName";
    OperatorValue queryOperator = OperatorValue.Equal;
    string value = "test";

    switch (attribute)
    {
        case ("LastName"):
            if (queryOperator.Equals(OperatorValue.Equal))
            {
                dataSet = dataSet.Where(p => p.PrimaryName.Equals(value));
            }
            else if (queryOperator.Equals(OperatorValue.NotEquals))
            {
                dataSet = dataSet.Where(p => !p.PrimaryName.Equals(value));
            }
            else if (queryOperator.Equals(OperatorValue.StartsWith))
            {
                dataSet = dataSet.Where(p => p.PrimaryName.StartsWith(value));
            }
            else
            {
                dataSet = dataSet.Where(p => p.PrimaryName.Contains(value));
            }
            break;
        case ("FirstName"):
            if (queryOperator.Equals(OperatorValue.Equal))
            {
                dataSet = dataSet.Where(p => p.SecondaryName.Equals(value));
            }
            else if (queryOperator.Equals(OperatorValue.NotEquals))
            {
                dataSet = dataSet.Where(p => !p.SecondaryName.Equals(value));
            }
            else if (queryOperator.Equals(OperatorValue.StartsWith))
            {
                dataSet = dataSet.Where(p => p.SecondaryName.StartsWith(value));
            }
            else
            {
                dataSet = dataSet.Where(p => p.SecondaryName.Contains(value));
            }
            break;
        case ("ID"):
            if (queryOperator.Equals(OperatorValue.Equal))
            {
                dataSet = dataSet.Where(p => p.ID.Equals(value));
            }
            else if (queryOperator.Equals(OperatorValue.NotEquals))
            {
                dataSet = dataSet.Where(p => !p.ID.Equals(value));
            }
            else if (queryOperator.Equals(OperatorValue.StartsWith))
            {
                dataSet = dataSet.Where(p => p.ID.StartsWith(value));
            }
            else
            {
                dataSet = dataSet.Where(p => p.ID.Contains(value));
            }
            break;
    }

    foreach (Person person in dataSet)
        Console.WriteLine(person.ID);
}

public enum OperatorValue
{
    Equal,
    NotEquals,
    StartsWith
}

public class Person
{
    public string ID { get; set; }
    public string SecondaryName { get; set; }
    public string PrimaryName { get; set; }
}

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

Ответы [ 2 ]

4 голосов
/ 23 апреля 2020

Таким образом, значение attribute определяет, какое свойство вы хотите выбрать. По-видимому, все эти свойства являются строковыми свойствами.

При значении operatorValue вы хотите выбрать действие с выбранным строковым свойством: равно, неравно, содержит и т. Д. c.

Мне кажется, что у вас есть enum OperatorValue, что-то вроде этого:

enum OperatorValue
{
    Equals,
    NotEquals,
    StartsWith,
    Contains,
}

Я напишу методы расширения, аналогичные уже существующим Queryable.Where . См. методы расширения, демистифицированные

Преобразование строки queryOperator в OperatorValue:

public static OperatorValue ToOperatorValue(this string queryOperator)
{
    // TODO: exception if input null
    return Enum.Parse(typeof(OperatorValue), queryOperator);

    // TODO: decide what to do if queryOperator has not existing enum value
}

Использование:

string queryOperatorTxt = ...
OperatorValue operator = queryOperatorTxt.ToOperatorValue();

Конечно, мы также нужен метод для преобразования строкового атрибута в propertySelector:

public static Expression<Func<Person, string>> ToPropertySelector(this string attributeTxt)
{
    // TODO: check incorrect parameters
    Expression<Func<Person, string>> propertySelector;
    switch (attributeTxt)
    {
        case "LastName":
           propertySelector = (person) => person.PrimaryName;
           break;
        case "FirstName":
           propertySelector = (person) => person.SecondaryName;
           break;
        ... etc

        default:
            // TODO: decide what to do with unknown attributeTxt
    }
}

использование:

string attributeTxt = ...
Expression<Func<Person, string>> propertySelector = attributeTxt.ToPropertySelector();

Теперь, когда мы знаем, как преобразовать queryOperator и атрибут, мы можем создать расширение для the Where:

public static IQueryable<Person> Where(
    this IQueryable<Person> source,
    Expression<Func<Person, string>> propertySelector,
    OperatorValue operator,
    string value)
{
    // TODO: exceptions if incorrect parameters
    switch (operator)
    {
        case OperatorValue.Equals:
            return source.Where(item => propertySelector(item) == value);

        case OperatorValue.NotEquals:
            return source.Where(item => propertySelector(item) != value);

        case OperatorValue.StartsWith:
            return source.Where(item => propertySelector(item).StartsWith(value);

        case OperatorValue.Contains:
            return source.Where(item => propertySelector(item).Contains(value);

        default:
           // TODO
    }
}

Соберите все вместе:

IQueryable<Person> dataSet = db.Persons;
string attributeTxt = ...
string queryOperatorTxt = ...
string value = ...

Expression<Func<Person, string>> propertySelector = attributeTxt.ToPropertySelector();
OperatorValue operation = queryOperatorTxt.ToOperatorValue();

IQueryable<Person> queryPersons = dataSet.Where(propertySelector, operation);

Ну, разве это не похоже на аккуратный оператор LINQ!

1 голос
/ 23 апреля 2020

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

public static void Main()
{
    IEnumerable<Person> dataSet = new List<Person>
    {
        new Person{ ID = "1", PrimaryName = "Prim", SecondaryName = "Sec"},
        new Person{ ID = "2", PrimaryName = "test", SecondaryName = "Sec2"},
        new Person{ ID = "3", PrimaryName = "test", SecondaryName = "Sec3"}
    };
    string attribute = "LastName";
    OperatorValue queryOperator = OperatorValue.Equal;
    string value = "test";

    Func<Person, string> getter = GetFuncForProperty(attribute);
    dataSet = Filter(dataSet, getter, queryOperator, value);

    foreach (Person person in dataSet)
        Console.WriteLine(person.ID);
}

public static IEnumerable<Person> Filter(IEnumerable<Person> source, Func<Person, string> getter, OperatorValue operatorValue, string searchValue)
{
    switch (operatorValue)
    {
        case OperatorValue.Equal:
            return source.Where(p => getter.Invoke(p).Equals(searchValue));
        case OperatorValue.NotEquals:
            return source.Where(p => !getter.Invoke(p).Equals(searchValue));
        case OperatorValue.StartsWith:
            return source.Where(p => getter.Invoke(p).StartsWith(searchValue));
    }

    throw new ArgumentException(operatorValue.ToString() + " is not supported");
}

public static Func<Person, string> GetFuncForProperty(string propertyName)
{
    switch (propertyName)
    {
        case "ID":
            return (Person person) => person.ID;
        case "FirstName":
            return (Person person) => person.SecondaryName;
        case "LastName":
            return (Person person) => person.PrimaryName;
    }

    throw new ArgumentException(propertyName + " is not supported");
}

public enum OperatorValue
{
    Equal,
    NotEquals,
    StartsWith
}

public class Person
{
    public string ID { get; set; }
    public string SecondaryName { get; set; }
    public string PrimaryName { get; set; }
}

Посмотреть его на: https://dotnetfiddle.net/MAqvZZ

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