Paweł Łukasiewicz
2024-05-05
Paweł Łukasiewicz
2024-05-05
Udostępnij Udostępnij Kontakt
Wprowadzenie

Wystarczy wiedzy teoretycznej, przejdźmy, prawie od razu, do przykładów w oparciu o nasz ulubiony język jakim jest C#. Jak już doskonale wiecie, lokalne indeksy wtórne, muszą być tworzone w tym samym czasie, w którym tworzona jest tabela. Aby to zrobić musimy posłużyć się metodą CreateTable podając specyfikację jednego lub więcej lokalnych indeksów wtórnych. W poniższym przykładzie utworzymy tabelę przechowywującą informacje o konkretnych samochodach w kolekcji samochodów. Kluczem partycji jest Brand a kluczem sortowania Model. Indeks pomocniczy, CarCollectionModelIndex ułatwi nam zapytania o rodzinę modeli danej kategorii.

Kolejne kroki, które musimy wykonać to utworzenie instancji klasy AmazonDynamoDBClient (tutaj możecie posłużyć się szablonem projektu dostępnym w tym wpisie: DynamoDB - tworzenie tabeli). Drugi krok to utworzenie instancji klasy CreateTableRequest w którym musimy przekazać informacje takie jak nazwa tabeli, klucz główny oraz wartości przepustowości. Dodatkowo, dla lokalnego indeksu wtórnego, musimy podać nazwę indeksu, nazwę oraz typ danych klucza sortowania indeksu, schemat klucza oraz rzutowane atrybuty. Ostatni krok to wykonanie metody CreateTable podając powyższy obiekt jako parametr żądania.

Spójrzcie na poniższy przykład oraz komentarze dotyczące kolejnych linii kodu:

public async Task<ActionResult<string>> TableWithLocalSecondaryIndex()
{
    string tableName = "Car";

    var createTableRequest = new CreateTableRequest()
    {
        TableName = tableName
    };

    // Konfiguracja przepustowości
    createTableRequest.ProvisionedThroughput = new ProvisionedThroughput()
    {
        ReadCapacityUnits = 5,
        WriteCapacityUnits = 5
    };

    // Definicja atrybutów
    var attributeDefintion = new List<AttributeDefinition>()
    {
        {new AttributeDefinition {AttributeName = "Brand", AttributeType = "S"} },
        {new AttributeDefinition {AttributeName = "Model", AttributeType = "S"} },
        {new AttributeDefinition {AttributeName = "Class", AttributeType = "N"} },
    };
    createTableRequest.AttributeDefinitions = attributeDefintion;

    // Schemat klucza tabeli
    var tableKeySchema = new List<KeySchemaElement>()
    {
        {new KeySchemaElement {AttributeName = "Brand", KeyType = "HASH"} }, // klucz partycji
        {new KeySchemaElement {AttributeName = "Model", KeyType = "RANGE"} },// klucz sortowania
    };
    createTableRequest.KeySchema = tableKeySchema;

    // Schemat klucza indeksu
    var indexKeySchema = new List<KeySchemaElement>()
    {
        {new KeySchemaElement {AttributeName = "Brand", KeyType = "HASH"} }, // klucz partycji
        {new KeySchemaElement {AttributeName = "Class", KeyType = "RANGE"} },// klucz sortowania
    };

    // typ rzutowania atrybutów do indeksu
    var projection = new Projection()
    {
        ProjectionType = "INCLUDE"
    };

    // atrybyty nie będące kluczami
    List<string> nonKeyAttributes = new List<string>();
    nonKeyAttributes.Add("Engine");
    nonKeyAttributes.Add("Year");
    // określając typ projekcji na 'INCLUDE' musimy również określić atrybuty nie będące kluczami,
    // które zostaną uwzględnione w projekcji
    projection.NonKeyAttributes = nonKeyAttributes;

    var localSecondaryIndex = new LocalSecondaryIndex()
    {
        IndexName = "CarCollectionModelIndex",
        KeySchema = indexKeySchema,
        Projection = projection
    };

    var localSecondaryIndexes = new List<LocalSecondaryIndex>();
    localSecondaryIndexes.Add(localSecondaryIndex);

    createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

    var result = await _amazonDynamoDB.CreateTableAsync(createTableRequest);

    return $"Utworzono tabele: {result.TableDescription.TableName}. Obecny status: {result.TableDescription.TableStatus}";
}
                    

Wykonanie powyższego kodu pozwoli na utworzenie tabeli. Pamiętajcie, że do kolejnego kroku możemy przejść dopiero wtedy, gdy DynamoDB zwróci nam status tabeli ACTIVE - w poprzednich wpisach przygotowaliśmy kod czekający na utworzenie tabeli, dopiero później tabelę możemy wypełnić danymi.

Szczegóły tabeli z lokalnym indeksem wtórnym

Użycie metody DescribeTable jest już Wam doskonale znane. W tym przypadku możemy uzyskać informacje dla każdego indeksu, tj. nazwa, schemat klucza czy rzutowane atrybuty. Przechodzimy od razu do konkretnego przykładu ponieważ klasa DescribeTableRequest nie skrywa przed nami już żadnych tajemnic:

public async Task<ActionResult<string>> DescribeTableWithLSI()
{
    StringBuilder sb = new StringBuilder();
    // nazwe tabeli warto wynieść 'wyżej' ponieważ odnosimy się do niej w kilku miejscach
    string tableName = "Car";

    var response = _amazonDynamoDB.DescribeTableAsync(tableName);
    List<LocalSecondaryIndexDescription> localSecondaryIndexes = response.Result.Table.LocalSecondaryIndexes;

    // W naszym przypadku jest tylko jeden lokalny indeks wtórny
    // Poniższy kod jednak będzie działał również dla większej liczby indeksów
    foreach (var lsiDescription in localSecondaryIndexes)
    {
        sb.AppendLine($"Informacja o indeksie dla: {lsiDescription.IndexName}");

        foreach (KeySchemaElement kse in lsiDescription.KeySchema)
        {
            sb.AppendLine($"Nazwa atrybutu: {kse.AttributeName} - Typ atrybutu: {kse.KeyType}");
        }

        var projection = lsiDescription.Projection;
        sb.AppendLine($"Rodzaj rzutowania: {projection.ProjectionType}");

        if (projection.ProjectionType.Equals("Include"))
        {
            sb.AppendLine("Atrybuty nie wchodzące w skład rzutowania: ");
            foreach (var nonKeyAttr in projection.NonKeyAttributes)
            {
                sb.AppendLine(nonKeyAttr);
            }
        }
    }

    return sb.ToString();
}

Po poprawnym wykonaniu kodu zobaczycie, że nasza definicja (z poprzedniego akapitu) zgadza się z opisem tabeli: DynamoDB: DescribeTable na tabeli z LSI (lokalny indeks wtórny)

Zapytanie o lokalny indeks wtórny

Query na lokalnym indeksie wtórnym możemy użyć w taki sam sposób jak zapytanie dla tabeli. Musimy określić nazwę indeksu, kryteria zapytania dla klucza sortowania indeksu oraz atrybuty, które chcemy zwrócić. W naszym przykładzie indeks to CarCollectionModelIndex a kluczem sortowania jest Model.

Zwracane są tylko atrybuty, które zostały umieszczone w indeksie. Zapytanie to możemy zmodyfikować w celu zwrócenia atrybutów nie będących kluczami, ale wymagałoby to wykonania czynności pobierania danych tabeli – jak wiemy ta operacja jest stosunkowa droga. Podobnie jak w poprzednich przypadkach musimy przygotować żądanie – wykorzystamy klasę QueryRequest, której obiekt przekażemy jako parametr metody Query. Spójrzcie na poniższy przykład:

public async Task<ActionResult<string>> QueryOnLSI()
{
    // Dodajcie dane do tabeli jezeli chcecie zobaczyc zwrocone rezultaty
    // Mozecie zmodyfikowac dwie metody z poprzednich wpisów:
    // FillDataIntoCarPartsCatalog oraz AddItem lub podmienić dane w metodzie: LoadReplyData()
    //await LoadWeatherData();
    StringBuilder sb = new StringBuilder();
    // nazwe tabeli warto wynieść 'wyżej' ponieważ odnosimy się do niej w kilku miejscach
    string tableName = "Car";
    string indexName = "CarCollectionModelIndex";

    var queryRequest = new QueryRequest()
    {
        TableName = tableName,
        IndexName = indexName,
        Select = "ALL_ATTRIBUTES",
        KeyConditionExpression = "Brand = :v_brand and #cl =:v_class",   // wyrażenie
        ExpressionAttributeNames = new Dictionary<string, string>        // i jego definicję (zastrzeżona nazwa 'Class'
        {
            {"#cl", "Class" }
        },
        ExpressionAttributeValues = new Dictionary<string, AttributeValue> // wartości kluczy
        {
            { ":v_brand", new AttributeValue { S = "Audi"} },
            { ":v_class", new AttributeValue { N = "6"} }  // ma na myśli wszystkie odmiany, A6, S6, RS6
        },
        ScanIndexForward = true
    };

    var result = await _amazonDynamoDB.QueryAsync(queryRequest);

    foreach (var item in result.Items)
    {
        foreach (var attribute in item)
        {
            // Spójrzcie na definicje atrybutów w poprzednim wpisie
            // Wtedy zobaczycie dlaczego takie podejście do 'wypisywania' wartości atrybutów
            sb.AppendLine($"{attribute.Key} --> {attribute.Value.S}");
        }

        sb.AppendLine();
    }

    return sb.ToString();
}
    

Tym razem nie wypełniałem tabeli danymi – pozostawiłem jedynie komentarz jak sami możecie dodać dane.