LightSpeed 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:
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.
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