Now that we’ve done all the hard work creating hooks and interfaces in previous posts creating the actual Selector is relatively simple.
I’ll just put the code up first:
// Copyright 2010 (c) Planet Software Pty Ltd. This work is
// licenced under the Creative Commons Attribution 2.5 Australia License. To view
// a copy of this licence, visit http://creativecommons.org/licenses/by/2.5/au/
public class Selector : Control
{
#region Private Fields
private QueryObjectDataSource _dataSource;
private IProjectionBinder _control;
#endregion
public Selector()
{
}
#region Public Properties
public string DataSourceId
{
get;
set;
}
public string ControlId
{
get;
set;
}
public string ControlParameter
{
get;
set;
}
public string AdditionalFields
{
get;
set;
}
#endregion
#region Overrides
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
_dataSource = FindControlIterative(Page, DataSourceId) as QueryObjectDataSource;
if (_dataSource == null)
{
throw new ArgumentException("Could not find datasource");
}
_dataSource.QuerySelector += new EventHandler<SelectEventArgs>(DataSource_QuerySelector);
_control = FindControlIterative(Page, ControlId) as IProjectionBinder;
if (_control == null)
{
throw new ArgumentException("Could not find control");
}
}
#endregion
#region Event Handlers
private void DataSource_QuerySelector(object sender, SelectEventArgs e)
{
List<string> fields = new List<string>();
if (!string.IsNullOrEmpty(AdditionalFields))
{
fields.AddRange(AdditionalFields.Split(';'));
}
fields.AddRange(_control.DataBoundFields(ControlParameter));
if(!fields.Any())
{
throw new ArgumentException("No fields to be bound where defined");
}
e.Query = e.Query.SelectDynamic(UniqueFields(fields));
}
#endregion
private IEnumerable<string> UniqueFields(IEnumerable<string> fieldNames)
{
var existing = new List<string>();
foreach (string name in fieldNames)
{
string[] propertyNames = name.Split('.');
string property = propertyNames[propertyNames.Length - 1];
if (!existing.Contains(property))
{
existing.Add(property);
yield return name;
}
}
}
}
Most of it is fairly simple. Our selector needs a DataSourceId and ControlId defined to hook into the Query chain and also to find the required projection fields. Two other properties are defined as well:
- ControlParameter – this argument gets fed to the DataBoundFields method
- AdditionalFields – this can be used if your control can’t find certain fields or if there is a complicated relationship defined. (In my example in the first part I use it to access SortedMeta.Name)
The UniqueFields method is put in there to account for possible double ups – for example as stated before I used an additional field of SortedMeta.Name where the control would have returned Name. In this case the additional field takes precedence and is used instead of the simple Name property.
The actual projection happens on the e.Query.SelectDynamic() call. I would have a look at this link Stack Overflow - LINQ expression tree question for a simple implementation. In my case I have extended it to use caching, support for ‘Properties of properties’ (ie SortedMeta.Name) and also to use properties instead of fields when creating a dynamic class.
And that’s it. Adding a Selector control to a page and linking it up will help reduce the amount of data your pulling out of your database with minimal effort.
Print | posted on Sunday, 22 August 2010 7:46 PM