Метод LINQ с различными параметрами - PullRequest
1 голос
/ 07 января 2010

У меня есть метод LINQ для страницы поиска в собственном приложении. Метод выглядит следующим образом

    public static DataTable SearchForPerson(String FirstName, String MiddleName, String LastName, String SSN, DateTime? BirthDate)
    {
        var persons = (from person in context.tblPersons 
                       where person.LastName == LastName || person.LastName.StartsWith(LastName)
                       join addresse in context.tblAddresses on person.PersonID equals addresse.PersonID 
                       orderby person.LastName
                       select new { person.PersonID, person.LastName, person.FirstName, person.SSN, addresse.AddressLine1 });

        var filteredPersonsList = persons.Where(p => p.LastName == LastName).ToList();
        if (filteredPersonsList.Count == 0)
            filteredPersonsList = persons.Where(p => p.LastName.StartsWith(LastName)).ToList();

        var dataTable = filteredPersonsList.CopyLinqToDataTable();



        return dataTable;
    }

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

Итак, наконец, на мой вопрос; Является ли более желательным ( лучший метод чтения, более эффективный и т. Д ... ) иметь один такой метод с механизмом ( Я имею в виду ПЕРЕКЛЮЧАТЕЛЬ на непустом параметре ) чтобы указать, какой параметр нужно искать или я должен просто сделать несколько версий, например SearchForPersonByLastName & SearchForPersonBySSN?

Кроме того, есть ли еще более изящное решение этой, на мой взгляд, распространенной проблемы?

Ответы [ 6 ]

2 голосов
/ 07 января 2010

Если я правильно понял ваш вопрос, вы пытаетесь добавить другие параметры в предложение where вашего запроса. Могу ли я предложить:

var persons = (from person in context.tblPersons 
                       where (!string.IsNullOrEmpty(LastName) && (person.LastName == LastName || person.LastName.StartsWith(LastName))) && 
                       (!string.IsNullOrEmpty(SSN) && (person.SSN == SSN)) // && etc as needed
                       join addresse in context.tblAddresses on person.PersonID equals addresse.PersonID 
                       orderby person.LastName
                       select new { person.PersonID, person.LastName, person.FirstName, person.SSN, addresse.AddressLine1 });

Это позволит вам передать любую комбинацию параметров для фильтрации, чтобы вы не были привязаны к фильтрации по одному параметру.

2 голосов
/ 07 января 2010

Правильно ли я понимаю, что для поиска будет использоваться только один из параметров? Если так, то это абсолютно разные методы. Каждый раз, когда вы описываете метод (или класс и т. Д.), Используя слова «и» или «или», вы, вероятно, имеете метод, который можно разбить на несколько методов. Похоже, этот метод в настоящее время описывается как «этот метод ищет Person s по FirstName или MiddleName или LastName или SSN или BirthDate». Итак, напишите методы

SearchByFirstName
SearchByMiddleName
SearchByLastName
SearchBySSN
SearchByBirthDate

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

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

Edit:

Хорошо, вы говорите, что можете искать по нескольким параметрам. Я по-прежнему настоятельно предпочитаю идею отдельных методов для каждого параметра (лучшее разделение задач, простота обслуживания, простота тестирования и т. Д.) Вот один из способов связать их всех вместе:

DataTable Search(
    string firstName,
    string middleName,
    string lastName,
    string ssn,
    DateTime? birthdate
) {
    IQueryable<Person> query = context.tblPersons;
    if(SearchParameterIsValid(firstName)) {
        query = SearchByFirstName(query, firstName);
    }
    if(SearchParameterIsValid(middleName)) {
        query = SearchByMiddleName(query, middleName);
    }
    if(SearchParameterIsValid(lastName)) {
        query = SearchByLastName(query, lastName);
    }
    if(SearchParameterIsValid(ssn)) {
        query = SearchBySSN(query, ssn);
    }
    if(birthDate != null) {
        query = SearchByBirthDate(query, birthDate);
    }

    // fill up and return DataTable from query
}

bool SearchParameterIsValid(string s) {
    return !String.IsNullOrEmpty(s);
}

IQueryable<Person> SearchByFirstName(
    IQueryable<Person> source
    string firstName
) {
    return from p in source
           where p.FirstName == firstName || p.FirstName.StartsWith(firstName)
           select p;
}

// etc.

Или:

DataTable Search(
    string firstName,
    string middleName,
    string lastName,
    string ssn,
    DateTime? birthdate
) {
    Predicate<Person> predicate = p => true;
    if(SearchParameterIsValid(firstName)) {
        predicate = PredicateAnd(predicate, FirstNamePredicate(firstName));
    }
    if(SearchParameterIsValid(middleName)) {
        predicate = PredicateAnd(predicate, MiddleNamePredicate(middleName));
    }
    // etc.
}

Predicate<T> PredicateAnd<T>(Predicate<T> first, Predicate<T> second) {
    return t => first(t) && second(t);
}

Predicate<Person> FirstNamePredicate(string firstName) {
    return p => p.FirstName == firstName || p.FirstName.StartsWith(firstName);
}

// etc.

DataTable SearchByPredicate(
    IQueryable<Person> source,
    Predicate<Person> predicate
) {
    var query = source.Where(predicate)
                      .Join(
                          context.tblAddresses,
                              p => p.PersonID,
                              a => a.PersonID,
                              (p, a) => new {
                                  p.PersonID,
                                  p.LastName,
                                  p.FirstName,
                                  p.SSN,
                                  a.AddressLine1
                              }
                          );

    return query.CopyLinqToDataTable();
}
1 голос
/ 07 января 2010

Может потребоваться создать объект для отражения человека, а затем добавить к нему метод фильтра:

Person.AddFilter (fieldToLimit, оператор, значение)

Таким образом, вы можете добавить к объекту любое количество критериев фильтрации.

Пример:

Person.AddFilter (FirstName, Содержит "Bob"); Person.AddFilter (LastName, StartsWith, "Z");

Другой способ - просто добавить свои критерии к типу данных Linq to SQL IQueryable, чтобы вы могли просто:

Person.Where (t => t.FirstName.Contains ("Bob")). Where (t => t.LastName.StartsWith ("Z"));

1 голос
/ 07 января 2010

Один метод у вас подойдет.

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

Я бы также упростил только StartsWith.

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

var persons = from person in context.tblPersons  
    select person; 
if (!string.IsNullOrEmpty(FirstName))
    persons = from person in persons 
        where person.FirstName.StartsWith(FirstName) 
        select person;
if (!string.IsNullOrEmpty(MiddleName))
    persons = from person in persons 
        where person.MiddleName.StartsWith(MiddleName) 
        select person;
if (!string.IsNullOrEmpty(LastName))
    persons = from person in persons 
        where person.LastName.StartsWith(LastName) 
        select person;
if (!string.IsNullOrEmpty(SSN))
    persons = from person in persons 
        where person.SSN = SSN 
        select person;
if (BirthDate.HasValue)
    persons = from person in persons 
        where person.BirthDate == BirthDate.Value 
        select person;
return (from person in persons 
    join address in context.tblAddresses  
    on person.PersonID equals address.PersonID  
    orderby person.LastName        
    select new { person.PersonID, person.LastName,
        person.FirstName, person.SSN,  address.AddressLine1 })  
    .ToList()
    .CopyLinqToDataTable();
1 голос
/ 07 января 2010

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

var filteredPersonsList = persons
    .Where(p => FirstName != null && p.FirstName == FirstName)
    .Where(p => MiddleName != null && p.MiddleName == MiddleName)
    .Where(p => LastName != null && p.LastName == LastName).ToList();

Чтобы вызывающий мог указать:

var matches = SearchForPerson("firstName", null, "lastName", "SSN", dob);

Чтобы игнорировать второе имя в поиске.

Обратите внимание, что вы можете объединить эти предложения в один, используя &&, хотя это может быть затруднительно для чтения.

1 голос
/ 07 января 2010

Намерение было бы намного более ясным с несколькими методами.

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

Но несколько методов покажут мне ТОЧНО, что вы пытаетесь сделать.

Как сказал Джейсон, обязательно выделите общий код во вспомогательный метод. Я бы не хотел видеть один и тот же (более или менее) запрос linq в каждом методе.

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