2011-01-08 14 views
5

Así que quiero buscar dentro de una tabla de clientes todos los clientes que en cada uno tengan su nombre, dirección de correo electrónico o números de teléfono que coincidan con todos las palabras clave de consultaUsando una tupla o algún otro tipo complejo en una expresión de consulta Linq-to-Entities

... que es probablemente más fácil de entender en el código que en Inglés:

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

Pero no puedo llamar PhoneNumber.Pack en la base de datos, por lo que necesito para hacer w un formato que almacenará tanto la valor bruto de w, así como el valor ed Pack, y tengo que hacer eso en el lado del cliente. El problema es que a Linq no le gusta tener tuplas o matrices en los argumentos de expresión, y no admite String.IndexOf, por lo que no puedo lanzar dos cadenas en una y luego tomar subseries.

¿Alguna otra forma de evitar esto? ¿O tal vez una reformulación de la consulta?

Editar: El SQL generado es el siguiente:

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) 
)) 
+0

Por cierto, ¿cómo obtuviste el SQL generado? Profiler? –

+0

http://anjlab.com/en/projects/opensource/sqlprofiler –

Respuesta

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

Esto producirá el resultado deseado, y la variable de temperatura en el interior foreach resolverá el problema.

+0

Bueno, eso es mucho más fácil de lo que lo estaba haciendo. ¡Gracias! Ojalá pudiera dividir la recompensa. –

1

que crearía una estructura privada:

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

a continuación, cambiar la condición:

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; 

Investigué más sobre esto, y creo que no vas a hacer esto como está. El problema es que, debido a ws.All, quiere crear un conjunto de cláusulas SQL una vez para cada valor en la secuencia ws. Necesita que sea una secuencia de tipos primitivos, como una cadena.

Si pudiera cambiar su código para tener dos parámetros de consulta, entonces creo que podría funcionar. Necesitará un conjunto de parámetros para las cosas que no necesitan embalaje y otra para las que sí lo necesitan. Luego, podría cambiar esto en una cadena de métodos LINQ y hacer una unión entre los dos. Ejemplo a seguir.


Funcionó. Mi código está abajo. Tenga en cuenta que utilicé la base de datos AdventureWorks2008R2, así que la mía es un poco más complicada que la suya: tengo una colección de direcciones de correo electrónico y de teléfonos con los que lidiar; un partido en cualquiera de los se acepta:

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

Tenga en cuenta también que encontré another way to get trace output:

IQueryable<Person> query = SearchCustomers(entities, "w1 w2", 
              "(602) (408)"); 
var oc = (ObjectQuery<Person>) query; 
Console.WriteLine(oc.ToTraceString()); 
+1

Bleh, mismo error: 'System.NotSupportedException: no se puede crear un valor constante de tipo 'SearchWord'. En este contexto solo se admiten tipos primitivos ('como Int32, String y Guid'). –

+0

@Rei: ya sabes, tiene sentido. Pregúntate cómo harías en SQL simple. ¿No tendrías que aprobar la lista de palabras como una tabla, incluso si no tuvieras el problema con 'Pack'? De hecho, ¿funciona la consulta si compara los números de teléfono directamente a 'w'? –

+1

Sí, tiene sentido, pero debe haber una consulta que haga lo mismo. 'Pack' simplemente elimina los caracteres no alfanuméricos para que los números se comparen de manera consistente. Es algo así como necesario. –

0

Me dividirlo en 2 métodos:

  • SearchCustomer
  • SearchCustomerPhoneNumber

En SearchCustomerPhoneNumber convierte el parámetro a empaquetado antes de realizar la consulta.

Dado que el número de teléfono no contendrá las letras y las demás lo harán, es posible verificar cuál de los métodos se debe ejecutar. La división realmente reducirá la carga en la base de datos.

+0

Los números de teléfono pueden contener letras, p. 1-800-flowers –

+0

Entonces probablemente tendrías que ejecutar ambos métodos cada vez, la dispersión todavía te daría un programa mucho más simple –

1

Tenga en cuenta que query.Where(a).Where(b) es lo mismo que query.Where(a & b) y qry.All() es esencialmente adoptando una serie de condiciones y encadenar AND declaraciones, algo así como (word 1 is found) && (word 2 is found) && (word 3 is found) ...

Usted puede utilizar que permite hacer lo siguiente (Estoy usando métodos de extensión, para poder encadenar esto al final de cualquier otro 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; 
    } 

o VB equivalente

<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 
+0

No estoy seguro de por qué nunca lo noté ... ¡gracias! –

Cuestiones relacionadas