Использование кортежа или другого сложного типа в выражении запроса Linq-to-Entities - PullRequest
5 голосов
/ 09 января 2011

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

... Что, вероятно, прощечтобы понять в коде, чем в английском:

public IQueryable<Contact> SearchCustomers(string query)
{
    var ws = from w in query.Split()
                where !String.IsNullOrWhiteSpace(w)
                select w;

    var q =
        from c in Customers
        where ws.All(w =>
                c.FirstName == w
                || c.LastName == w
                || c.EmailAddress == w
                || c.HomePhone == PhoneNumber.Pack(w)
                || c.CellPhone == PhoneNumber.Pack(w))
        select c;

    return q;
}

Но я не могу вызвать PhoneNumber.Pack для базы данных, поэтому мне нужно сделать w формат, который будет хранить оба необработанных значения w а также Pack ed значение, и я должен сделать это на стороне клиента.Проблема в том, что Linq не нравится иметь кортежи или массивы в аргументах выражения, и он не поддерживает String.IndexOf, поэтому я не могу выбросить две строки в одну и затем взять подстроки.другие способы обойти это?Или, может быть, повторение запроса?

Редактировать: Сгенерированный SQL выглядит так:

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName], 
(etc)
FROM [dbo].[Contacts] AS [Extent1]
WHERE ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    WHERE ( NOT ([Extent1].[FirstName] = N'rei' OR [Extent1].[LastName] = N'rei' OR [Extent1].[EmailAddress] = N'rei' OR [Extent1].[HomePhone] = N'rei' OR [Extent1].[CellPhone] = N'rei')) OR (CASE WHEN ([Extent1].[FirstName] = N'rei' OR [Extent1].[LastName] = N'rei' OR [Extent1].[EmailAddress] = N'rei' OR [Extent1].[HomePhone] = N'rei' OR [Extent1].[CellPhone] = N'rei') THEN cast(1 as bit) WHEN ( NOT ([Extent1].[FirstName] = N'rei' OR [Extent1].[LastName] = N'rei' OR [Extent1].[EmailAddress] = N'rei' OR [Extent1].[HomePhone] = N'rei' OR [Extent1].[CellPhone] = N'rei')) THEN cast(0 as bit) END IS NULL)
))

Ответы [ 4 ]

3 голосов
/ 27 мая 2011
public IQueryable<Contact> SearchCustomers(string query)
{
    var ws = from w in query.Split()
                where !String.IsNullOrWhiteSpace(w)
                select new { Unpacked = w , Packed = PhoneNumber.Pack(w) };

    var q = Customers;
    foreach(var x in ws)
    {
        string ux = x.Unpacked;
        string px = x.Packed;
        q = q.Where(
               c=> 
                c.FirstName == ux
                || c.LastName == ux
                || c.EmailAddress == ux
                || c.HomePhone == px
                || c.CellPhone == px
            );
    }
    return q;
}

Это даст желаемый результат, а временная переменная внутри foreach решит вашу проблему.

1 голос
/ 24 мая 2011

Обратите внимание, что query.Where(a).Where(b) - это то же самое, что и query.Where(a & b), а qry.All () по существу принимает ряд условий и объединяет AND операторов, что-то вроде (word 1 is found) && (word 2 is found) && (word 3 is found) ...

Вы можете использовать это, чтобы сделать следующее (я использую методы расширения, чтобы я мог связать это в конце любого другого IQueryable<Customer>).

    [System.Runtime.CompilerServices.Extension()]
    public static IQueryable<Customer> Search(this IQueryable<Customer> query, string searchTerm)
    {
        string[] queryWords = searchTerm.Split(" ");

        foreach (string w in queryWords) {
            string word = w;
            string packedWord = Pack(word);

            query = query.Where(c => c.FirstName == word || c.LastName == word || c.HomePhone == packedWord || c.CellPhone == packedWord);
        }
        return query;
    }

Или VB эквивалент

<System.Runtime.CompilerServices.Extension()>
Public Function Search(query As IQueryable(Of Customer), searchTerm As String) As IQueryable(Of Customer)
    Dim queryWords = searchTerm.Split(" ")

    For Each w In queryWords
        Dim word = w
        Dim packedWord = Pack(word)

        query = query.Where(Function(c) c.FirstName = word OrElse
                                c.LastName = word OrElse
                                c.HomePhone = packedWord OrElse
                                c.CellPhone = packedWord)
    Next
    Return query
End Function
1 голос
/ 09 января 2011

Я бы создал приватную структуру:

private struct UnpackedAndPacked
{
    public string Unpacked {get;set;}
    public string Packed {get;set;}
}

var ws = from w in query.Split()
         where !String.IsNullOrWhiteSpace(w)
         select new UnpackedAndPacked
                    {
                        Unpacked=w, 
                        Packed=PhoneNumber.Pack(w)
                    };  

Затем изменил бы условие:

    where ws.All(w => 
                 c.FirstName == w.Unpacked
                  || c.LastName == w.Unpacked
                  || c.EmailAddress == w.Unpacked
                  || c.HomePhone == w.Packed
                  || c.CellPhone == w.Packed)
    select c;

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

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


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

public static IQueryable<Person> SearchCustomers(
    AdventureWorksEntities entities, string nameQuery, string phoneQuery)
{
    var wsu = from w in nameQuery.Split()
        where !String.IsNullOrWhiteSpace(w)
        select w;
    var wsp = from w in phoneQuery.Split()
        where !String.IsNullOrWhiteSpace(w)
        select Pack(w);
    return
        entities.People.Where(
            c => wsu.All(w => c.FirstName == w || c.LastName == w)).
            Union(
                entities.People.Where(
                    c =>
                    wsp.All(
                        w =>
                        c.PersonPhones.Any(p => p.PhoneNumber == w) ||
                        c.EmailAddresses.Any(a => a.EmailAddress1 == w))));
}

Обратите также внимание, что я нашел другой способ получения трассировки :

IQueryable<Person> query = SearchCustomers(entities, "w1 w2",
                                           "(602) (408)");
var oc = (ObjectQuery<Person>) query;
Console.WriteLine(oc.ToTraceString());
0 голосов
/ 24 мая 2011

Я бы разделил его на 2 метода:

  • SearchCustomer
  • SearchCustomerPhoneNumber

В SearchCustomerPhoneNumber вы преобразуете параметр в упакованный перед выполнением запроса.

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

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