Mapping a code table in BLToolkit
Question
In my database, there is one large "code" table with system code look-ups for values used all over the system. Like so:
[TableName("code_entries")] public class Code {
[MapField("code_nbr")][PrimaryKey, Identity] public int Id;
[MapField("code")] public string Value;
}
I am new to BLToolkit, and am hoping that there is a concept similar to the static Mappings I have seen, but that will allow me to easily map occurrences of these codes in other tables to their respective values. For instance:
[TableName("person")] public class Person {
[MapField("person_nbr")][PrimaryKey, Identity] public int Id;
[MapField("dob")][Nullable] public int BirthDate;
[MapField("eye_color")][Nullable] public int EyeColorCode;
[MapField("hair_color")][Nullable] public int HairColorCode;
}
If EyeColorCode and HairColorCode above map to values in the Codes table, can I create an easy way to map that data within the OR classes and obtain the whole object in a single query?
I'd like to end up with something like:
// person.Id = 1
// person.DOB = some date
// person.EyeColor = "Blue"
// person.HairColor = "Brown"
Solution
It's not really what you wanted but you could use Associations
so you could add this to your Person class
[Association(ThisKey="eye_color", OtherKey="code_nbr", CanBeNull=true)]
public Code EyeColor;
[Association(ThisKey="hair_color", OtherKey="code_nbr", CanBeNull=true)]
public Code HairColor;
And then do something like
from p in db.Person
select new
{
Id = p.Id,
DOB = p.BirthDate,
EyeColor = p.EyeColor.Value,
HairColor = p.HairColor.Value
};
Anyway these seem like the type of codes that almost never change I usually put these on the client at startup and then fill in the description when I display the data, makes everything a lot easier, and if I can't find an Id then I just refresh the collection
OTHER TIPS
Thanks David. I went with your approach, but modified it slightly to make it a little less painful form me. I added associations to my classes as in your example:
[Association(ThisKey = "EyeColorCode", OtherKey = "Id")] public Code EyeColor { get; set; }
[Association(ThisKey = "HairColorCode", OtherKey = "Id")] public Code HairColor { get; set; }
Then I wrote an extension method. It takes a new object, and merges all the writable properties into the source object. Using this, I don't need to specify every single property in my query, i.e.:
from p in db.Person
select p.PropertyUnion(new Person() {
{
EyeColor = p.EyeColor,
HairColor = p.HairColor
};
This saves me quite a bit of code for some of my more complex objects, and I think it is more readable. Here is the code for the extension method:
/// <summary>
/// Union by retrieving all non-null properties from source parameter and setting those properties on the instance object
/// </summary>
public static T PropertyUnion<T>(this T destination, T source) {
// Don't copy from a null object
if (Object.ReferenceEquals(source, null) || Object.ReferenceEquals(destination, null)) {
return destination;
}
// copy properties
foreach (var property in source.GetType().GetProperties()) {
if (!property.CanWrite || !property.CanRead)
continue;
var match = destination.GetType().GetProperty(property.Name);
if (!match.CanWrite || !match.CanRead)
throw new MethodAccessException("Matching property '" + match.Name + "' must allow both read and write operations.");
var value = property.GetValue(source, null);
if (value != null && !value.Equals(Activator.CreateInstance(property.PropertyType)))
match.SetValue(destination, value, null);
}
return destination;
}
Thanks again for your help!