Can I use the DynamoDB .NET Object Persistence Model when I have a Global Secondary Index? - amazon-web-services

I have a table in Dynamo with a hash & range index plus a secondary global index with just a hash. If I try to Query or Save an object I get the following error:
Number of hash keys on table TableName does not match number of hash keys on type ObjectModelType
(Replacing TableName and ObjectModelType with the actual table and model type)
I have the hash properties (both the primary and secondary) decorated with DynamoDBHashKey
Googling the error turns up exactly zero results
Update: Ok, so not exactly zero, obviously it now returns this question!
Update the second: I've tried using the helper API & it works just fine, so I am assuming at this point that the Object Persistence Model doesn't support Global Secondary Indexes

I encountered the same problem and found FromQuery worked, although QueryFilter is actually from the DocumentModel namespace:
var queryFilter = new QueryFilter(SecondaryIndexHashKeyColumn, QueryOperator.Equal, "xxxx");
queryFilter.AddCondition(SecondaryIndexRangeKeyColumn, QueryOperator.LessThan, DateTime.Today);
var items = context.FromQuery<MyItem>(new QueryOperationConfig { IndexName = SecondaryIndexName, Filter = queryFilter }).ToList();

Thank you Brendon for that tip! I was able to adapt it to get deserialization of a multiple result set.
var queryFilter = new QueryFilter(indexedColumnName, QueryOperator.Equal, targetValue);
Table table = Table.LoadTable(client, Configuration.Instance.DynamoTable);
var search = table.Query(new QueryOperationConfig { IndexName = indexName, Filter = queryFilter });
List<MyObject> objects = new List<MyObject>();
List<Document> documentSet = new List<Document>();
do
{
documentSet = search.GetNextSetAsync().Result;
foreach (var document in documentSet)
{
var record = JsonConvert.DeserializeObject<MyObject>(document.ToJson());
objects .Add(record);
}
} while (!search.IsDone);
Thanks x1000!! :)

Related

What does DynamoDB scan return if item with Exclusive Start Key does not exist in the table?

I'm trying to implement pagination for my API. I have a DynamoDB table with a simple primary key.
Since the ExclusiveStartKey in a DynamoDB scan() operation is nothing but the primary key of the last item fetched in the scan operation before, I was wondering what would DynamoDB return if I perform a scan() with an ExclusiveStartKey that does not exist in the table?
# Here response contains the same list of items for the same
# primary key passed to the scan operation
response = table.scan(ExclusiveStartKey=NonExistentPrimaryKey)
I expected DynamoDB to return no items (correct me if this assumption of mine is what's wrong), i.e the scanning should resume from the ExclusiveStartKey, if it exists in the table. If not, it should return no items.
But what I do see happening is, the scan() still returns items. When I give the same non-existent primary key, it keeps returning me a list starting from the same item.
Does DynamoDB simply apply the hash function on the ExclusiveStartKey and from the result of this hash decide from which partition it has to start returning items or something?
# My theory as to what DynamoDB does in a paginated scan operation
partitionId = dynamodbHashFunction(NonExistentPrimaryKey)
return fetchItemsFromPartition(partitionId)
My end goal is that when an invalid ExclusiveStartKey is provided by the user (i.e a non-existent primary key), I want to return nothing or even better, return a message that the ExclusiveStartKey is invalid.
Looks like you want to return items based on a value. If that value does not exist, then you want to have an empty result set. This is possible with the
Java V2 DynamoDbTable object's scan method:
https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html
For this solution, one way is to scan an AmazonDB table and return a result set based on the value of specific column (including the key). You can use an Expression object. This lets you set the value that you want to return in a result set.
For example, here is Java logic that returns all items where a date column is 2013-11-15. If there are no items that meet this condition, then no items are returned. There is no need for a pre-check, etc. You need to setup the ScanEnhancedRequest properly.
public static void scanIndex(DynamoDbClient ddb, String tableName, String indexName) {
System.out.println("\n***********************************************************\n");
System.out.print("Select items for "+tableName +" where createDate is 2013-11-15!");
try {
// Create a DynamoDbEnhancedClient and use the DynamoDbClient object.
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(ddb)
.build();
// Create a DynamoDbTable object based on Issues.
DynamoDbTable<Issues> table = enhancedClient.table("Issues", TableSchema.fromBean(Issues.class));
// Setup the scan based on the index.
if (indexName == "CreateDateIndex") {
System.out.println("Issues filed on 2013-11-15");
AttributeValue attVal = AttributeValue.builder()
.s("2013-11-15")
.build();
// Get only items in the Issues table for 2013-11-15.
Map<String, AttributeValue> myMap = new HashMap<>();
myMap.put(":val1", attVal);
Map<String, String> myExMap = new HashMap<>();
myExMap.put("#createDate", "createDate");
Expression expression = Expression.builder()
.expressionValues(myMap)
.expressionNames(myExMap)
.expression("#createDate = :val1")
.build();
ScanEnhancedRequest enhancedRequest = ScanEnhancedRequest.builder()
.filterExpression(expression)
.limit(15)
.build();
// Get items in the Issues table.
Iterator<Issues> results = table.scan(enhancedRequest).items().iterator();
while (results.hasNext()) {
Issues issue = results.next();
System.out.println("The record description is " + issue.getDescription());
System.out.println("The record title is " + issue.getTitle());
}
}
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}

Can I query DynamoDBMapper with partition key only?

I've seen this page about how to query with partition keys only. However, my case is using DynamoDBMapper class to make the query, what seemed to work there does not apply.
Here's a part of my code:
private final DynamoDBMapper mapper;
List<QueryResult> queryResult = mapper.query(QueryResult.class, queryExpression);
The table I query has a primary partition key id and primary sort key timestamp.
I wanted to query all the rows with designatedid, eav looks like:
{:id={S: 0123456,}}
but if the id has duplicates (which makes sense cause it's partition key), it always gives me
"The provided key element does not match the schema"
Not sure how to resolve this. Due to sharing code with other tables, DynamoDBMapper class is a must.
Any help appreciated! Thanks.
Does the below work?
final DynamoDBQueryExpression<QueryResult> queryExpression = new DynamoDBQueryExpression<>();
expression.setKeyConditionExpression("id = :id");
expression.withExpressionAttributeValues(ImmutableMap.of(":id", new AttributeValue("0123456")));
Here is a working example:
final MyItem hashKeyValues = MyItem.builder()
.hashKeyField("abc")
.build();
final DynamoDBQueryExpression<MyItem> queryExpression = new DynamoDBQueryExpression<>();
queryExpression.withHashKeyValues(hashKeyValues);
queryExpression.setConsistentRead(false); //or true
final PaginatedQueryList<MyItem> response = dynamoDBMapper.query(MyItem.class, queryExpression);

DynamoDB Can I query just the values of a GSI Index when it is a GSI with sort Key

I have a DynamoDB Table "Music". On this it has a GSI with partition key "Category" and sort key "UserRating".
I can query easily as an example for songs that are in "Category" = "Rap" and "UserRating" = 1
What I would like to do is query and just get back all the "Categories". As this is a a GSI and the partition key I have heard you can do it but I am not sure how.
Is it possible or will I have to create a separate GSI on "Category" without the sort key.
Thanks for your help.
When you don't want to filter by key. You may need to scan the index. The below solution is scanning the index to get all the category (not all distinct category).
Please find below the Java code to get all the Category from GSI. Replace the secondary index name in the below code accordingly.
List<String> categoryList = new ArrayList<>();
DynamoDB dynamoDB = new DynamoDB(dynamoDBClient);
Table table = dynamoDB.getTable("Music");
Index index = table.getIndex("Secondary Index Name");
ItemCollection<ScanOutcome> items = null;
ScanSpec scanSpec = new ScanSpec().withSelect(Select.SPECIFIC_ATTRIBUTES).withAttributesToGet("Category");
items = index.scan(scanSpec);
Iterator<Item> pageIterator = items.iterator();
while (pageIterator.hasNext() ) {
categoryList.add(pageIterator.next().getString("Category"));
}

Sitecore Search Orderby

I am seeing a very strange behaviour from the Sitecore 7.1 Search when ordering by a string field. The code is something like this:
var indexableItem = new SitecoreIndexableItem(Sitecore.Context.Item);
var searchIndex = ContentSearchManager.GetIndex(indexableItem);
var context = searchIndex.CreateSearchContext();
var results = new List<Item>();
var count = 0;
var totalSearchResults = 0;
var contactColleagues = context.GetQueryable<SearchResultItem>()
.FirstOrDefault(x => x.ItemId == Sitecore.Context.Item.ID);
if (contactColleagues != null)
{
var items = contactColleagues.GetDescendants<ColleagueSearchResultItem>(context)
.Where(x => x.TemplateId == Templates.Colleague);
items = items.OrderBy(x => x.Surname);
var resultItems = items.Page(this.PageNumber, PageSize).GetResults();
}
This all works as expected and returns my dataset ordered by surname as expected. That is until a certainly individual comes along who's surname is "Or". Now Sitecore returns this persons name at the start of the list, no matter what we do.
After some testing I found the same issue happened if I decided to call someone "Andy And", this would appear at the being of the list before "Jeff Aardvark".
I'm assuming this is a bug in the way the data is being presented to the Lucene index.
Has anyone come across this, or have any thoughts about how this could be worked around?
Thanks in advance.
I think you have a problem with stop words. The default analyzer removes them when the item is crawled. You can however prevent this behaviour.
This post explains how to to turn off the stop words filter:
http://blog.horizontalintegration.com/2015/01/08/sitecore-standard-analyzer-turn-off-the-stop-words-filter/
If you look at the surname field in the index using Luke (https://code.google.com/p/luke/) is the value blank? It sounds like potentially dangerous query values are being stripped out either at the index level or as they are being loaded from the index.

Sitecore: Follow relational links in Sitecore Query

I have an item structure that doens't correspond to "documents" in the standard Sitecore sense. It corresponds to data items.
One of the relationships that my items have is the following:
Person -> State -> Country
Where a Person has a link field to a State and state's have link fields to a Country.
My question is: How would one write a query to retrieve all of the People who have an eventual link to a Country of a certain abbreviation?
I'm stumped as to how to do it in Sitecore Query. My current solution is using LINQ and several queries.
Assuming that the links between these items where created using a Reference field, such as a Droplink, Treelist, Droptree, etc., you can traverse the references/referrers using the Sitecore Links database.
Here is an example (untested) that starts with a country item and fetches any referring states. The same query is performed on the states to fetch any referring people. The results are added to a generic List<Item> collection.
List<Item> peopleList = new List<Item>();
var countryReferences = Globals.LinkDatabase.GetReferences(countryItem);
var referringStates = countryReferences.Select(c => c.GetTargetItem()).Where(c => c.Template.Key == "state");
foreach (var state in referringStates)
{
var stateReferences = Globals.LinkDatabase.GetReferences(state);
var referringPeople = stateReferences.Select(s => s.GetTargetItem()).Where(s => s.Template.Key == "person");
foreach (var person in referringPeople)
{
peopleList.Add(person);
}
}