Monday, January 11, 2010

Simple Savant v0.3 Released

I've just released Simple Savant v0.3 at CodePlex. It's been a number of months since the last release so this one includes a number of bug fixes as well as a several significant new features:

  • Typeless operations - All existing functionality has been exposed through methods not tied to .NET item types.
  • Asynchronous operations - All existing operations may now be invoked asynchronously.
  • Default property formatters are exposed for global replacement or reuse.

Each of these features is described in more detail below. See the release notes for a complete list of bug fixes and minor enhancements.

Typeless Operations

Most methods in the original Savant API required a generic type parameter. This made sense since object mappings could only be defined using attributes applied to your .NET types. This also allowed strong-typing of method parameters and return values. Here's an example:

   1: Guid personId = Guid.NewGuid();
   2: PersonItem person = Savant.Get<PersonItem>(personId);
   3: DateTime birthDate = person.BirthDate;

The biggest drawback of this approach is compile-time dependence on the generic parameter, which makes it difficult to invoke Savant methods from tools and infrastructure code that cannot explicitly reference item types in advance.

With Savant v0.3 we can rewrite the code above without referencing the PersonItem type:

   1: Guid personId = Guid.NewGuid();
   2: AttributeMapping itemNameMapping = AttributeMapping.Create("Id", typeof (Guid));
   3: ItemMapping mapping = ItemMapping.Create("Person", itemNameMapping);
   5: AttributeMapping dateMapping = AttributeMapping.Create("BirthDate", typeof (DateTime));
   6: mapping.AttributeMappings.Add(dateMapping);
   8: string[] propertyNames = mapping.AttributeMappings.Select(a => a.PropertyName).ToArray();
   9: PropertyValues values = Savant.GetAttributes(mapping, personId, propertyNames);
  10: DateTime birthDate = (DateTime)values["BirthDate"];

There is one significant difference between these examples. In the first example we're retrieving all attribute values mapped for the PersonItem type. In the second example we're retrieving only the "BirthDate" attribute. This makes perfect sense since that's the only attribute mapping added to our item mapping.

You may sometimes want to invoke the typeless methods while still working with .NET types. The ItemMapping and PropertyValues classes include helper methods to reduce the code required for this. The example below demonstrates these methods by building a complete mapping from the PersonItem type and then copying the returned values into a new instance of PersonItem:

   1: Guid personId = Guid.NewGuid();
   2: ItemMapping mapping = ItemMapping.Create(typeof(PersonItem));
   4: PropertyValues values = Savant.GetAttributes(mapping, personId);
   6: PersonItem person = (PersonItem)PropertyValues.CreateItem(typeof(PersonItem), values);
   7: DateTime birthDate = person.BirthDate;

Other benefits of dynamic mappings and typeless operations include the following:

  • Data entity types can be decoupled from the Savant assembly if desired. All the functionality available through Savant mapping attributes can be replicated with dynamically constructed mappings.
  • Data partitioning is now much easier with Savant. To use the same mapping across partitioned domains, simply change ItemMapping.DomainName value before invoking the typeless get, put, or select methods on Savant.

Asynchronous Operations

Any Savant method can now be invoked asynchronously using new extension methods that support lambda expressions. You'll need to import the Coditate.Savant.Async namespace to use these methods. Here's an example:

   1: using Coditate.Savant.Async;
   2: ...
   3: Guid personId = Guid.NewGuid();
   4: AsyncFunction<PersonItem> function = Savant.ExecuteAsync(s => s.Get<PersonItem>(personId));
   6: // do something else useful
   7: PersonItem person = function.EndInvoke();
   8: DateTime birthDate = person.BirthDate;

Global Formatter Replacement

Savant has supported custom property formatters since the first release. But these could only be specified for individual properties using mapping attributes. You couldn't, say, replace the formatter used for all DateTime properties without applying a CustomFormatAttribute to every DateTime property defined by your application. Nor could you access the installed formatters for use in other contexts.

Both of these are possible now that the formatter registry is exposed on SavantConfig. Here's how to get and use a default formatter:

   1: PropertyFormatter formatter = Savant.Config.Formatter;
   2: ITypeFormatter dateFormatter = formatter.GetFormatter(typeof (DateTime));
   3: string date = dateFormatter.ToString(DateTime.Now);

And here's how to replace one of the default formatters globally:

   1: PropertyFormatter formatter = Savant.Config.Formatter;
   2: ITypeFormatter dateFormatter = new MyCustomDateFormatter();
   3: formatter.SetFormatter(typeof(DateTime), dateFormatter);

No comments:

Header photo courtesy of: / CC BY-NC 2.0