Skip to content
scale-tone edited this page Apr 16, 2017 · 5 revisions
var moviesTable = ctx.GetTable<Movie>();
var reviewsTable = ctx.GetTable<Review>();

This leads to a Get operation against a DynamoDB table:

var inceptionMovie = moviesTable
    .Where(m => m.Genre == "Thriller" && m.Title == "Inception")
    .Single();

Console.WriteLine(inceptionMovie.Budget);

This leads to a Query operation:

var moviesQuery =
    from movie in moviesTable
    where movie.Genre == "Thriller" && movie.Year > 2000
    select movie;

foreach (var movie in moviesQuery)
{
    Console.WriteLine(movie.Title + " " + movie.Description);
}

This leads to a Scan operation, but only Title field is loaded from DynamoDB:

var movieTitlesQuery = from movie in moviesTable select movie.Title;

foreach (string title in movieTitlesQuery)
{
    Console.WriteLine(title);
}

This leads to a Scan operation, but only two fields are loaded from DynamoDB:

var projectionQuery = from movie in moviesTable select new {movie.Year, movie.Budget};

foreach (var t in projectionQuery)
{
    Console.WriteLine("Year: {0}; Budget: {1}", t.Year, t.Budget);
}

All query and scan comparison operators are supported, even the IN operator (via List<T>.Contains() method):

var ratings = new List<RatingEnum> {RatingEnum.Awful, RatingEnum.Average};
foreach (var r in reviewsTable.Where(r => ratings.Contains(r.Rating)))
{
    Console.WriteLine(r.Text);
}

This is how to use CONTAINS and BEGINS_WITH operators:

foreach 
(
    var badReview in reviewsTable
    .Where(r => r.Title.StartsWith("bullshit") && r.Text.Contains("sucks"))
)
{
    Console.WriteLine(badReview.Reviewer);
}

Starting with version 2.2.0 queries can be executed asynchronously, like this:

var movies = await moviesTable.Where(m => m.Description.Contains("Doom")).ToListAsync();

or like this:

var movies = await
    ( 
        from movie in moviesTable
        where movie.Description.Contains("Doom")
        select movie
    )
    .ToListAsync();

NOTE: keep in mind, that synchronous queries fetch the records "on demand" (while you're enumerating your results; when you stop enumerating - the rest of your records is not fetched), but ToListAsync() method causes all matching records to be fetched.

Starting with version 2.3.0, there's also a way to specify a custom FilterExpression for your Query or Scan operations:

var describedMoviesQuery =
    from movie in moviesTable
    .WithFilterExpression(new Amazon.DynamoDBv2.DocumentModel.Expression
    {
        ExpressionStatement = "attribute_exists (Description)"
    })
    select movie;

or even customize ScanOperationConfig, QueryOperationConfig, GetItemOperationConfig and DocumentBatchGet before the corresponding operations are executed:

var moviesQuery =
    (
        from movie in moviesTable
        where movie.Genre == "Thriller" && movie.Year > 2000
        select movie
    )
    .ConfigureQueryOperation(config => { config.ConsistentRead = true; });

NOTE: to see, what happens under the hood (what operations are actually executed etc.), add logging to your DataContext instance:

ctx.OnLog += s => Debug.WriteLine(s);

NOTE: Of course, not the whole set of LINQ methods can be translated into DynamoDB queries. A good practice for a LINQ provider, when it cannot translate the query to the database language, is to replace the unsupported methods with default Enumerable's implementations. That's what DataContext does. So, when you do something like this:

if (reviewsTable.Where(r => r.Rating == RatingEnum.Excellent).Any())
{
    ...
}

, all the reviews with excellent rating are actually loaded from DynamoDB and then enumerated.

This is why it would be more efficient to do this:

if (reviewsTable.Where(r => r.Rating == RatingEnum.Excellent).Select(r => r.Id).Any())
{
    ...
}

, to reduce the number of fields loaded from DynamoDB.

There're still some LINQ methods, that you cannot directly use in your queries. They are: .GroupBy(), .Join() and some others. These methods cause a multiple enumeration of underlying SearchReader, which is now intentionally not allowed (Would you like DataContext to silently retrieve your entities from DynamoDB multiple times? I don't think so.). But you always have an option to explicitly switch to Enumerable's functionality by specifying .AsEnumerable() or .ToArray() in your query.

A special word about .Count() method. Yes, DynamoDB supports Count operation. And my first thought was, of course, to support it too. But it appeared to be, that when you only ask DynamoDB to count your entities, it will cost you the same money, as if you retrieved all of them! So, in this situation (provided that you're using cache), it looks more reasonable to retrieve the entities, then count them and put them to cache. This is why .Count() method is not translated into DynamoDB's Count operation by now.

And about read consistency. By default, DataContext makes read-inconsistent queries against DynamoDB. That's simply because, when using caching, you mostly don't need any read consistency from DynamoDB! The most up-to-date data is always in the cache, and it's always read from the cache first. But, of course, in rare cases an entity might be already dropped from the cache, so it will be loaded from DynamoDB and might appear to be stale. If you're really worrying about this, you can set the ConsistentRead field to true in your DataContext's constructor:

public ReviewsDataContext() : base(DynamoDbClient, string.Empty)
{
    this.ConsistentRead = true;
}