Ninja data type mappings in LightSpeed

Mindscape LightSpeed NinjaLightSpeed has traditionally supported a wide variety of .NET primitive types, with built-in mappings to the equivalent database types. Sometimes, however, you’ll want to map a database column to a custom type, or persist a particular field in a special way.

For example, a common idiom in some legacy databases (especially older Oracle databases) is to represent a boolean value by a CHAR(1) column with values like ‘Y’ and ‘N’ or ‘T’ and ‘F’. In your domain model, of course, you don’t want to work with these strings: you want to work with a CLR Boolean.

One way to do this is to set the Generation option to FieldOnly, and write the property accessors by hand, using your preferred type. But this isn’t always an option. For example, it can cause problems with mappings to DTOs; or if you want to model a .NET TimeSpan by a MySQL or SQL Server time column, LightSpeed’s built-in handling of TimeSpans will get in the way. Plus, it means you have to create a partial class and write several whole lines of code for every property that needs the custom mapping. Bo-ring! Don’t you guys know Starcraft 2 is out?

Enter ninja data type mappings.

In LightSpeed 3.11 (and the latest LightSpeed 3.1 nightly builds), you can customise how LightSpeed maps database values to CLR values and vice versa. This involves two steps: first, writing some conversion code to define the type mapping, and second, telling LightSpeed to use this mapping for a particular field.

Defining a ninja data type mapping

To define a data type mapping, you need to implement the new IFieldConverter interface. IFieldConverter has two methods, one for each conversion direction. Here’s how an IFieldConverter might look for the Y/N boolean example.

public class YesNoConverter : IFieldConverter
{
  public object ConvertFromDatabase(object databaseValue)
  {
    string s = (string)databaseValue;
    return s == "Y";
  }
 
  public object ConvertToDatabase(object value)
  {
    return ((bool)value) ? "Y" : "N";
  }
}

And that’s it for the mapping. Now we need to tell LightSpeed to use it on our favourite fields. The way you do this depends on whether you’re writing code by hand, or using the designer.

Applying a mapping to a field in code

To apply a mapping to a field in code, use the new ColumnAttribute.ConverterType property, passing the type of your IFieldConverter implementation:

[Column(ConverterType = typeof(YesNoConverter))]
private bool _yesNo;
 
public bool YesNo
{
  get { return _yesNo; }
  set { Set(ref _yesNo, value, "YesNo"); }
}

Voila! Job done.

Applying a mapping to a property in the designer

The designer surfaces ninja data type mappings in a slightly different way. Instead of applying the mapping separately to each property that needs it, you create a user-defined type that bundles up a CLR property type (like boolean), a database column type (like string) and the mapping between them. You can then select that user-defined type just as if it were a built-in type. Let’s take a look.

First, we create the user-defined type. This is done via the LightSpeed Model Explorer (View > Other Windows > LightSpeed Model). Right-click the model node, choose Add New User Defined Type, give it a name by which you’d like to refer to it in the Data Type drop-down, and enter the relevant details:

Let’s quickly run over the entries here:

  • CLR Type Name: This is the type that you want to use in your domain model. Your IFieldConverter.ConvertFromDatabase should be returning this type, and ConvertToDatabase should be expecting this type.
  • Is Value Type: This should be true if the CLR type is a value type, otherwise false (typically if the CLR type is System.String). This is relevant only if you have nullable instances of this type.
  • Name: How you want to refer to it in the Data Type drop-down.
  • Converter Type: The type name of your IFieldConverter implementation. This doesn’t have to be fully-qualified, but if it’s not, then it has to be in the same namespace as your model classes, or in one of the model’s imported namespaces.
  • Data Type: The column type in the database. Your IFieldConverter.ConvertFromDatabase should be expecting this type, and ConvertToDatabase should be returning this type. Here, it’s used when you sync the model and database, so that you don’t get spurious “change column YesNo to Boolean” suggestions.
  • Is Standard Data Type: Set this to false if you need to want the column type to be a database special (like time) rather than one of the LightSpeed built-in data types. You can then enter the database type name instead of the LightSpeed data type.

Okay, now we’ve created our YesNo user-defined type with all the code generation, database synchronisation and runtime mapping information nicely bundled up together, how do we apply it to an entity property? Simple: just select the property and choose the user-defined type from the Data Type drop-down:

And there it is, done. LightSpeed will now generate a boolean property, but map it to a string column. Of course, now we’ve set up the user-defined type, we can apply it to as many properties as we want, which makes this really convenient for mapping patterns that are used widely across a database.

Want to take it for a spin? Grab the latest nightly of the free Express edition from the Downloads page, or update your Professional edition from the store.

Tagged as LightSpeed

5 Responses to “Ninja data type mappings in LightSpeed”

  • A generic version of that interface would be nice, e.g:

    interface IFieldConverter

  • A generic version of that interface would be nice, e.g:

    interface IFieldConverter<TDatabase, TModel>

  • Hi Martin,

    You’re right, that would be nice, but unfortunately the C# type system meant that wasn’t practical. For LightSpeed code to use a generic interface, it needs to know the types at compile time, and we don’t: we can only call the IFieldConverter methods with object arguments.

    What we could do is provide a generic abstract base class, and implement IFieldConverter in that base class. That way our implementation would take care of the tedious casting boilerplate and your overrides of the abstract methods would be able to deal with strong types. (This is how we internally deal with EntityHolder and EntityCollection objects, which have the same issue that we don’t know the type parameters at LightSpeed compile time.) How does that sound?

  • The generic abstract class will be in the 3.11 release. It’s called FieldConverter and will be in the new FieldConverters namespace. (The weak-typed interface will still be there for those as want it.) Thanks for the suggestion!

  • […] http://www.codeproject.com/KB/vb/moneyDatatype.aspx5 minutes later following Ivan’s article: http://www.mindscape.co.nz/blog/index.php/2010/08/22/ninja-data-type-mappings-in-lightspeed/public class MoneyConverter : IFieldConverter { public object ConvertFromDatabase(object […]

  • Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top