Getting Started With Simol

Simol makes it easy for .NET applications to store objects in SimpleDB. This section demonstrates how to define simple object mappings, save, load, and select objects.

Storing Objects

To start with let's create a Person class that's just about the simplest object we could save with Simol:

    public class Person
    {
        public Person()
        {
            Id = Guid.NewGuid();
        }

        public string Name { get; set; }

        [ItemName]
        public Guid Id { get; set; }
    }

The ItemName attribute tells Simol to use Person.Id as the unique identifier when saving to SimpleDB. Saving an instance of Person is very straightforward:

    var simol = new SimolClient(AwsAccessKeyId, AwsSecretAccessKey);
    var person = new Person {Name = "Jack Frost"};
    simol.Put(person);

Important: You'll need to sign up for a SimpleDB account at Amazon to get your own AWS id and secret key.

Running the code above results in a new Person domain with one item:

Id Name
cf5e2f47-99d0-4bdd-86ff-d02d7aaa9e92 Jack Frost


Simol creates new domains automatically when necessary. By default the domain name is set to the unqualified class name of the object being stored. You can customize the domain name using the Simol DomainName attribute.

Let's create a new Customer class extending Person and use the DomainName attribute to ensure that customers are also stored in the Person domain:

    [DomainName("Person")]
    public class Customer : Person
    {
        public List<string> PhoneNumbers { get; set; }
    }

Now we'll create a Customer with two phone numbers and save it to SimpleDB:

    var customer = new Customer
        {Name = "Frank Berry", PhoneNumbers = new List<string> {"770-555-1234", "678-555-5678"} };
    simol.Put(customer);

After running the code above our Person domain contains these two items:

Id Name PhoneNumbers
cf5e2f47-99d0-4bdd-86ff-d02d7aaa9e92 Jack Frost
50a60862-09a2-450a-8b7d-5d585662990b Frank Berry 678-555-5678, 770-555-1234


Note: Each element of the PhoneNumbers list is stored as a distinct attribute value with no commas.

Retrieving Objects

Here's how to retrieve the customer and person objects we stored in the previous section:

    var jackId= new Guid("cf5e2f47-99d0-4bdd-86ff-d02d7aaa9e92");
    var frankId = new Guid("50a60862-09a2-450a-8b7d-5d585662990b");
    Person jack = simol.Get<Person>(jackId);
    Customer frank = simol.Get<Customer>(frankId );

Even though we originally saved Frank Berry as a Customer we can also retrieve him as a Person. The extra PhoneNumber attributes returned from SimpleDB are simply discarded by Simol:

    var frankId = new Guid("50a60862-09a2-450a-8b7d-5d585662990b");
    Person frank = simol.Get<Person>(frankId);

We can also search for Frank's record using his phone number with a select query:

    string selectQuery = "select * from Person where PhoneNumbers = @PhoneNumbers";
    CommandParameter parameter = new CommandParameter("PhoneNumbers", "678-555-5678");
    List<Customer> customers = simol.Select<Customer>(selectQuery, parameter);

We can even search using multiple phone numbers at once. To do this simply add multiple parameter values and use an "IN" clause in your select query:

    var phoneNumbers = new List<string> { "678-555-5678", "770-555-1234" };
    var parameter = new CommandParameter("PhoneNumbers", phoneNumbers);
    string selectQuery = "select * from Person where PhoneNumbers in (@PhoneNumbers)";

    List<Customer> customers = simol.Select<Customer>(selectQuery, parameter);

Important: SimpleDb imposes a limit of 20 values per in clause.

We aren't storing much information about our customers yet. Let's add 3 more properties to our Customer class: BirthDate, Zipcode, and Age. Here is our new Customer class:

    [DomainName("Person")]
    public class Customer : Person
    {
        public List<string> PhoneNumbers { get; set; }

        [AttributeName("DOB")]
        public DateTime? BirthDate { get; set; }

        public string Zipcode { get; set; }

        [SimolExclude]
        public TimeSpan Age { 
            get
            {
                return DateTime.Now - (BirthDate ?? DateTime.Now);
            }
        }
    }

Note that we've customized our mapping for Customer.BirthDate using an AttributeName attribute. This tells Simol to store BirthDate in SimpleDB as "DOB". (We'll pretend our database administrator has already imported several million customer items into SimpleDB using "DOB" as the attribute name.)

And since Customer.Age is a calculated, read-only field, we've marked that property with the SimolExclude attribute so Simol ignores it completely.

Now let's pretend someone in our marketing department wants to send singing telegrams to customers on their birthday. They've selected a few zip codes to test the concept and would like to know how many customers with upcoming birthdays are in each area. To help them out we can run this select query:

    string selectQuery = "select count(*) from Person where Zipcode = @Zipcode and DOB between @StartDate and @EndDate";
    var zipParam = new CommandParameter("Zipcode", "30005");
    var startDateParam = new CommandParameter("StartDate", "BirthDate", new DateTime(2010, 1, 1));
    var endDateParam = new CommandParameter("EndDate", "BirthDate", new DateTime(2010, 2, 1));

    int count = (int)simol.SelectScalar<Customer>(selectQuery, zipParam, startDateParam, endDateParam);

You might wonder why it's necessary to provide the Customer generic parameter when running this select scalar query since we aren't actually returning any Customer objects. The reason is that when you execute select queries the command parameters are "bound" to item properties for proper formatting before sending to SimpleDB. This is also why we need to provide extra information for our "StartDate" and "EndDate" parameters.

The "Zipcode" parameter is automatically bound to Customer.Zipcode because the parameter and property share the same name. But we don't have any properties named "StartDate" or "EndDate" so we've explicitly bound those parameters to Customer.BirthDate. This tells Simol to use the exact same formatting rules that it uses for Customer.BirthDate when formatting the parameters. This is very important since SimpleDB stores all values as strings and has no understanding of numeric or data types.

Since our Customer.BirthDate property uses Simol's default date formatter Simol could infer proper formatting rules from the parameter values. But this method would break as soon as we applied custom formatting rules to Customer.BirthDate (such as storing only the year, month, and day rather than the complete date and time information).

Last edited May 27, 2011 at 2:54 PM by ashleytate, version 37

Comments

ashleytate May 20, 2011 at 9:48 PM 
@dontmailme_cr: See this discussion: http://simol.codeplex.com/discussions/224248

dontmailme_cr Sep 3, 2010 at 11:32 AM 
This is great.

one small problem. this doest not work with latest version of simple db dll
Error :
Could not load file or assembly 'AWSSDK, Version=1.0.9.0, Culture=neutral, PublicKeyToken=cd2d24cd2bace800' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
when i try to run it. i have to manually copy the older dll and then it started working.

i have AWSSDK version 1.0.11.0 installed.

ashleytate Mar 26, 2010 at 12:50 PM 
@Polyktor: That is strange behavior. I could not duplicate with IE 8 running in standard or compatibility mode. I don't have control over the exact html rendering so hopefully it was a Codeplex hiccup that is fixed.

Polyktor Mar 17, 2010 at 7:10 PM 
One weird comment. I'm viewing this page on IE 7 (Win Vista x64). The table that shows the example of multiple attributes added looks like this to me:

f5e2f47-99d0-4bdd-86ff-d02d7aaa9e92 Jack Frost
50a60862-09a2-450a-8b7d-5d585662990b Frank Berry , <-- no phone numbers shown

In Firefox it shows the proper table row:

50a60862-09a2-450a-8b7d-5d585662990b Frank Berry 678-555-5678, 770-555-1234

Very confusing for a newbie (like me)

ashleytate Jan 28, 2010 at 1:19 PM 
Glad you're finding it useful!

You should be able to accomplish what you want a couple of ways. You could either define a Type property on your class like this:

public Person {
public string Type {
get {return GetType().Name;}
set {}
}
}

Or just read and write your data using the typeless operations such as PutAttributes(ItemMapping, PropertyValues[]) and explicitly adding the Type mapping.

SoopahMan Jan 28, 2010 at 5:07 AM 
This is a lot better than I was hoping to find in .Net SimpleDB libraries! Thanks for writing this!

As a suggestion - sometimes it's nice to put everything related into a single Domain, to conserve Domains in a large system with a lot of objects. You still want to be able to capture for example, a list of Person objects even though they may be lumped-in with other objects. In these scenarios I like to store my SimpleDB objects with a "Type" Attribute that specifies the class name. Shorthand:

.Put("Person1", {Type: "Person", Name: "Joe"});

That way I can query for everything where Type = 'Person' and get a list of Person objects, even though I'm conserving my 100 Domain limit by putting many types in one place.

It would be nice if SimpleSavant supported this approach, probably as a Property setting on the SimpleSavant object like, "StoreTypeWithItem."