Categories
Uncategorized

An entry for the “Oh right, of course that doesn’t work” file…

I have a class called MultiValueDictionary<TKey, TValue> that implements IReadOnlyDictionary<TKey, ReadOnlyCollection<TValue>>.

So far so good. All of usual LINQ stuff works as expected.

Then I think, “Wouldn’t it nice if I could also cast that into a IReadOnlyCollection<KeyValuePair<TKey, TValue>> and get a flattened view of it”.

So I add the interface and wire up the methods as explicit implementations. It compiles cleanly and I open up my tests.

error CS1061: 'MultiValueDictionary<string, int>' does not contain a definition for 'ToList' and no accessible extension method 'ToList' accepting a first argument of type 'MultiValueDictionary<string, int>' could be found (are you missing a using directive or an assembly reference?)

What do you mean it doesn’t have a ToList method. That’s a well known extension method on IEnumerable<T>, which my class implements.

In fact, I implemented that interface twice. Once as IEnumerator<KeyValuePair<TKey, ReadOnlyCollection<TValue>>> and once as IEnumerator<KeyValuePair<TKey, TValue>>.

Oh…

Well back to the drawing board.

Categories
Chain

ORM Idea: Declarative Aggregates

Normally when we need aggregates (min, max, average, sum, etc.) we end up having to declare it in multiple places. First you need an object to hold the result of the query. Then you need to put the actual logic of the query into a view, inline SQL, or LINQ expression.

This alone isn’t too much of a problem. But each time the report needs to be changed, you have to find the class and the query so that you can modify them both. And if the query appears in multiple locations, then you have to duplicate the logic in each place.

But what if we moved the aggregates directly into the class?

[Table("Sales.EmployeeSalesView"]
public class SalesFigures
{
	[AggregateColumn(AggregateType.Min, "TotalPrice")]
	public decimal SmallestSale { get; set; }

	[AggregateColumn(AggregateType.Max, "TotalPrice")]
	public decimal LargestSale { get; set; }

	[AggregateColumn(AggregateType.Average, "TotalPrice")]
	public decimal AverageSale { get; set; }

	[CustomAggregateColumn("Max(TotalPrice) - Min(TotalPrice)")]
	public decimal Range { get; set; }

	[GroupByColumn]
	public int EmployeeKey { get; set; }

	[GroupByColumn]
	public string EmployeeName { get; set; }
}

Now if you want to get more information, say grouping by month, you can do it directly in the model.

What does this look like in action? Well that depends on the ORM. For Tortuga Chain, it looks like this:

var report = dataSource.From<SalesFigures>().ToCollection.ExecuteAsync();

A nice effect of this pattern is that the logic is easily reusable. For example, say you wanted two reports. One for the current month and one for the current year.

var monthlyReport = dataSource.From<SalesFigures>(new {SalesMonth = month, SalesYear = year}).ToCollection.ExecuteAsync();

var yearlyReport = dataSource.From<SalesFigures>(new {SalesYear = year}).ToCollection.ExecuteAsync();

This feature is part of Tortuga Chain 4.3, but I can see it being incorporated into any ORM that includes SQL generation.