Thursday, April 16, 2009

New Simple Savant Release

I've just released Simple Savant v0.2 at CodePlex. This version brings the library up to date with the latest Amazon SimpleDB features and also completes the baseline feature set. (See this post for an introduction to Simple Savant.) The new release adds support for:

This release also includes a couple of breaking changes to reduce the potential for ambiguous or unsafe behavior in certain circumstances:

  • Select operations now return all available results by default. Previously Savant returned just the first page of results by default.
  • Savant now only limits the number of requests sent to SimpleDB on select operations rather than attempting to return a precise number of results. This seemed the best approach considering the potential confusion when using Savant with selects containing the "limit" keyword. Use SelectCommand.MaxResultPages to limit the number of result pages requested from SimpleDB in a single call.
  • DeleteAttribute requests are no longer used to delete null item properties on put operations. This not only degraded performance but could also lead to inconsistent data states. There are now two configuration options available for null property handling. Null item properties can either be ignored--meaning you become responsible for explicitly deleting attributes for null properties on put operations OR Savant can manage this for you by storing null properties as a single null character (\0) in SimpleDB.

Partial Object Operations

Partial object operations are useful when you need to put, get, select, or delete a small number of item attributes. For example, if we were working with a Person domain and needed to update just the birth date and email address for a large number of people we could do so with the code below. The example first puts new values for the two attributes and then gets just the birth date.

   1: PropertyValues values1 = new PropertyValues(person.Id);
   2: values1["BirthDate"] = new DateTime(2000, 1, 1);
   3: values1["EmailAddress"] = "mike@example.com";
   4: savant.PutAttributes<PersonItem>(values1);
   5:  
   6: PropertyValues values2 = Savant.GetAttributes<PersonItem>(person.Id, "BirthDate");

Scalar Selects

Scalar selects not only provide support for count(*) queries, but also make it easy to read a single scalar value with a minimal amount of code. In the example below we're selecting just the birth date for a person with a specific email address. If this method is used with a select query that returns extra items or attributes they will be silently discarded.

   1: DateTime? birthDate = (DateTime?)
   2:     savant.SelectScalar<PersonItem>("select BirthDate from Person where EmailAddress = @EmailAddress",
   3:                                     new CommandParameter("EmailAddress", "mike@example.com"));

BatchPutAttributes

Amazon's new BatchPutAttributes operation supports transactional puts of attributes for multiple items in the same domain. This is used automatically when you invoke SimpleSavant.Put() with multiple item parameters. For example, we could ensure that Tom, Dick, and Harry are either all in or all out of SimpleDB with the code below.

   1: PersonItem tom = new PersonItem();
   2: PersonItem dick = new PersonItem();
   3: PersonItem harry = new PersonItem();
   4: savant.Put(tom, dick, harry);

Multi-valued Command Parameters

Finally, it's now possible to provide multiple command parameter values for use with "in" clauses. The parameter values are expanded to a comma-delimited list for execution against SimpleDB. In the code snippet below we're using this feature to count the number of people in our domain whose height is denoted in inches or meters.

   1: CommandParameter parameter = new CommandParameter("HeightUnit", null);
   2: parameter.Values = new List<object> {HeightUnit.Inches, HeightUnit.Meters};
   3: int count = (int) savant.SelectScalar<PersonItem>("select count(*) from Person where HeightUnit in (@HeightUnit)",
   4:                                     parameter);

15 comments:

Anonymous said...

Could you post a Delete example? I'm getting an exception "Expected type of [System.String] but was [System.Guid]." when specifying an instance to delete (attempting to delete a row, so to speak). Almost there on my project! Thanks!

Ashley Tate said...

It's pretty straightforward:

PersonItem person = new PersonItem {
Id = Guid.NewGuid()
};
savant.Put<PersonItem>(person);
savant.Delete<PersonItem>(person.Id);

See this post for the PersonItem definition: http://coditate.blogspot.com/2009/03/simple-savant-c-interface-to-amazon.html

The error indicates you are passing a Guid to Delete() but your item type specifies a String property as its ItemName.

timb said...

Hi there, sorry but does the 'limit' keyword work?

For example this...
select * from mydomain where Count > '0' order by Count desc limit 3

returns all the results within the where clause (about 100).

Thanks for your help :) (loving savant by the way)

Ashley Tate said...

@timb: Just set SelectCommand.MaxResultPages = 1. By default Savant retrieves and aggregates all results available for a query. So 'limit' only constrains the page-size, not the total results.

timb said...

@ashley - ah yes, that works a treat. Thanks very much! I had tried the maxpage thing before but I think I was setting to the the same value as the limit so that's what I was doing wrong there. By the way, how many results are there per page? Is this configurable?

timb said...

sorry don't answer that! You already answered it before. I need a coffee :)

Ashley Tate said...

@timb: You can only control page sizes by using the limit keyword in your query, not through the Savant API. (I believe the default page size is 100.)

timb said...

@Ashley, thanks, that makes perfect sense.

Thanks again for this. This will massively speed up the development of most websites I make these days. It's great fun to work with. Thanks for sharing.

Ashley Tate said...

@timb: Thanks for the kind words! I'm glad it's working well for you.

Brian said...

I think I'm doing something wrong here, but I can't see it:

[SavantExclude]
public string Example
{
get
{
return ("Hello");
}
}

This results in an error message saying "Only public, read/write properties may be used with Savant. The following non-conforming properties of type 'CloudData.Case' have been marked with SavantAttributes: 'Example'"

Any thoughts?
Great stuff! Thanks very much!

Ashley Tate said...

@Brian: This open issue explains the workaround:

http://simplesavant.codeplex.com/WorkItem/View.aspx?WorkItemId=4057

ldoolitt said...

Is there a way to turn off the auto caching? Great feature but in my setup this is just a hindrance.

Ashley Tate said...

@ldoolitt: You can disable caching by setting SavantConfig.Cache to null before instantiating SimpleSavant:

SavantConfig config = new SavantConfig{Cache=null};
SimpleSavant savant = new SimpleSavant(awsId, awsKey, config);

Sonic Soft said...

Hi,

I tried to use Simple Savant to store an object that have a property defined as List{Person}, where Person is another class on the project. It does saves it, but when I retrieve the object back the list is always null. Is there a limitation for properties defined as collections?

Thanks

PD: I used curly brackets for the list definition because blogger don't let me use the proper less than and greater than symbols...

Ashley Tate said...

@Sonic Soft: You would need to define a custom formatter for your Person class to make this work. Use the CustomFormatAttribute for this. In short, if you want a Person to be saved as a stringified attribute value you need to tell Simple Savant how to serialize it.

 
Header photo courtesy of: http://www.flickr.com/photos/tmartin/ / CC BY-NC 2.0