ravendbKomma igång med ravendb


Anmärkningar

Det här avsnittet ger en översikt över vad ravendb är, och varför en utvecklare kanske vill använda den.

Det bör också nämna alla stora ämnen inom ravendb och länka till relaterade ämnen. Eftersom dokumentationen för ravendb är ny kan du behöva skapa initialversioner av relaterade ämnen.

En enkel RavenDB-konsolapplikation

För detta exempel kommer vi att använda Live Test RavenDB-instansen .

Vi kommer att bygga en enkel konsolapp här som visar de mest grundläggande operationerna:

  • Skapande
  • Återvinning med ID
  • att fråga
  • Uppdaterande
  • Radering

Börja med att skapa en ny Visual Studio-lösning och lägg till ett Console Application-projekt till det. Låt oss kalla det RavenDBDemoConsole. Användningen av klientbiblioteket bör vara liknande om läsaren använder VS-kod eller hans / hennes favoritredigerare.

Därefter måste vi lägga till de referenser som krävs. Högerklicka på referensnoden i rutan Solution Explorer och välj Hantera NuGet-paket. Bläddra online efter 'RavenDb.Client'. Jag kommer att använda den senaste stabila versionen, som är - från och med detta skrivande - 3.5.2.

Låt oss skriva lite kod, ska vi? Börja med att lägga till följande med uttalanden:

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

Dessa tillåter oss att använda RavenDBs IDocumentStore och DocumentStore , som är ett gränssnitt och dess out-of-the-box implementering för att ansluta till en RavenDB-instans. Detta är toppnivåobjektet vi behöver använda för att ansluta till en server och RavenDB-dokumentationen av det rekommenderar att det används som singleton i applikationen.

Så vi kommer att gå vidare och skapa en, men för enkelhets skull kommer vi inte att implementera singletonomslaget runt det - vi kommer bara att bortskaffa det när programmet går ut så att anslutningen stängs på ett rent sätt. Lägg till följande kod till din huvudmetod:

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

Som sagt i början använder vi Live Test RavenDB-instansen, vi använder dess adress som Url egenskapen för DocumentStore . Vi anger också ett standarddatabasnamn, i detta fall "Husdjur". Om databasen inte finns ännu skapar RavenDB den när du försöker komma åt den. Om det existerar kan klienten använda den befintliga. Vi måste anropa metoden Initialize() så att vi kan börja arbeta med den.

I denna enkla applikation kommer vi att underhålla ägare och husdjur. Vi tänker på deras anslutning eftersom en ägare kan ha godtyckligt antal husdjur men ett husdjur kan bara ha en ägare. Även om i den verkliga världen kan ett husdjur ha godtyckligt antal ägare, till exempel en man och en hustru, kommer vi att välja detta antagande eftersom många-till-många-relationer i en dokumentdatabas hanteras något annorlunda än i ett relationsdatabas och förtjänar ett eget ämne. Jag har valt den här domänen eftersom den är tillräckligt vanlig för att förstå.

Så vi bör nu definiera våra domänobjekttyper:

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

Det finns några saker att notera här:

För det första kan våra Owner innehålla noll eller fler Pet . Observera att klassen Owner har en egenskap som heter Id medan klassen Pet inte gör det. Detta beror på att Pet objekt kommer att lagras inuti Owner föremål, vilket är ganska annorlunda från hur denna typ av relation skulle genomföras i en relationsdatabas.

Man kan hävda att detta inte bör genomföras så här - och det kan vara rätt, det beror verkligen på kraven. Som tumregel, om ett Pet är vettigt att existera utan en Owner bör det inte vara inbäddat utan existera på egen hand med en egen identifierare. I vår ansökan antar vi att ett Pet endast betraktas som ett husdjur om det har en ägare, annars skulle det vara ett kriterium eller ett djur. Av den anledningen lägger vi inte till en Id egenskap i klassen Pet .

För det andra, notera att identifieraren för ägarklassen är en sträng som det vanligtvis visas i exemplen i RavenDB-dokumentationen. Många utvecklare brukade förhållande databaser kanske anser att detta är en dålig praxis, vilket vanligtvis är vettigt i den relationella världen. Men eftersom RavenDB använder Lucene.Net för att utföra sina uppgifter och eftersom Lucene.Net är specialiserat på att arbeta med strängar är det helt acceptabelt här - också har vi att göra med en dokumentdatabas som lagrar JSON och, i princip, allt representeras som en sträng i JSON.

En sak att notera om egenskapen Id är att den inte är obligatorisk. I själva verket fäster RavenDB sina egna metadata till alla dokument vi sparar, så även om vi inte definierade det, skulle RavenDB inte ha några problem med våra objekt. Det är dock generellt definierat för enklare åtkomst.

Innan vi ser hur vi kan använda RavenDB från vår kod, låt oss definiera några vanliga hjälpmetoder. Dessa bör vara självförklarande.

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

Och vår huvudsakliga metod:

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

Skapande

Låt oss se hur vi kan spara några objekt i RavenDB. Låt oss definiera följande vanliga metoder:

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

Låt oss nu se hur det fungerar. Vi har definierat några enkla C # -logiker för att skapa Owner och fortsätta skapa och tilldela det Pet tills användaren så önskar. Den del som RavenDB berör och därmed fokuserar på denna artikel är hur vi sparar objekt.

För att spara den nyligen skapade Owner tillsammans med dess Pet måste vi först öppna en session som implementerar IDocumentSession . Vi kan skapa en genom att ringa OpenSession på dokumentlagringsobjektet.

Så notera skillnaden, medan dokumentlagret är ett permanent objekt som vanligtvis finns under applikationens hela livslängd, är IDocumentSession ett kortlivat, lättviktigt objekt. Det representerar en serie operationer som vi vill utföra på en gång (eller åtminstone på bara några få samtal till databasen).

RavenDB betonar (och något tvingar) att du undviker alltför många rundturer till servern, något som de kallar "Client-Server chatter protection" på RavenDB: s webbplats. Just av denna anledning har en session en standardgräns för hur många databassamtal den tål, och man måste därför vara uppmärksam på när en session öppnas och bortskaffas. Eftersom vi i det här exemplet behandlar skapandet av en Owner och dess Pet som en operation som bör genomföras på egen hand, gör vi detta i en session och sedan bortskaffar vi den.

Vi kan se ytterligare två metodsamtal som är intressanta för oss:

  • session.Store(owner) , som registrerar objektet för att spara, och dessutom ställer in Id egenskapen för objektet om det ännu inte är inställt. Det faktum att identifieringsegenskapen kallas Id är därför en konvention.
  • session.Savehanges() skickar de faktiska operationerna för att utföra till RavenDB-servern med alla väntande operationer.

Återvinning med ID

En annan vanlig åtgärd är att få ett objekt med dess identifierare. I den relationella världen gör vi detta normalt med ett Where uttryck som anger identifieraren. Men eftersom RavenDB är varje fråga görs med hjälp av index, som kan vara gammal , är det inte den metod för att ta - i själva verket RavenDB kastar ett undantag om vi försöker fråga efter id. Istället bör vi använda Load<T> -metoden och ange id. Med vår menylogik redan på plats behöver vi bara definiera metoden som faktiskt laddar den begärda informationen och visar dess detaljer:

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

Allt som är RavenDB-relaterat här är än en gång initialisering av en session och sedan med Load metoden. RavenDB-klientbiblioteket returnerar det deserialiserade objektet som den typ vi anger som typparameter. Det är viktigt att veta att RavenDB inte verkställer någon form av kompatibilitet här - alla mappbara egenskaper kartläggs och de icke-mappbara inte.

RavenDB behöver prefixet för dokumenttyp förberedd till Id - det är anledningen till att NormalizeOwnerId ringer. Om ett dokument med den angivna ID inte finns returneras null .

att fråga

Vi kommer att se två typer av frågor här: en där vi frågar efter egna egenskaper för Owner och en där vi frågar efter de inbäddade Pet .

Låt oss börja med den enklare, där vi frågar Owner vars egenskap Name börjar med den angivna strängen.

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

Återigen, för att vi vill utföra frågan som ett självständigt arbete, öppnar vi en session. Vi kan fråga över en dokumentsamling genom att ringa Query<TDocumentType>Query<TDocumentType> . Det returnerar ett IRavenQueryable<TDocumentType> -objekt, på vilket vi kan kalla de vanliga LINQ-metoderna, liksom några RavenDB-specifika tillägg. Vi gör en enkel filtrering här och villkoret är att värdet på egenskapen Name börjar med den angivna strängen. Vi tar de första 10 artiklarna i resultatuppsättningen och skapar en lista över den. Man måste vara uppmärksam på att korrekt specificera resultatuppsättningsstorleken - här är en annan defensiv verkställighet som spelas av RavenDB som kallas Obegränsat resultatuppsättningsskydd. Det betyder att (som standard) endast de första 128 artiklarna returneras.

Vår andra fråga ser ut enligt följande:

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

Den här är inte mycket mer komplicerad, jag har skrivit det för att visa hur naturligt det är möjligt att fråga efter inbyggda objektegenskaper. Denna fråga returnerar helt enkelt de första 10 Owner som har minst ett Pet vars namn börjar med det angivna värdet.

Radering

Vi har två alternativ för att radera. Det ena är att skicka dokumentidentifieraren, vilket är användbart om vi inte har själva objektet i minnet men vi har identifieraren och vi skulle vilja förhindra en annars undvikbar rundtur till databasen. Det andra sättet är uppenbarligen då att skicka ett faktiskt objekt som sparats till RavenDB. Vi tittar på det första alternativet här, det andra använder bara en annan överbelastning och passerar ett lämpligt objekt:

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

Återigen måste vi öppna en session för att utföra vårt arbete. Som tidigare nämnts raderar vi det önskade objektet genom att lämna dess identifierare till metoden Delete . Identifieringsprefixet bör också finnas på plats här, precis som det var fallet med Load metoden. För att faktiskt skicka raderingskommandot till databasen måste vi ringa SaveChanges metoden, vilket gör just det, tillsammans med alla andra väntande operationer som registreras i samma session.

Uppdaterande

Och slutligen tittar vi på hur man uppdaterar dokument. I princip har vi två sätt att göra detta. Den första är okomplicerad, vi laddar ett dokument, uppdaterar dess egenskaper vid behov och överför det sedan till Store metoden. Detta borde vara enkelt enligt demonstrationen av lastning och sparande, men det finns några saker som är värda att notera.

Först använder RavenDB-klientbiblioteket en ändringsspårare som gör det möjligt att uppdatera alla dokument utan att faktiskt överföra det till Store så länge den session som laddade dokumentet fortfarande är öppen. I detta fall SaveChanges ringa SaveChanges på sessionen för att uppdateringen ska ske.

För det andra, för att detta ska fungera, behöver objektet uppenbarligen att dess identifierare ska ställas in så att RavenDB kan räkna ut vad som ska uppdateras.

Med dessa sagt kommer vi bara att titta på det andra sättet att uppdatera. Det finns ett koncept som heter patchning, som kan användas för att uppdatera dokument. Precis som det var fallet med radera, har det också sina egna användarscenarier. Att använda den föregående metoden för att utföra en uppdatering är ett bra sätt om vi redan har objektet i minnet och / eller vi vill använda dets typsäkerhet. Att använda lapp är alternativet om vi vill undvika en annars onödig rundtur till databasen om vi inte redan har objektet i minnet. Nackdelen är att vi förlorar en del av typsäkerheten, eftersom vi måste specificera egenskaperna som ska uppdateras genom att använda vanliga strängar (ingenting som någon LINQ-magi inte kunde lösa). Låt oss se koden:

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

Det lindar det. Du borde kunna se exemplet fungera genom att klistra in kodfragmenten i en konsolapp.

Installation eller installation

Detaljerade instruktioner för att få upp eller installera ravendb.