ravendbAan de slag met ravendb


Opmerkingen

Deze sectie geeft een overzicht van wat ravendb is en waarom een ontwikkelaar het misschien wil gebruiken.

Het moet ook alle grote onderwerpen binnen ravendb vermelden en naar de gerelateerde onderwerpen verwijzen. Aangezien de documentatie voor ravendb nieuw is, moet u mogelijk eerste versies van die gerelateerde onderwerpen maken.

Een eenvoudige RavenDB-consoletoepassing

Voor dit voorbeeld gebruiken we de Live Test RavenDB-instantie .

We zullen hier een eenvoudige console-app bouwen die de basisbewerkingen toont:

  • schepping
  • Ophalen op ID
  • bevraging
  • Updaten
  • verwijdering

Begin met het maken van een nieuwe Visual Studio-oplossing en voeg er een Console Application-project aan toe. Laten we het RavenDBDemoConsole noemen. Het gebruik van de clientbibliotheek moet vergelijkbaar zijn als de Reader VS Code of zijn / haar favoriete editor gebruikt.

Vervolgens moeten we de vereiste referenties toevoegen. Klik met de rechtermuisknop op het knooppunt Verwijzingen in het deelvenster Solution Explorer en selecteer NuGet-pakketten beheren. Blader online naar 'RavenDb.Client'. Ik gebruik de nieuwste stabiele release, die - vanaf dit moment - 3.5.2 is.

Laten we wat code schrijven, zullen we? Begin met het toevoegen van de volgende instructies:

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

Hiermee kunnen we RavenDB's IDocumentStore en DocumentStore , een interface en de kant-en-klare implementatie om verbinding te maken met een RavenDB-instantie. Dit is het topniveau-object dat we moeten gebruiken om verbinding te maken met een server en de RavenDB-documentatie ervan adviseert dat het als een singleton in de applicatie wordt gebruikt.

Dus we gaan door en maken er een, maar voor de eenvoud zullen we de singleton wrapper er niet omheen implementeren - we zullen deze gewoon weggooien wanneer het programma wordt afgesloten zodat de verbinding op een schone manier wordt gesloten. Voeg de volgende code toe aan uw hoofdmethode:

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

Zoals we in het begin zeiden, gebruiken we de Live Test RavenDB-instantie, we gebruiken het adres ervan als de eigenschap Url van de DocumentStore . We geven ook een standaarddatabasenaam op, in dit geval "Huisdieren". Als de database nog niet bestaat, maakt RavenDB deze aan wanneer u probeert toegang te krijgen. Als het bestaat, kan de client de bestaande gebruiken. We moeten de methode Initialize() aanroepen, zodat we ermee kunnen beginnen werken.

In deze eenvoudige toepassing behouden we eigenaren en huisdieren. We denken aan hun connectie omdat één eigenaar een willekeurig aantal huisdieren kan hebben, maar één huisdier slechts één eigenaar. Hoewel in de echte wereld een huisdier een willekeurig aantal eigenaren heeft, bijvoorbeeld een man en een vrouw, zullen we voor deze veronderstelling kiezen omdat veel-op-veel-relaties in een documentdatabase enigszins anders worden behandeld dan in een relationele database en verdient een eigen onderwerp. Ik heb dit domein gekozen omdat het gebruikelijk genoeg is om te begrijpen.

We moeten nu onze domeinobjecttypen definiëren:

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

Er zijn enkele dingen waar u op moet letten:

Ten eerste kunnen onze Owner nul of meer Pet bevatten. Merk op dat de klasse Owner een eigenschap heeft die Id en de klasse Pet niet. Dit komt omdat de Pet objecten worden opgeslagen in de Owner objecten, wat nogal anders is dan hoe dit soort relatie zou worden geïmplementeerd in een relationele database.

Men zou kunnen stellen dat dit niet zo zou moeten worden geïmplementeerd - en het kan kloppen, het hangt echt af van de vereisten. Als vuistregel geldt dat als een Pet logisch is te bestaan zonder een Owner , het niet moet worden ingebed, maar alleen moet bestaan met een eigen ID. In onze applicatie gaan we ervan uit dat een Pet alleen als een huisdier wordt beschouwd als het een eigenaar heeft, anders zou het een beest of een beest zijn. Om deze reden voegen we geen Id eigenschap toe aan de klasse Pet .

Merk ten tweede op dat de identifier van de eigenaarklasse een string is zoals deze in het algemeen wordt getoond in de voorbeelden in de RavenDB-documentatie. Veel ontwikkelaars die gewend zijn aan relationele databases, kunnen dit als een slechte gewoonte beschouwen, wat meestal logisch is in de relationele wereld. Maar omdat RavenDB Lucene.Net gebruikt om zijn taken uit te voeren en omdat Lucene.Net gespecialiseerd is in het werken met tekenreeksen, is het hier volkomen acceptabel - we hebben ook te maken met een documentdatabase waarin JSON wordt opgeslagen en uiteindelijk is eigenlijk alles als een tekenreeks weergegeven in JSON.

Nog een ding om op te merken over de eigenschap Id is dat deze niet verplicht is. In feite voegt RavenDB zijn eigen metadata toe aan elk document dat we opslaan, dus zelfs als we het niet zouden definiëren, zou RavenDB geen problemen hebben met onze objecten. Het is echter algemeen gedefinieerd voor gemakkelijkere toegang.

Voordat we kijken hoe we de RavenDB uit onze code kunnen gebruiken, laten we een paar veel voorkomende helper-methoden definiëren. Deze moeten voor zichzelf spreken.

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

En onze belangrijkste methode:

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

schepping

Laten we eens kijken hoe we sommige objecten in RavenDB kunnen opslaan. Laten we de volgende algemene methoden definiëren:

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

Laten we nu kijken hoe het werkt. We hebben enkele eenvoudige C # -logica gedefinieerd om Owner te maken en deze te blijven maken en toewijzen aan Pet totdat de gebruiker dit wenst. Het deel waar RavenDB om gaat en dus de focus van dit artikel is, is hoe we objecten opslaan.

Om de nieuw gecreëerde Owner samen met zijn Pet te redden, moeten we eerst een sessie openen waarin IDocumentSession geïmplementeerd. We kunnen er een maken door de OpenSession op het documentarchiefobject aan te roepen.

Let dus op het verschil, terwijl de documentopslag een permanent object is dat over het algemeen gedurende de hele levensduur van de toepassing bestaat, de IDocumentSession een kortstondig, lichtgewicht object is. Het vertegenwoordigt een reeks bewerkingen die we in één keer willen uitvoeren (of in elk geval in slechts enkele oproepen naar de database).

RavenDB benadrukt (en enigszins dwingt) dat u buitensporige aantallen retourreizen naar de server vermijdt, iets dat ze 'Client-Server chatter-bescherming' op de RavenDB-website noemen. Juist om deze reden heeft een sessie een standaardlimiet voor het aantal database-oproepen dat het zal tolereren, en dus moet men letten op wanneer een sessie wordt geopend en verwijderd. Omdat we in dit voorbeeld het creëren van een Owner en zijn Pet als een bewerking die op zichzelf moet worden uitgevoerd, doen we dit in één sessie en verwijderen we het.

We kunnen nog twee methodeaanroepen zien die voor ons interessant zijn:

  • session.Store(owner) , die het te registreren object registreert en bovendien de eigenschap Id van het object instelt als het nog niet is ingesteld. Het feit dat de identifier-eigenschap Id wordt genoemd, is daarom een conventie.
  • session.Savehanges() verzendt de daadwerkelijke bewerkingen om uit te voeren naar de RavenDB-server, waarbij alle lopende bewerkingen worden vastgelegd.

Ophalen op ID

Een andere veel voorkomende bewerking is om een object aan de hand van zijn ID te krijgen. In de relationele wereld doen we dit normaal gesproken met behulp van een Where expressie, die de identifier opgeeft. Maar omdat in RavenDB elke zoekopdracht wordt uitgevoerd met behulp van indexen, die misschien verouderd zijn , is het niet de aanpak die we moeten nemen - in feite genereert RavenDB een uitzondering als we proberen op ID te zoeken. In plaats daarvan moeten we de methode Load<T> gebruiken en de id opgeven. Nu onze menulogica al aanwezig is, moeten we alleen de methode definiëren die de gevraagde gegevens laadt en de details ervan weergeeft:

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

Het enige dat RavenDB-gerelateerd is, is nogmaals de initialisatie van een sessie en vervolgens de methode Load gebruiken. De RavenDB-clientbibliotheek retourneert het gedeserialiseerde object als het type dat we specificeren als de parameter type. Het is belangrijk om te weten dat RavenDB hier geen enkele vorm van compatibiliteit afdwingt - alle toewijsbare eigenschappen worden toegewezen en de niet-toewijsbare niet.

RavenDB heeft het voorvoegsel van het documenttype nodig dat is toegevoegd aan de Id - dat is de reden voor het aanroepen van NormalizeOwnerId . Als een document met de opgegeven ID niet bestaat, wordt null geretourneerd.

bevraging

We zullen hier twee soorten zoekopdrachten zien: een waarin we de eigen eigenschappen van Owner documenten doorzoeken en een waarin we de ingesloten Pet objecten doorzoeken.

Laten we beginnen met de eenvoudigste, waarin we de Owner documenten opvragen waarvan de eigenschap Name begint met de opgegeven tekenreeks.

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

Nogmaals, omdat we de query als onafhankelijk werk willen uitvoeren, openen we een sessie. We kunnen een documentverzameling Query<TDocumentType> door Query<TDocumentType> aan te roepen op het Query<TDocumentType> . Het retourneert een IRavenQueryable<TDocumentType> -object, waarop we de gebruikelijke LINQ-methoden kunnen oproepen, evenals enkele RavenDB-specifieke extensies. We filteren hier eenvoudig en de voorwaarde is dat de waarde van de eigenschap Name begint met de ingevoerde tekenreeks. We nemen de eerste 10 items van de resultatenset en maken er een lijst van. Men moet aandacht besteden aan het correct specificeren van de grootte van de resultaatset - hier is een andere defensieve handhaving in het spel van RavenDB genaamd Unbounded resultaatsetbescherming. Dit betekent dat (standaard) alleen de eerste 128 items worden geretourneerd.

Onze tweede zoekopdracht ziet er als volgt uit:

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

Deze is niet veel gecompliceerder, ik heb het geschreven om aan te tonen hoe natuurlijk het mogelijk is om ingesloten objecteigenschappen te doorzoeken. Deze zoekopdracht retourneert eenvoudig de eerste 10 Owner die ten minste één Pet waarvan de naam begint met de ingevoerde waarde.

verwijdering

We hebben twee opties om verwijdering uit te voeren. Een daarvan is om de document-ID door te geven, wat handig is als we het object zelf niet in het geheugen hebben, maar we hebben wel de ID en we willen een anderszins vermijdbare retour naar de database voorkomen. De andere manier, uiteraard dan, is om een daadwerkelijk object door te geven dat is opgeslagen aan RavenDB. We zullen hier de eerste optie bekijken, de andere gebruikt gewoon een andere overbelasting en geeft een passend object door:

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

Nogmaals, we moeten een sessie openen om ons werk uit te voeren. Zoals eerder vermeld, verwijderen we hier het gewenste object door de ID ervan door te geven aan de methode Delete . Het voorvoegsel van de identifier moet hier ook op zijn plaats zitten, net zoals het geval was met de methode Load . Om de delete-opdracht daadwerkelijk naar de database te sturen, moeten we de SaveChanges methode aanroepen, die precies dat doet, samen met andere openstaande bewerkingen die in dezelfde sessie zijn geregistreerd.

Updaten

En tot slot kijken we hoe we documenten kunnen bijwerken. Kortom, we hebben twee manieren om dit te doen. De eerste is eenvoudig, we laden een document, werken de eigenschappen indien nodig bij en geven het vervolgens door aan de Store methode. Dit zou eenvoudig moeten zijn volgens de demonstratie van laden en opslaan, maar er zijn een paar dingen die het vermelden waard zijn.

Ten eerste gebruikt de RavenDB-clientbibliotheek een wijzigings-tracker die het mogelijk maakt om elk document bij te werken zonder het daadwerkelijk aan Store door te geven, zolang de sessie die het document heeft geladen nog open is. In dit geval volstaat het om SaveChanges tijdens de sessie aan te roepen om de update te laten plaatsvinden.

Ten tweede, om dit te laten werken, moet het object duidelijk zijn ID instellen zodat RavenDB kan achterhalen wat het moet bijwerken.

Met dit gezegd, zullen we alleen kijken naar de andere manier van updaten. Er is een concept genaamd patching, dat kan worden gebruikt om documenten bij te werken. Net zoals het geval was met delete, heeft het ook zijn eigen gebruiksscenario's. Het is een goede manier om de vorige methode te gebruiken om een update uit te voeren als we het object al in het geheugen hebben en / of het type veiligheid willen gebruiken. Het gebruik van patching is de optie als we een anders onnodige retour naar de database willen voorkomen als we het object nog niet in het geheugen hebben. Het nadeel is dat we een deel van het type veiligheid verliezen, omdat we de eigenschappen moeten bijwerken die moeten worden bijgewerkt met gewone tekenreeksen (niets dat sommige LINQ-magic niet kon oplossen). Laten we de code bekijken:

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

Dat sluit het af. U zou het voorbeeld moeten kunnen zien werken door de codefragmenten in een console-app te plakken.

Installatie of instellingen

Gedetailleerde instructies voor het instellen of installeren van ravendb.