Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Il programmatore al lavoro

"NoSQL" grazie a MongoDB, parte 2

Ted Neward

Download del codice di esempio

Ted NewardNel mio articolo precedente , nozioni fondamentali del MongoDB impiegato anteriore e centro: recupero di installarlo, esecuzione e inserimento e ricerca dei dati. Tuttavia, trattate le nozioni di base, ovvero gli oggetti di dati utilizzati sono coppie nome/valore semplice. Che apportate senso, perché “ sweet campione di MongoDB ” include strutture di dati non strutturati e relativamente semplici. Ma sicuramente questo database può archiviare coppie nome/valore più così semplice.

In questo articolo utilizzeremo un metodo leggermente diverso per indagare MongoDB (o qualsiasi tecnologia). La routine denominata test un'esplorazione consentirà noi trovare un possibile bug nel server e selezionare uno dei problemi comuni orientata agli sviluppatori verranno eseguita in quando utilizzano MongoDB lungo il percorso.

Nei nostri Last Episode …

Innanzitutto Ci accerteremo che siamo tutti sulla stessa pagina, e verranno inoltre illustrate alcune nuova leggermente terreno. Osservare Let’s MongoDB in modo un po' più strutturato rispetto nell'articolo precedente (msdn.microsoft.com/magazine/ee310029). Piuttosto che semplicemente creare una semplice applicazione e modificare su di esso, let’s kill due uccelli con uno-Pietra e creare test di esplorazione, segmenti di codice che è simile a unit test, ma che esplorare funzionalità anziché tentare di verificarla.

Scrivere test di esplorazione ha scopi diversi durante l'analisi di una nuova tecnologia. Uno, consentono di rilevare se la tecnologia in esame è intrinsecamente verificabile (partendo dal presupposto che se è difficile test di esplorazione, esso sarà difficile unit test, ovvero un enorme contrassegno rosso). Due, vengono utilizzati come una sorta di regressione quando una nuova versione della tecnologia in esame è disponibile, poiché forniscono un heads-up se la funzionalità precedente. E tre, poiché il test dovrebbero essere relativamente piccoli e granulare, esplorazione test intrinsecamente apprendere una tecnologia semplice creando nuovi casi “ What-If ” basate su casi precedenti.

Ma a differenza di unit test, test non vengono continuamente sviluppati a fianco dell'applicazione di esplorazione così quando si considera la tecnologia acquisita, riservare i test. Non eliminare, tuttavia, consente inoltre di separare i bug nel codice dell'applicazione da quelle della libreria o framework. I test di farlo, fornendo un ambiente leggero applicazione indipendente per sperimentazione senza l'overhead dell'applicazione.

Con quello presente, creare let’s MongoDB-esplorare, un progetto di test Visual C#. Aggiungere all'elenco dei riferimenti ad assembly e generazione per assicurarsi che tutto ciò che è opportuno passare MongoDB.Driver.dll. (Generazione dovrebbe riprendere il uno TestMethod viene generato come parte del modello di progetto. Passerà per impostazione predefinita, tutti i dati dovrebbero essere buoni, che significa che se il progetto non riesce a generare, qualcosa è filettato nell'ambiente. Verifica di ipotesi è sempre una cosa positiva.)

Come tempting come sarebbe passare subito la scrittura di codice, tuttavia, un problema di replica piuttosto rapidamente: MongoDB è necessario che il processo server esterno (mongod.exe) sia in esecuzione prima codice client può connettersi a fronte di tale ed eseguire operazioni utili. È bello semplicemente dire “ Motta, bene, let’s avviarlo e tornare alla scrittura di codice ”, c'è un problema corollary. È una scommessa quasi certi che a un certo punto non 15 settimane nuovamente esaminando il codice, alcuni sviluppatori scarsa (si, me o un collega) cercherà di eseguire questi test, vedere quando tutti e perdere due o tre giorni cercando di scoprire cosa sta succedendo prima che riflette verificare se il server di esecuzione.

Lezione: Tentare di acquisire tutte le dipendenze nei test in qualche modo. Il problema si verifica durante il nuovo unit test, comunque. A questo punto sarà necessario avviare da un normale server, apportare alcune modifiche e annullare tutti. È più semplice eseguire semplicemente l'arresto e avvio del server, pertanto risolvere ora il consente di risparmiare tempo in seguito.

Questa idea dell'esecuzione di un'operazione prima del test (o dopo, o entrambi) non è nuovo e Microsoft Test e gestione del laboratorio progetti possono avere inizializzatori di verifica e per--suite di test e i metodi di pulitura. Questi sono che tramite attributi personalizzati ClassInitialize e ClassCleanup di operazioni per--suite di test e TestInitialize TestCleanup per operazioni di verifica. Per ulteriori informazioni, vedere “ utilizzo di unit test ”. Pertanto, un inizializzatore per a-suite di test verrà avviato il processo mongod.exe e pulizia per--suite di test verrà arrestato il processo, come illustrato in di Figura 1.

Figura 1 parziale del codice per l'inizializzatore di test e pulitura

namespace MongoDB_Explore
{
  [TestClass]
  public class UnitTest1
  {
    private static Process serverProcess;

   [ClassInitialize]
   public static void MyClassInitialize(TestContext testContext)
   {
     DirectoryInfo projectRoot = 
       new DirectoryInfo(testContext.TestDir).Parent.Parent;
     var mongodbbindir = 
       projectRoot.Parent.GetDirectories("mongodb-bin")[0];
     var mongod = 
       mongodbbindir.GetFiles("mongod.exe")[0];

     var psi = new ProcessStartInfo
     {
       FileName = mongod.FullName,
       Arguments = "--config mongo.config",
       WorkingDirectory = mongodbbindir.FullName
     };

     serverProcess = Process.Start(psi);
   }
   [ClassCleanup]
   public static void MyClassCleanup()
   {
     serverProcess.CloseMainWindow();
     serverProcess.WaitForExit(5 * 1000);
     if (!serverProcess.HasExited)
       serverProcess.Kill();
  }
...

La prima volta che si esegue una finestra di dialogo verrà visualizzata che informa l'utente che avvia il processo. Scegliendo OK renderà la finestra di dialogo chiusa... viene eseguito fino al successivo del test. Una volta ottiene troppo fastidiosa finestra di dialogo, individuare la casella di opzione indicante, “ non mostrare questa finestra di dialogo Nuovo ” e controllare per rendere il messaggio scompare per buona. Se è in esecuzione software firewall, ad esempio Windows Firewall, la finestra di dialogo probabilmente renderà qui un aspetto, inoltre, perché il server desidera aprire una porta per ricevere le connessioni client. Applicare la stessa modalità di gestione e tutto ciò che deve essere eseguita automaticamente. Inserire un punto di interruzione sulla prima riga del codice di pulitura per verificare che il server è in esecuzione, se lo si desidera.

Una volta che il server è in esecuzione, test possono avviare attivazione, tranne le superfici di un altro problema: Ogni test desidera lavorare con il proprio database aggiornato, ma è utile per alcuni dati preesistenti per effettuare test di determinati elementi del database (query, ad esempio) semplice. Sarebbe bello se ogni test potrebbe avere proprie aggiornata dei dati preesistenti. Che sarà il ruolo dei metodi che TestInitializer e TestCleanup.

Ma prima di iniziare a, let’s esaminare questo rapido TestMethod, che tenta di garantire che il server è possibile trovare, una connessione e un oggetto inserito, trovato e rimosso, per portare l'esplorazione test veloce con trattati nell'articolo precedente (vedere di Figura 2).

Figura 2 TestMethod per assicurarsi che il server può essere trovato e una connessione creato

[TestMethod]
public void ConnectInsertAndRemove()
{
  Mongo db = new Mongo();
  db.Connect();

  Document ted = new Document();
  ted["firstname"] = "Ted";
  ted["lastname"] = "Neward";
  ted["age"] = 39;
  ted["birthday"] = new DateTime(1971, 2, 7);
  db["exploretests"]["readwrites"].Insert(ted);
  Assert.IsNotNull(ted["_id"]);

  Document result =
    db["exploretests"]["readwrites"].FindOne(
    new Document().Append("lastname", "Neward"));
  Assert.AreEqual(ted["firstname"], result["firstname"]);
  Assert.AreEqual(ted["lastname"], result["lastname"]);
  Assert.AreEqual(ted["age"], result["age"]);
  Assert.AreEqual(ted["birthday"], result["birthday"]);

  db.Disconnect();
}

Se questo codice viene eseguito, esso viaggi un'asserzione e il test ha esito negativo. In particolare, viene generato l'ultima asserzione intorno “ compleanno ”. In modo apparentemente l'invio di un valore DateTime nel database MongoDB senza una volta non round trip abbastanza correttamente. Il tipo di dati va una data con un tempo di mezzanotte associato ma viene restituita come una data con un tempo di 8 del mattino, che interrompe l'asserzione AreEqual alla fine del test associato.

Questo evidenzia l'utilità di test di esplorazione, senza di esso (come avviene, ad esempio con il codice nell'articolo precedente), questa caratteristica MongoDB poco potrebbe avere andato inosservata fino a settimane o mesi nel progetto. Se si tratta di un bug nel server MongoDB è un giudizio di valore e non un elemento esaminato subito. Il punto è che il test di esplorazione posizionare la tecnologia in microscope, aiutare a isolare il problema “ interessante ”. Che consente agli sviluppatori che intendono per utilizzare la tecnologia marca le proprie decisioni come a se si tratta di una modifica sostanziale. Uomo è forearmed.

Correggere il codice in modo che il test viene superato, tra l'altro, richiede DateTime che viene restituita dal database per essere convertita in ora locale. Questo portato in un forum in linea e in base alla risposta dall'autore del MongoDB.Driver Corder Sam, “ tutte le date in vengono convertite nell'ora UTC ma sinistro come UTC proveniente. ” Pertanto è necessario sia convertire DateTime in ora UTC prima di passarli tramite DateTime.ToUniversalTime oppure convertire qualsiasi DateTime recuperati dal database per il fuso orario locale tramite DateTime.ToLocalTime, utilizzando il seguente codice di esempio:

Assert.AreEqual(ted["birthday"], 
  ((DateTime)result["birthday"]).ToLocalTime());

Uno dei grandi vantaggi di comunità sforzi in sé evidenzia, le entità coinvolte sono in genere solo un messaggio di posta immediatamente.

Aggiunta di complessità

Gli sviluppatori che desiderano utilizzare MongoDB necessario comprendere che, contrariamente all'aspetto iniziale, non è un oggetto database, che non è possibile gestire arbitrariamente complessa di oggetti grafici senza supporto. Esistono alcune convenzioni di affrontare i modi per fornire tale Guida, ma finora in questo rimane così in ritengono dello sviluppatore.

Si consideri ad esempio Figura 3 , un semplice insieme di oggetti progettati per riflettere l'archiviazione di un numero di documenti che descrivono una famiglia conosciuta. Finora in modo efficace. Infatti, mentre è in esso, il test realmente deve query al database per gli oggetti inseriti, come illustrato in di Figura 4, per assicurarsi che siano recuperabili. E … il test viene superato. Awesome.

Figura 3 di un insieme di oggetti semplici

[TestMethod]
public void StoreAndCountFamily()
{
  Mongo db = new Mongo();
  db.Connect();

  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  db["exploretests"]["familyguy"].Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  db.Disconnect();
}

Figura 4 query al database per gli oggetti

[TestMethod]
public void StoreAndCountFamily()
{
  Mongo db = new Mongo();
  db.Connect();

  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  db["exploretests"]["familyguy"].Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  ICursor griffins =
    db["exploretests"]["familyguy"].Find(
      new Document().Append("lastname", "Griffin"));
  int count = 0;
  foreach (var d in griffins.Documents) count++;
  Assert.AreEqual(2, count);

  db.Disconnect();
}

In realtà, che potrebbe non essere completamente true, lettori di seguito a casa e digitare il codice potrebbe risultare che il test non passa dopo tutto, come afferma che il numero previsto di oggetti non corrispondenti 2. Poiché i database sono previste si, questo mantiene lo stato tra le chiamate e, poiché il codice del test non rimozione esplicitamente tali oggetti, rimangono in test.

Consente di evidenziare un'altra funzionalità del database orientati ai documenti: I duplicati vengono completamente previsto e consentiti. Che è perché ogni documento, una volta inseriti, è contrassegnato con l'attributo implicit_id e assegnato un identificatore univoco per l'archiviazione, che diventa in effetti, di chiave primaria del documento.

In questo modo, se i test si intende passare, il database deve essere cancellato prima di eseguire ciascun test. Mentre è piuttosto semplice eliminare solo i file nella directory in cui MongoDB li memorizza, nuovamente, questa viene eseguita automaticamente come parte della suite di test è preferibile notevolmente. Ogni test può eseguire questa operazione manualmente dopo il completamento, quale potrebbe essere un po' noioso nel tempo. O il codice del test può sfruttare la funzione TestInitialize e TestCleanup Microsoft Test e gestione del laboratorio per registrare il codice comune (e perché non includono il database connettere e disconnettere logica), come illustrato in di Figura 5.

Figura 5 elenco Advantage TestInitialize e TestCleanup

private Mongo db;

[TestInitialize]
public void DatabaseConnect()
{
  db = new Mongo();
  db.Connect();
}
        
[TestCleanup]
public void CleanDatabase()
{
  db["exploretests"].MetaData.DropDatabase();

  db.Disconnect();
  db = null;
}

Anche se l'ultima riga del metodo CleanDatabase non è necessaria perché il test successivo sovrascriverà il riferimento di campo con un nuovo Mongo oggetto, a volte che è preferibile rendere deselezionare che il riferimento non è più valido. Avvertenza emptor. È importante che cade il database scritto test, svuotare i file che MongoDB viene utilizzata per archiviare i dati e lasciando tutto nuovo e sparkly parziale per il test successivo.

Evidenzia le cose, tuttavia, il modello della famiglia è incompleto, due persone a cui fa riferimento sono due e dato che, deve avere un riferimento a altro come spouses, come illustrato di seguito:

peter["spouse"] = lois;
  lois["spouse"] = peter;

L'esecuzione di questo test, tuttavia, produce una StackOverflowException — serializzatore MongoDB driver nativi non comprensione la nozione di circolare fa riferimento e naively segue i riferimenti intorno ad infinitum . Ehi. Non valido.

Questa correzione è necessario scegliere una delle due opzioni. Con uno, il campo Nome coniuge può essere popolato con campo _id del documento (una volta che tale documento è stato inserito) e aggiornati, come illustrato in di Figura 6.

Figura 6 superare il problema riferimenti circolari

[TestMethod]
public void StoreAndCountFamily()
{
  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  var fg = db["exploretests"]["familyguy"];
  fg.Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  peter["spouse"] = lois["_id"];
  fg.Update(peter);
  lois["spouse"] = peter["_id"];
  fg.Update(lois);

  Assert.AreEqual(peter["spouse"], lois["_id"]);
  TestContext.WriteLine("peter: {0}", peter.ToString());
  TestContext.WriteLine("lois: {0}", lois.ToString());
  Assert.AreEqual(
    fg.FindOne(new Document().Append("_id",
    peter["spouse"])).ToString(),
    lois.ToString());

  ICursor griffins =
    fg.Find(new Document().Append("lastname", "Griffin"));
  int count = 0;
  foreach (var d in griffins.Documents) count++;
  Assert.AreEqual(2, count);
}

C'è tuttavia uno svantaggio: l'approccio: Richiede che i documenti di essere inseriti nel database e i relativi valori _id (che sono istanze di Oid, nella terminologia MongoDB.Driver) essere copiati nei campi coniuge di ogni oggetto. Ciascun documento viene aggiornata in nuovamente. Sebbene sia andata e ritorno al database MongoDB è veloci rispetto a quelli con un aggiornamento RDBMS tradizionale, questo metodo è ancora piuttosto sprechi.

Un secondo approccio è pregenerare valori Oid per ogni documento, compilare i campi Nome coniuge e quindi inviare l'intero batch nel database, come illustrato in di Figura 7.

Figura 7 di Un modo migliore per risolvere il problema di riferimenti circolari

[TestMethod]
public void StoreAndCountFamilyWithOid()
{
  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";
  peter["_id"] = Oid.NewOid();

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";
  lois["_id"] = Oid.NewOid();

  peter["spouse"] = lois["_id"];
  lois["spouse"] = peter["_id"];

  var cast = new[] { peter, lois };
  var fg = db["exploretests"]["familyguy"];
  fg.Insert(cast);

  Assert.AreEqual(peter["spouse"], lois["_id"]);
  Assert.AreEqual(
    fg.FindOne(new Document().Append("_id",
    peter["spouse"])).ToString(),
    lois.ToString());

  Assert.AreEqual(2, 
    fg.Count(new Document().Append("lastname", "Griffin")));
}

Questo approccio richiede solo il metodo Insert, poiché ora sono noti i valori di Oid anticipo. Comunque, si noti che le chiamate ToString sul test di asserzione sono intenzionali, in questo modo, i documenti vengono convertiti in stringhe prima del confronto.

Ciò che è veramente importante notare sul codice in di Figura 7, tuttavia, è che de-referencing il documento fa riferimento tramite l'OID può essere relativamente difficile e noioso perché lo stile di documento presuppone che i documenti siano più o meno entità autonome o gerarchica, non un grafico di oggetti. (Notare che il driver .NET fornisce DBRef, che offre un metodo leggermente più ricco di riferimento o la rimozione di un altro documento, ma comunque sarà non renderla un oggetto grafico facile sistema). Pertanto, sebbene sia certamente possibile un modello oggetti completo e memorizzarlo in un database MongoDB, non è consigliabile . Stick strettamente archiviazione cluster gruppi di dati utilizzando i documenti di Word o Excel come una metafora Guida. Se un oggetto può essere considerato come un foglio di calcolo o documenti di grandi dimensioni, è probabilmente adeguato MongoDB o alcuni altri database orientati ai documenti.

Per visualizzare

Ci abbiamo terminato l'indagine di MongoDB, ma prima si concludere, vi sono alcune cose più per esplorare, tra cui eseguire query predicativa, aggregazioni, supporto LINQ e alcune note di amministrazione di produzione. È necessario affrontare mese successivo. (Articolo sarà una parte molto occupata!) Nel frattempo, esaminare il sistema MongoDB e assicurarsi di eliminare automaticamente messaggi di posta elettronica con i suggerimenti per le colonne successive.

Ted Neward  è un'entità con Neward & Associates, un'impresa indipendenti ed è specializzato in sistemi piattaforma .NET Framework e Java enterprise. Ha scritto più di 100 articoli, è un MVP C#, relatore di INETA autore e coautore di libri di una dozzina, compresa la prossima “ Professional F # 2.0 ” (Wrox). Egli consulta e mentors regolarmente. Contattarlo all' ted@tedneward.com e leggere il suo blog all'indirizzo blogs.tedneward.com.

Grazie all'esperto di tecnica seguente per la revisione di questo articolo: Sam Corder