Looking for ravendb Answers? Try Ask4KnowledgeBase
Looking for ravendb Keywords? Try Ask4Keywords

ravendbIniziare con Ravendb


Osservazioni

Questa sezione fornisce una panoramica di ciò che è Ravendb e perché uno sviluppatore potrebbe volerlo usare.

Dovrebbe anche menzionare qualsiasi argomento di grandi dimensioni all'interno di ravendb e collegarsi agli argomenti correlati. Poiché la documentazione di ravendb è nuova, potrebbe essere necessario creare versioni iniziali di tali argomenti correlati.

Una semplice applicazione per console RavenDB

Per questo esempio useremo l' istanza RavenDB di Live Test .

Costruiremo qui una semplice app per console che dimostra le operazioni di base:

  • Creazione
  • Recupero per ID
  • Interrogazione
  • In aggiornamento
  • cancellazione

Inizia creando una nuova soluzione di Visual Studio e aggiungendo un progetto di applicazione della console. Chiamiamolo RavenDBDemoConsole. L'utilizzo della libreria client dovrebbe essere simile se il Reader utilizza VS Code o il suo editor preferito.

Successivamente, dobbiamo aggiungere i riferimenti richiesti. Fare clic con il tasto destro del mouse sul nodo Riferimenti nel riquadro Esplora soluzioni e selezionare Gestisci pacchetti NuGet. Sfoglia online per 'RavenDb.Client'. Userò l'ultima versione stabile, che è - al momento in cui scrivo - 3.5.2.

Scriviamo un po 'di codice, vero? Inizia aggiungendo le seguenti istruzioni using:

using Raven.Client;
using Raven.Client.Document;
 

Questi ci consentono di utilizzare IDocumentStore e DocumentStore di IDocumentStore , che è un'interfaccia e un'implementazione pronta all'uso per connettersi a un'istanza RavenDB. Questo è l'oggetto di livello superiore che è necessario utilizzare per connettersi a un server e la documentazione di RavenDB ne consiglia l'utilizzo come un singleton nell'applicazione.

Quindi andremo avanti e ne creeremo uno, ma per semplicità non implementeremo il singleton wrapper attorno ad esso - lo smaltiremo solo quando il programma verrà chiuso in modo che la connessione venga chiusa in modo pulito. Aggiungi il seguente codice al tuo metodo principale:

using (IDocumentStore store = new DocumentStore
{
    Url = "http://live-test.ravendb.net",
    DefaultDatabase = "Pets"
})
{
    store.Initialize();
}
 

Come detto all'inizio, usiamo l'istanza RavenDB di Live Test, usiamo il suo indirizzo come proprietà Url di DocumentStore . Specifichiamo anche un nome di database predefinito, in questo caso, "Animali domestici". Se il database non esiste ancora, RavenDB lo crea quando prova ad accedervi. Se esiste, allora il client può usare quello esistente. Dobbiamo chiamare il metodo Initialize() modo che possiamo iniziare a operare su di esso.

In questa semplice applicazione manterremo proprietari e animali domestici. Pensiamo alla loro connessione in quanto un proprietario può avere un numero arbitrario di animali domestici, ma un animale domestico può avere un solo proprietario. Anche se nel mondo reale, un animale domestico potrebbe avere un numero arbitrario di proprietari, ad esempio un marito e una moglie, opteremo per questa ipotesi in quanto le relazioni molti a molti in un database di documenti sono gestite in modo leggermente diverso da quelle di un database relazionale e merita un argomento a parte. Ho scelto questo dominio perché è abbastanza comune da comprendere.

Quindi dovremmo ora definire i nostri tipi di oggetti di dominio:

public class Owner
{
    public Owner()
    {
        Pets = new List<Pet>();
    }

    public string Id { get; set; }
    public string Name { get; set; }
    public List<Pet> Pets { get; set; }

    public override string ToString()
    {
        return
            "Owner's Id: " + Id + "\n" +
            "Owner's name: " + Name + "\n" +
            "Pets:\n\t" +
            string.Join("\n\t", Pets.Select(p => p.ToString()));
    }
}

public class Pet
{
    public string Color { get; set; }
    public string Name { get; set; }
    public string Race { get; set; }

    public override string ToString()
    {
        return string.Format("{0}, a {1} {2}", Name, Color, Race);
    }
}
 

Ci sono alcune cose da notare qui:

In primo luogo, i nostri Owner possono contenere zero o più Pet s. Si noti che la classe Owner ha una proprietà denominata Id mentre la classe Pet non lo fa. Questo perché gli oggetti Pet verranno memorizzati all'interno degli oggetti Owner , il che è abbastanza diverso da come questo tipo di relazione verrebbe implementata in un database relazionale.

Si potrebbe obiettare che questo non dovrebbe essere implementato in questo modo - e potrebbe essere giusto, in realtà dipende dai requisiti. Come regola generale, se un Pet ha senso esistere senza un Owner non dovrebbe essere incorporato ma esistere da solo con un proprio identificatore. Nella nostra applicazione, assumiamo che un Pet sia considerato un animale domestico solo se ha un proprietario, altrimenti sarebbe un animale o una bestia. Per questo motivo, non aggiungiamo una proprietà Id alla classe Pet .

In secondo luogo, si noti che l'identificatore della classe del proprietario è una stringa poiché viene generalmente mostrata negli esempi nella documentazione di RavenDB. Molti sviluppatori abituati ai database relazionali potrebbero considerare questa una cattiva pratica, che di solito ha senso nel mondo relazionale. Ma poiché RavenDB usa Lucene.Net per svolgere i suoi compiti e perché Lucene.Net è specializzata nel funzionamento con stringhe, qui è perfettamente accettabile - inoltre, abbiamo a che fare con un database di documenti che memorizza JSON e, dopotutto, praticamente tutto è rappresentato come una stringa in JSON.

Un'altra cosa da notare sulla proprietà Id è che non è obbligatorio. In effetti, RavenDB allega i propri metadati su qualsiasi documento che salviamo, quindi anche se non lo definiamo, RavenDB non avrebbe problemi con i nostri oggetti. Tuttavia, è generalmente definito per un accesso più facile.

Prima di vedere come possiamo utilizzare RavenDB dal nostro codice, definiamo alcuni metodi di supporto comuni. Questi dovrebbero essere auto-esplicativi.

// Returns the entered string if it is not empty, otherwise, keeps asking for it.
private static string ReadNotEmptyString(string message)
{
    Console.WriteLine(message);
    string res;
    do
    {
        res = Console.ReadLine().Trim();
        if (res == string.Empty)
        {
            Console.WriteLine("Entered value cannot be empty.");
        }
    } while (res == string.Empty);

    return res;
}

// Will use this to prevent text from being cleared before we've read it.
private static void PressAnyKeyToContinue()
{
    Console.WriteLine();
    Console.WriteLine("Press any key to continue.");
    Console.ReadKey();
}

// Prepends the 'owners/' prefix to the id if it is not present (more on it later)
private static string NormalizeOwnerId(string id)
{
    if (!id.ToLower().StartsWith("owners/"))
    {
        id = "owners/" + id;
    }

    return id;
}

// Displays the menu
private static void DisplayMenu()
{
    Console.WriteLine("Select a command");
    Console.WriteLine("C - Create an owner with pets");
    Console.WriteLine("G - Get an owner with its pets by Owner Id");
    Console.WriteLine("N - Query owners whose name starts with...");
    Console.WriteLine("P - Query owners who have a pet whose name starts with...");
    Console.WriteLine("R - Rename an owner by Id");
    Console.WriteLine("D - Delete an owner by Id");
    Console.WriteLine();
}
 

E il nostro metodo principale:

private static void Main(string[] args)
{
    using (IDocumentStore store = new DocumentStore
    {
        Url = "http://live-test.ravendb.net", 
        DefaultDatabase = "Pets"
    })
    {
        store.Initialize();

        string command;
        do
        {
            Console.Clear();
            DisplayMenu();

            command = Console.ReadLine().ToUpper();
            switch (command)
            {
                case "C":
                    Creation(store);
                    break;
                case "G":
                    GetOwnerById(store);
                    break;
                case "N":
                    QueryOwnersByName(store);
                    break;
                case "P":
                    QueryOwnersByPetsName(store);
                    break;
                case "R":
                    RenameOwnerById(store);
                    break;
                case "D":
                    DeleteOwnerById(store);
                    break;
                case "Q":
                    break;
                default:
                    Console.WriteLine("Unknown command.");
                    break;
            }
        } while (command != "Q");
    }
}
 

Creazione

Vediamo come possiamo salvare alcuni oggetti in RavenDB. Definiamo i seguenti metodi comuni:

private static Owner CreateOwner()
{
    string name = ReadNotEmptyString("Enter the owner's name.");

    return new Owner { Name = name };
}

private static Pet CreatePet()
{
    string name = ReadNotEmptyString("Enter the name of the pet.");
    string race = ReadNotEmptyString("Enter the race of the pet.");
    string color = ReadNotEmptyString("Enter the color of the pet.");

    return new Pet
    {
        Color = color,
        Race = race,
        Name = name
    };
}

private static void Creation(IDocumentStore store)
{
    Owner owner = CreateOwner();
    Console.WriteLine(
        "Do you want to create a pet and assign it to {0}? (Y/y: yes, anything else: no)", 
        owner.Name);

    bool createPets = Console.ReadLine().ToLower() == "y";
    do
    {
        owner.Pets.Add(CreatePet());

        Console.WriteLine("Do you want to create a pet and assign it to {0}?", owner.Name);
        createPets = Console.ReadLine().ToLower() == "y";
    } while (createPets);

    using (IDocumentSession session = store.OpenSession())
    {
        session.Store(owner);
        session.SaveChanges();
    }
}
 

Ora vediamo come funziona. Abbiamo definito alcune semplici regole C # per creare oggetti Owner e continuare a creare e assegnare ad esso oggetti Pet fino a quando l'utente lo desidera. La parte in cui RavenDB è interessato e quindi è al centro di questo articolo è il modo in cui salviamo gli oggetti.

Per salvare il nuovo Owner creato insieme ai suoi Pet , dobbiamo prima aprire una sessione, che implementa IDocumentSession . Possiamo crearne uno chiamando l' OpenSession sull'oggetto del negozio di documenti.

Quindi, nota la differenza, mentre l'archivio documenti è un oggetto permanente che generalmente esiste durante l'intero ciclo di vita dell'applicazione, mentre IDocumentSession è un oggetto leggero e di breve durata. Rappresenta una serie di operazioni che vogliamo eseguire in un colpo solo (o almeno, in poche chiamate al database).

RavenDB enfatizza (e in qualche modo impone) che si evita un eccessivo numero di roundtrip sul server, qualcosa che chiamano "Protezione chatter client-server" sul sito web di RavenDB. Proprio per questo motivo, una sessione ha un limite predefinito al numero di chiamate al database che tollera e pertanto è necessario prestare attenzione a quando una sessione viene aperta e dismessa. Perché in questo esempio, consideriamo la creazione di un Owner e dei suoi Pet s come un'operazione che dovrebbe essere eseguita da sola, lo facciamo in una sessione e poi lo smaltiamo.

Possiamo vedere altre due chiamate di metodo che ci interessano:

  • session.Store(owner) , che registra l'oggetto per il salvataggio e, inoltre, imposta la proprietà Id dell'oggetto se non è ancora impostata. Il fatto che la proprietà identificatore sia chiamata Id è quindi una convenzione.
  • session.Savehanges() invia le operazioni effettive da eseguire al server RavenDB, commettendo tutte le operazioni in sospeso.

Recupero per ID

Un'altra operazione comune è ottenere un oggetto tramite il suo identificatore. Nel mondo relazionale, normalmente lo facciamo usando un'espressione Where , specificando l'identificatore. Ma poiché in RavenDB, ogni query viene eseguita utilizzando gli indici, che potrebbero essere obsoleti , non è l'approccio da adottare, infatti, RavenDB genera un'eccezione se tentiamo di eseguire una query in base all'ID. Invece, dovremmo usare il metodo Load<T> , specificando l'id. Con la nostra logica di menu già in atto, abbiamo solo bisogno di definire il metodo che effettivamente carica i dati richiesti e mostra i suoi dettagli:

private static void GetOwnerById(IDocumentStore store)
{
    Owner owner;
    string id = NormalizeOwnerId(ReadNotEmptyString("Enter the Id of the owner to display."));

    using (IDocumentSession session = store.OpenSession())
    {
        owner = session.Load<Owner>(id);
    }

    if (owner == null)
    {
        Console.WriteLine("Owner not found.");
    }
    else
    {
        Console.WriteLine(owner);
    }

    PressAnyKeyToContinue();
}
 

Tutto ciò che è correlato a RavenDB è, ancora una volta, l'inizializzazione di una sessione, quindi utilizzando il metodo Load . La libreria client RavenDB restituirà l'oggetto deserializzato come il tipo specificato come parametro type. È importante sapere che RavenDB non applica alcun tipo di compatibilità qui: tutte le proprietà mappabili vengono mappate e quelle non modificabili no.

RavenDB ha bisogno il prefisso tipo di documento anteposto al Id - questo è il motivo per la chiamata di NormalizeOwnerId . Se un documento con l'Id specificato non esiste, viene restituito un valore null .

Interrogazione

Vedremo qui due tipi di query: una in cui eseguiamo una query sulle proprietà dei documenti Owner e su cui interrogiamo sugli oggetti Pet incorporati.

Iniziamo con quello più semplice, in cui interrogiamo i documenti del Owner cui proprietà Name inizia con la stringa specificata.

private static void QueryOwnersByName(IDocumentStore store)
{
    string namePart = ReadNotEmptyString("Enter a name to filter by.");

    List<Owner> result;
    using (IDocumentSession session = store.OpenSession())
    {
        result = session.Query<Owner>()
           .Where(ow => ow.Name.StartsWith(namePart))
           .Take(10)
           .ToList();
    }

    if (result.Count > 0)
    {
        result.ForEach(ow => Console.WriteLine(ow));
    }
    else
    {
        Console.WriteLine("No matches.");
    }
    PressAnyKeyToContinue();
}
 

Ancora una volta, perché vorremmo eseguire la query come lavoro indipendente, apriamo una sessione. Possiamo eseguire una query su una raccolta di documenti chiamando Query<TDocumentType> sull'oggetto di sessione. Restituisce un IRavenQueryable<TDocumentType> , sul quale possiamo chiamare i soliti metodi LINQ, nonché alcune estensioni specifiche di RavenDB. Facciamo un semplice filtraggio qui, e la condizione è che il valore della proprietà Name inizi con la stringa inserita. Prendiamo i primi 10 elementi del set di risultati e ne creiamo un elenco. Bisogna fare attenzione a specificare correttamente la dimensione del set di risultati - ecco un'altra applicazione difensiva in gioco eseguita da RavenDB chiamata Protezione set di risultati illimitata. Significa che (per impostazione predefinita) vengono restituiti solo i primi 128 elementi.

La nostra seconda query ha il seguente aspetto:

private static void QueryOwnersByPetsName(IDocumentStore store)
{
    string namePart = ReadNotEmptyString("Enter a name to filter by.");

    List<Owner> result;
    using (IDocumentSession session = store.OpenSession())
    {
       result = session.Query<Owner>()
           .Where(ow => ow.Pets.Any(p => p.Name.StartsWith(namePart)))
           .Take(10)
           .ToList();
    }

    if (result.Count > 0)
    {
        result.ForEach(ow => Console.WriteLine(ow));
    }
    else
    {
        Console.WriteLine("No matches.");
    }
    PressAnyKeyToContinue();
}
 

Questo non è molto più complicato, l'ho scritto per dimostrare quanto sia possibile interrogare in modo naturale le proprietà degli oggetti incorporati. Questa query restituisce semplicemente i primi 10 Owner che hanno almeno un Pet cui nome inizia con il valore inserito.

cancellazione

Abbiamo due opzioni per eseguire la cancellazione. Uno è quello di passare l'identificatore del documento, che è utile se non abbiamo l'oggetto stesso in memoria, ma abbiamo l'identificatore e vorremmo evitare un roundtrip altrimenti evitabile al database. L'altro modo, ovviamente, è passare un oggetto reale salvato su RavenDB. Vedremo la prima opzione qui, l'altra sta semplicemente usando un altro overload e passando un oggetto appropriato:

private static void DeleteOwnerById(IDocumentStore store)
{
    string id = NormalizeOwnerId(ReadNotEmptyString("Enter the Id of the owner to delete."));

    using (IDocumentSession session = store.OpenSession())
    {
        session.Delete(id);
        session.SaveChanges();
    }
}
 

Ancora una volta, abbiamo bisogno di aprire una sessione per svolgere il nostro lavoro. Come accennato in precedenza, qui cancelliamo l'oggetto desiderato passando il suo identificatore al metodo Delete . Il prefisso identificativo dovrebbe essere presente anche qui, proprio come nel caso del metodo Load . Per inviare effettivamente il comando delete al database, dobbiamo chiamare il metodo SaveChanges , che farà proprio questo, insieme a tutte le altre operazioni in sospeso registrate nella stessa sessione.

In aggiornamento

E infine, daremo un'occhiata a come aggiornare i documenti. Fondamentalmente, abbiamo due modi per farlo. Il primo è semplice, cariciamo un documento, aggiorniamo le sue proprietà secondo necessità, quindi lo passiamo al metodo Store . Questo dovrebbe essere semplice in base alla dimostrazione del caricamento e del salvataggio, ma ci sono alcune cose degne di nota.

Innanzitutto, la libreria client RavenDB utilizza un tracker delle modifiche che rende possibile l'aggiornamento di qualsiasi documento senza in realtà passarlo sullo Store purché la sessione che ha caricato il documento sia ancora aperta. In questo caso, chiamare SaveChanges sulla sessione è sufficiente per l'aggiornamento.

In secondo luogo, affinché funzioni, l'oggetto ha ovviamente bisogno di essere identificato in modo che RavenDB possa capire cosa aggiornare.

Detto questo, daremo solo un'occhiata all'altra modalità di aggiornamento. Esiste un concetto chiamato patching, che può essere utilizzato per aggiornare i documenti. Proprio come nel caso di delete, ha anche i propri scenari di utilizzo. Usare il metodo precedente per eseguire un aggiornamento è un buon modo se abbiamo già l'oggetto in memoria e / o vogliamo usare il suo tipo di sicurezza. L'uso delle patch è l'opzione se vogliamo evitare un roundtrip altrimenti inutile nel database se non abbiamo già l'oggetto in memoria. Il rovescio della medaglia è che perdiamo parte del tipo safety, dal momento che dobbiamo specificare le proprietà da aggiornare usando stringhe semplici (nulla che alcuni LINQ-magic non possano risolvere). Vediamo il codice:

private static void RenameOwnerById(IDocumentStore store)
{
    string id = NormalizeOwnerId(ReadNotEmptyString("Enter the Id of the owner to rename."));
    string newName = ReadNotEmptyString("Enter the new name.");

    store.DatabaseCommands.Patch(id, new Raven.Abstractions.Data.PatchRequest[]{
        new Raven.Abstractions.Data.PatchRequest
        {
            Name = "Name",
            Value = newName
        }
    });
}
 

Questo lo avvolge. Dovresti essere in grado di vedere l'esempio lavorando incollando i frammenti di codice in un'app console.

Installazione o configurazione

Istruzioni dettagliate su come installare o installare ravendb.