2010-11-17 12 views
5

Ho un'applicazione che consente di cercare una determinata entità in base a diversi criteri (da qualche parte nell'ordine di 20 metodi diversi in totale). Voglio essere in grado di combinare i risultati di diverse ricerche al fine di produrre un singolo set di risultati.Strategia generale per ricerche complesse a più stadi

Ad esempio:

results = (entities from search 1 AND entities from search 2) OR (entities from search 3) 

Supponiamo che le ricerche sono abbastanza complessi di natura tale che la loro combinazione in una singola query logica non è possibile (a causa di relazioni complesse che necessitano di essere interrogato, eccetera).

Supponiamo inoltre che il numero di entità coinvolte (probabile) renda impossibile qualsiasi tipo di strategia in memoria.

miei pensieri iniziali erano qualcosa sulla falsariga di:

1) Effettuare le ricerche separatamente, ottenere un elenco di corrispondenza "id entità" da ciascuno di essi, e quindi eseguire un "livello root" di ricerca in base su questi.

Ad esempio:

select * from entity e 
where 
(e.Id in (search 1 id list) AND e.Id in(search 2 id list)) 
OR e.Id in (search 3 id list) 

2) Eseguire una query esterna che seleziona l'entità sulla base dei risultati restituiti dai miei subquery (completo).

Ad esempio:

select * from entity e 
where (e.Id in (select e1.id from entity e1 where ...) AND e.Id in (select e2.id from entity e2 where...)) 
OR e.Id in (select e3.id from entity e3 where...) 

Ovviamente questi esempi sono drasticamente semplificate a scopo illustrativo; le singole query saranno molto più coinvolte e la loro combinazione sarà arbitraria (ho appena illustrato un esempio rappresentativo qui).

Sarei molto interessato a ricevere suggerimenti su come gli altri hanno gestito questa situazione. Sono certamente aperto a qualsiasi possibilità che non ho esplorato sopra.

Per riferimento, questa è un'applicazione .NET che utilizza un ORM NHibernate supportato da un database SQL Server 2008 R2.

Ho già deciso di utilizzare hql o sql nativo per questo come ICriteria o Linq non forniscono la flessibilità necessaria per l'esecuzione delle singole query né le operazioni di combinazione richieste.

risposta

2

Ho fatto questo mantenendo i contatori delle prestazioni di ricerca in una tabella. Monitoraggio basico della percentuale media di righe filtrate dalla ricerca e del tempo di esecuzione.

ho quindi creare una figura prestazioni sulla base TotalNumberOfRowsToSearch * Percent_Not_Matched/RunTimeInSeconds Questa figura è una correlazione diretta di righe al secondo può filtrare. Mediato su migliaia di corse, è una previsione piuttosto buona.

Quindi eseguo ogni query in ordine con la massima prestazione una prima.

Se si sta eseguendo un AND logico sul risultato totale, eseguire ogni query successiva solo sui risultati della query precedente.

Se si sta eseguendo un OR logico, eseguire ogni query successiva solo sui risultati NON nei risultati di ricerca precedenti combinati.

In questo modo, la query cambierà in base agli indici e ai tipi di dati.

Se si desidera una soluzione meno dinamica, è sufficiente calcolare i dati relativi alle prestazioni per ciascuna parte della ricerca e utilizzare prima quelli con prestazioni migliori. Ricorda che una query eseguita in 55 ms ma corrisponde al 99% dei risultati non è utile come quella eseguita in 1 secondo e corrisponde all'1% dei risultati, quindi fai attenzione che i risultati potrebbero andare contro le tue idee iniziali.

Basta osservare l'errore di divisione per 0 quando si calcolano i dati relativi alle prestazioni.

+0

Grazie per intervenire, questo è molto istruttivo da un aspetto di prestazione (che è una considerazione importante ovviamente). – DanP

0

Se è possibile utilizzare ICriteria, lo consiglio. Può ridurre drasticamente la quantità di codice con ricerche complesse. Ad esempio, la differenza tra l'utilizzo di una ricerca da solo e l'utilizzo come subquery nella ricerca aggregata sarebbe una proiezione aggiuntiva.

Non ho ancora provato a dividere le ricerche complesse e a eseguirle separatamente. Combinando l'intera ricerca in un'unica chiamata al database, come nel tuo secondo esempio, finora ha funzionato per me. Se non ottengo un tempo di risposta decente (minuti anziché secondi), l'Ottimizzazione guidata motore di database si è dimostrata inestimabile con indici e statistiche suggeriti.

+0

In generale, sarei completamente d'accordo con questo ... sfortunatamente ho bisogno di eseguire qualche sql magic a causa dell'ereditarietà complessa usata nel modello - quindi in effetti è vantaggioso usare sql raw in alcuni punti (e probabilmente più performante dal momento che posso evitare inutili join, ecc.) – DanP

2

Il mio approccio con Linq è creare un elenco di espressioni in cui costruire i criteri complessi e applicarli alla fine.

Qualcosa del genere:

List<Expression<Func<WorkItem, bool>>> whereExpressions = new List<Expression<Func<WorkItem, bool>>>(); 
if (!string.IsNullOrEmpty(searchMask)) 
      { 
       whereExpressions.Add(
             x => 
             (x.Name.ToLower().IndexOf(searchMask.ToLower()) > -1 || 
             x.Id.ToString().IndexOf(searchMask) > -1 || 
             (x.Description != null && 
              x.Description.ToLower().IndexOf(searchMask.ToLower()) > -1))); 
      } 

whereExpressions.Add(x => (x.Status == status)); 

Alla fine, dopo la costruzione della lista espressione si applicano le espressioni:

IQueryable<WorkItem> result = Session.Linq<WorkItem>(); 
foreach (Expression<Func<WorkItem, bool>> whereExpression in whereExpressions) 
      { 
       result = result.Where(whereExpression); 
      } 

È possibile anche fornire flessibilità nel metodo di ordinamento e consentire il paging:

IQueryable<WorkItem> items; 
      if (ascOrDesc == "asc") 
      { 
       items = result.OrderBy(DecideSelector(indexer)).Skip(startPoint - 1).Take(numOfrows); 
      } 
      else 
      { 
       items = result.OrderByDescending(DecideSelector(indexer)).Skip(startPoint - 1).Take(numOfrows); 
      } 

Dove DecideSelector è definito in questo modo:

private Expression<Func<WorkItem, object>> DecideSelector(string fieldCode) 
     { 
      switch (fieldCode) 
      { 
       case "Deadline": 
        return item => item.Deadline; 
       case "name": 
        return item => item.Name; 
       case "WiStatus": 
        return item => item.Status; 
       case "WiAssignTo": 
        return item => item.AssignedUser; 
       default: 
        return item => item.Id; 
      } 
     } 
+0

Questo è quello che faccio anche io di solito; sfortunatamente le funzionalità linq di nhibernate non sono sufficienti per le mie esigenze (quando dico ricerche "complicate", intendo:)) – DanP

Problemi correlati