2011-09-30 2 views
5

Come al solito, mi rivolgo all'enorme potenza del cervello che è la base di utenti StackOverflow per risolvere un problema Lucene.NET con cui sto combattendo. Prima di tutto, sono un noob completo quando si tratta di Lucene e Lucene.NET e utilizzando i tutorial sparsi e gli snippet di codice online, ho messo insieme la soluzione seguente per il mio scenario.Lucene.net: interrogazione e utilizzo di un filtro per limitare i risultati

Lo scenario

Ho un indice della seguente struttura:

--------------------------------------------------------- 
| id | date | security |   text   | 
--------------------------------------------------------- 
| 1 | 2011-01-01 | -1-12-4- | some analyzed text here | 
--------------------------------------------------------- 
| 2 | 2011-01-01 | -11-3- | some analyzed text here | 
--------------------------------------------------------- 
| 3 | 2011-01-01 | -1- | some analyzed text here | 
--------------------------------------------------------- 

ho bisogno di essere in grado di interrogare il campo di testo, ma limitare i risultati per gli utenti che dispongono di ID ruolo specifico di.

Quello che mi è venuta per raggiungere questo obiettivo (dopo molti, molti viaggi a Google) è quello di utilizzare un "campo della sicurezza" e un filtro Lucene per limitare il set di risultati come indicato di seguito:

class SecurityFilter : Lucene.Net.Search.Filter 
{ 
    public override System.Collections.BitArray Bits(Lucene.Net.Index.IndexReader indexReader) 
    { 
     BitArray bitarray = new BitArray(indexReader.MaxDoc()); 

     for (int i = 0; i < bitarray.Length; i++) 
     { 
      if (indexReader.Document(i).Get("security").Contains("-1-")) 
      { 
       bitarray.Set(i, true); 
      } 
     } 

     return bitarray; 
    } 
} 

. .. e poi ...

Lucene.Net.Search.Sort sort = new Lucene.Net.Search.Sort(new Lucene.Net.Search.SortField("date", true)); 
Lucene.Net.Analysis.Standard.StandardAnalyzer analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29); 
Lucene.Net.Search.IndexSearcher searcher = new Lucene.Net.Search.IndexSearcher(Lucene.Net.Store.FSDirectory.Open(indexDirectory), true); 
Lucene.Net.QueryParsers.QueryParser parser = new Lucene.Net.QueryParsers.QueryParser(Lucene.Net.Util.Version.LUCENE_29, "text", analyzer); 
Lucene.Net.Search.Query query = parser.Parse("some search phrase"); 
SecurityFilter filter = new SecurityFilter(); 
Lucene.Net.Search.Hits hits = searcher.Search(query, filter, sort); 

questo funziona come previsto e sarebbe tornato solo i documenti con l'ID di di 1 e 3. Il problema è che su grandi indici di questo processo diventa molto lento.

Infine, la mia domanda ... Qualcuno là fuori ha qualche consiglio su come accelerarlo o avere una soluzione alternativa che sarebbe più efficiente di quella che ho presentato qui?

+0

È possibile modificare il formato dell'indice? – goalie7960

+0

Sì, a questo punto qualsiasi cosa può essere modificata. – nokturnal

risposta

5

Ho cambiato la mia risposta con un semplice esempio che spiega cosa intendevo nella mia risposta precedente.

Ho fatto questo rapidamente e non rispetta le migliori pratiche, ma dovrebbe darvi l'idea.

Si noti che il campo di sicurezza dovrà essere tokenizzato in modo che ciascun ID in esso contenuto sia separato, ad esempio utilizzando WhitespaceAnalyzer.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using Lucene.Net.Search; 
using Lucene.Net.Documents; 
using Lucene.Net.Index; 
using Lucene.Net.Analysis.Standard; 
using System.IO; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     public class RoleFilterCache 
     { 
      static public Dictionary<string, Filter> Cache = new Dictionary<string,Filter>(); 

      static public Filter Get(string role) 
      { 
       Filter cached = null; 
       if (!Cache.TryGetValue(role, out cached)) 
       { 
        return null; 
       } 
       return cached; 
      } 

      static public void Put(string role, Filter filter) 
      { 
       if (role != null) 
       { 
        Cache[role] = filter; 
       } 
      } 
     } 

     public class User 
     { 
      public string Username; 
      public List<string> Roles; 
     } 

     public static Filter GetFilterForUser(User u) 
     { 
      BooleanFilter userFilter = new BooleanFilter(); 
      foreach (string rolename in u.Roles) 
      { 
       // call GetFilterForRole and add to the BooleanFilter 
       userFilter.Add(
        new BooleanFilterClause(GetFilterForRole(rolename), BooleanClause.Occur.SHOULD) 
       ); 
      } 
      return userFilter; 
     } 

     public static Filter GetFilterForRole(string role) 
     { 
      Filter roleFilter = RoleFilterCache.Get(role); 
      if (roleFilter == null) 
      { 
       roleFilter = 
        // the caching wrapper filter makes it cache the BitSet per segmentreader 
        new CachingWrapperFilter(
         // builds the filter from the index and not from iterating 
         // stored doc content which is much faster 
         new QueryWrapperFilter(
          new TermQuery(
           new Term("security", role) 
          ) 
         ) 
       ); 
       // put in cache 
       RoleFilterCache.Put(role, roleFilter); 
      } 
      return roleFilter; 
     } 


     static void Main(string[] args) 
     { 
      IndexWriter iw = new IndexWriter(new FileInfo("C:\\example\\"), new StandardAnalyzer(), true); 
      Document d = new Document(); 

      Field aField = new Field("content", "", Field.Store.YES, Field.Index.ANALYZED); 
      Field securityField = new Field("security", "", Field.Store.NO, Field.Index.ANALYZED); 

      d.Add(aField); 
      d.Add(securityField); 

      aField.SetValue("Only one can see."); 
      securityField.SetValue("1"); 
      iw.AddDocument(d); 
      aField.SetValue("One and two can see."); 
      securityField.SetValue("1 2"); 
      iw.AddDocument(d); 
      aField.SetValue("One and two can see."); 
      securityField.SetValue("1 2"); 
      iw.AddDocument(d); 
      aField.SetValue("Only two can see."); 
      securityField.SetValue("2"); 
      iw.AddDocument(d); 

      iw.Close(); 

      User userone = new User() 
      { 
       Username = "User one", 
       Roles = new List<string>() 
      }; 
      userone.Roles.Add("1"); 
      User usertwo = new User() 
      { 
       Username = "User two", 
       Roles = new List<string>() 
      }; 
      usertwo.Roles.Add("2"); 
      User userthree = new User() 
      { 
       Username = "User three", 
       Roles = new List<string>() 
      }; 
      userthree.Roles.Add("1"); 
      userthree.Roles.Add("2"); 

      PhraseQuery phraseQuery = new PhraseQuery(); 
      phraseQuery.Add(new Term("content", "can")); 
      phraseQuery.Add(new Term("content", "see")); 

      IndexSearcher searcher = new IndexSearcher("C:\\example\\", true); 

      Filter securityFilter = GetFilterForUser(userone); 
      TopDocs results = searcher.Search(phraseQuery, securityFilter,25); 
      Console.WriteLine("User One Results:"); 
      foreach (var aResult in results.ScoreDocs) 
      { 
       Console.WriteLine(
        searcher.Doc(aResult.doc). 
        Get("content") 
       ); 
      } 
      Console.WriteLine("\n\n"); 

      securityFilter = GetFilterForUser(usertwo); 
      results = searcher.Search(phraseQuery, securityFilter, 25); 
      Console.WriteLine("User two Results:"); 
      foreach (var aResult in results.ScoreDocs) 
      { 
       Console.WriteLine(
        searcher.Doc(aResult.doc). 
        Get("content") 
       ); 
      } 
      Console.WriteLine("\n\n"); 

      securityFilter = GetFilterForUser(userthree); 
      results = searcher.Search(phraseQuery, securityFilter, 25); 
      Console.WriteLine("User three Results (should see everything):"); 
      foreach (var aResult in results.ScoreDocs) 
      { 
       Console.WriteLine(
        searcher.Doc(aResult.doc). 
        Get("content") 
       ); 
      } 
      Console.WriteLine("\n\n"); 
      Console.ReadKey(); 
     } 
    } 
} 
+0

+1 sull'utilizzo dei filtri memorizzati nella cache. Funzionano così bene che sono la scelta più ovvia qui. Non sono d'accordo con l'utilizzo di una query per eseguire la query di sicurezza: penso che i filtri (memorizzati nella cache) debbano essere utilizzati per tutto ciò che non si desidera ottenere e le query per le query a termine che richiedono il calcolo del punteggio. –

+0

Il problema (principale) nel codice di nokturnal non è la memorizzazione nella cache.Esamina tutti i documenti nell'indice per formare un filtro ("Contains" è anche un'altra trappola qui). –

+0

Sono leggermente confuso ma sono sicuro che ciò sia dovuto alla mia inesperienza con Lucene. Fammi vedere se ho questo ... Fondamentalmente, vorrei creare un filtro memorizzato nella cache per ogni ruolo che viene ricostruito ogni volta che l'indice viene modificato. Lo scenario sopra riportato consente di applicare più filtri memorizzati nella cache contemporaneamente (uno per ogni ruolo per cui l'utente è valido)? – nokturnal

6

Se indicizzare il campo della sicurezza come analizzato (tale che divide la stringa di protezione come 1 12 4 ...)

è possibile creare un filtro come questo

Filter filter = new QueryFilter(new TermQuery(new Term("security ", "1"))); 

o

form una domanda come some text +security:1

+0

Soluzione intrigante, domani scherzerò e ti farò sapere come va. – nokturnal

+0

@LB: questa soluzione sta funzionando bene, ma ora sta influenzando la soluzione che mi hai aiutato in questo http://stackoverflow.com/questions/7662829/lucene-net-range-queries-highlighting. C'è un modo per non avere il "1" da + sicurezza: 1 evidenziato? – nokturnal

+0

No, quindi è necessario utilizzare la soluzione basata su filtro –