Today I spent a bit of time working on the data model for my fantasy soccer application.
I don't want to talk about the actual model today though, because I'm not really ready.
What I want to do is talk about a problem that occurred to me, namely Unit Of Measure.
Any real sport fan knows you need to know what kind of shape someone is in, did they put on too much weight in the off season? In cycling this stuff is deadly serious, Jan Ullrich was famous for coming back from winter, carrying "too much" weight, in fact so much so that it often took Jan until the 2nd to 3rd week of the Tour De France to get into optimal condition.
Anyway the point of all this is that weight has units, and if I tell you someone's weight is 120, you are none the wiser until I tell you the units (pounds or kilos).
Now if you have a database being populated from information gathered around the world you almost certainly need to deal with this sort of problem. In NZ you get Kilos, in the US pounds etc etc.
If you don't know the units in a particular row, you can't do meaningful comparisons, i.e. something like this makes no sense:
var heaviest = from athlete in ctx.Athletes orderby athlete.Weight descending select athlete;
Well ComplexTypes are good for this sort of thing i.e.
<ComplexProperty Name="UnitOfMeasure"> <Property Name="Value" Type="Decimal"/> <Property Name="Scale" Type="Decimal"/> <Property Name="Units" Type="String"/> </ComplexProperty> If you now define the athletes weight to be a UnitOfMeasure you know both the Weight and Units:
i.e. if you are operating in Kilos you say something like this:
athlete.Weight.Value = 90; athlete.Weight.Scale = 2.2M; athlete.Weight.Units = "kg";
Which is equivalent to this:
athlete.Weight.Value = 198; athlete.Weight.Scale = 1; athlete.Weight.Units = "lbs";
Because there are 2.2 lbs / kilo.
At this point the we can reason about the athlete' weight:
foreach (Athlete a in ctx.Athletes) Console.WriteLine("{0} weighs {1} {2}", a.Firstname, a.Weight.Value, a.Weight.Units);
Unfortunately sorting is still not possible.
However if you add a helper class like this:
public class ScaledAthlete { public Athlete Athlete; public Decimal ScaledWeight; }
And add this property to your partial ObjectContext class:
public static IQueryable<ScaledAthlete> ScaledAthletes{ get{ return from athlete in this.Athletes select new ScaledAthlete{ Athlete = athlete, ScaledWeight = athlete.Weight.Scale * athlete.Weight.Value}; } }
Then you can write code like this:
var heaviest = from a in Athlete.ScaledAthletes orderby a.ScaledWeight descending select a.Athlete;
foreach (Athlete a in heaviest) Console.WriteLine("{0} weighs {1} {2}", a.Firstname, a.Weight.Value, a.Weight.Units);
And it will work nicely, with all the real work happening in the database.
Some key points:
The thing that is nice about this approach is the scaling is built-in to the datasource (i.e. ScaledAthletes), so it is just there whenever you issue a query over ScaledAthletes.
At this point you might be surprise to find out there is a much easier way too.
You can re-write this using a let like this:
return from athlete in ctx.Athletes let weight = athlete.Weight.Value * athlete.Weight.Scale orderby weight descending select athlete;
Or my even without the let:
return from athlete in ctx.Athletes orderby athlete.Weight.Value * athlete.Weight.Scale descending select athlete;
Notice you don't need a helper class, a helper method, and you can have more control over the resulting query.
So why the big detour... well as I said in my statement of intent this is supposed to be warts and all, what you just saw is how I got there and what I learnt along the way.
Happy measuring.