Custom collections and databinding, part 2: filtering.

0

Posted on : 08-11-2005 | By : matteosp | In : .Net Programming

In my last post I presented myimplementation of a class that incapsulates the
logic for supporting the databing of a generic custom collection.

Now I want to discuss the way I implemented the capability to filter data presented the consumers of my class. What I was looking for and I obtained is the possibility to apply a filter in the way of a complex filter expression like ” AnyProperty = ‘AnyValue’ AND AnotherProperty > = ‘AnotherValue’ AND NOT ThirdProperty LIKE ‘%ThirdValue%’ “.

I’m going to present my solution and I hope to read your comments, opinions,
and so on. Remember that my class, CollectionView
, accept as source any class that implements ICollection and that consequently
I must be able to apply that kind of filter expression to any kinf of object.

Consider the example:

ArrayList list = new ArrayList();list.Add(new Blogger(”Albert Einstein”, “blogs.genies.org/albert”, 45));

list.Add(new Blogger(”Jack Keruoac”, “blogs.beatfoundation.org/jk”, 24));

list.Add(new Blogge(”Henry Miller”, “blogs.lesparisiennes.fr/henry”, 36));

CollectionView view = new CollectionView(list);

view.FilterExpression = “Age >= ‘36′ AND BlogUrl LIKE ‘blogs.beatfoundation%’ AND NOT Name LIKE ‘Alb%’”;


in which Blogger is a very simple class with only 3 properties:
public int Age,
public string BlogUrl and
public string Name.

I divided the logic needed to apply the filter into 3 classes: CollectionViewFilter,
ConditionEvaluator, ExpressionEvaluator.
All these 3 classes are declared internal in my class library as the only
property CollectionView exposes for applying the filter is a string FilterExpression {get; set;}.
Let’s examine these classes.

CollectionViewFilter.

This class is the entry point for the filtering process. It contains the
routines needed to extract from the filter expression the tokens that represent
the single conditions. The filter expression needs to be passed to constructor
and the tokens extraction is done with the help of a regular expression. So
considering the example above, it split that filter expression in these 3
tokens:

  • Age >= ‘36′
  • BlogUrl LIKE ‘blogs.beatfoundation%’
  • Name LIKE ‘Alb%’

The class exposes only one public method that is: public bool MatchObject(object obj). The purpose of this method is obviouisly returning a bool value that indicates if the object in argument meet all the conditions (expressed by the 3 tokens) or not. I do this by extracting from every token the property name (i.e.: BlogUrl), the operator (LIKE) and the property value (’blogs.beatfoundation%’). I delegate the process of verifying if the property BlogUrl of any instance of the Blogger class is LIKE ‘blogs.beatfoundation%’ to the ConditionEvaluator class.

ConditionEvaluator.

This logical task accomplished by this class is quite simple, I will not discuss over it’s implementation. It accept in the constructor the property name, the operator and the property value that CollectionViewFilter class extracts from each token, then it performs these steps:

  • Discover via reflection the property descriptor for the specified property
    name.
  • Receive the instance of the object that needs to be matched with the condition
    (the same instance received by CollectionViewFilter
    in MatchObject()
    method, in the case of my example an instance of the Blogger class.)
  • Perform the necessary comparison based on: the property type, the specified
    operator and the jollychars if any in the value.
  • Returns a bool value indicating if the passed instance meets the condition
    (BlogUrl LIKE ‘blogs.beatfoundation%’).

CollectionViewFilter repeats the operation for each token it extracted from the filter expression. So it use ConditionEvaluator for evaluate on the instance passed to MatchObject() method all the conditions expressed by the 3 tokens. This will cause the original filter expression

Age >= ‘36′ AND BlogUrl LIKE ‘blogs.beatfoundation%’ AND
NOT Name LIKE ‘Alb%’
“;

to be translated is a string like:

true AND true AND NOT true” (the case of
new Blogger(”Albert Einstein”, “blogs.genies.org/albert”, 45)
).

ExpressionEvaluator .

Now, how can I simply translate a string like the one obove (or a more
complex one) in single evalutaed bool value? Why the magic C# team didn’t
implemented an Eval function like the one JScript has? And the I told to my
self: “Hey! wait a moment. As I can compile some JScript statement I can get
the Eval function in my C# runtime!!”

And I did it this way in my ExpressionEvaluator class,
look at this snippet:
private static object evaluator = null;private static Type evaluatorType = null;

private static readonly string jsSource =

@”package ExpressionEvaluator

{

class Evaluator

{

public function Eval(expr : String) : String

{

return eval(expr);

}

}

}”;

static ExpressionEvaluator()

{

ICodeCompiler compiler = new JScriptCodeProvider().CreateCompiler();

CompilerParameters parameters = new CompilerParameters();

parameters.GenerateInMemory = true;

CompilerResults results = compiler.CompileAssemblyFromSource(parameters, jsSource);

Assembly assembly = results.CompiledAssembly;

evaluatorType = assembly.GetType(”ExpressionEvaluator.Evaluator”);

evaluator = Activator.CreateInstance(evaluatorType);

}
The JScript statement contained in jsSource is compiled only once in the
static constructor (need a reference to microsoft.jscript.dll) and is available
for functions like:
public static bool EvalToBoolean(string statement){

object o = EvalToObject(statement);

return bool.Parse(o.ToString());

}

public static object EvalToObject(string statement)

{

BindingFlags flags = BindingFlags.InvokeMethod;

object[] args = new object[] { statement };

return evaluatorType.InvokeMember(”Eval”, flags, null, evaluator, args);

}
This made my class CollectionViewFilter able to
simply resolve “true AND true AND NOT true” into “false”.The entire process is launched by CollectionView whenever
it has to build is inner source from the ICollection
passed in his constructor or when is property FilterExpression
changes to determine which of the elements in the source collection should be
passed to the dataconsumer.

UPDATE: I forgot a little thing: in order to make a string like
“true AND true AND NOT true” evaluable by the JScript routine you have to
replace the logical operators, in particular you must replace “AND” with
“&&”, “OR” with “||”, “NOT” with “!”.

Custom collections and databinding.

0

Posted on : 05-11-2005 | By : matteosp | In : .Net Programming

I always prefer custom business objects to .Net Datasets and Datatables when I write data representation logic for my web apps. So data abstraction layers I write are plenty of classes representing business entities and type-safe custom collections for entities enlisting. While collecting instances in a custom collection is not an hard task at all, provide to a collection functionalities like complex databinding, sorting and filtering is a little bit harder. Furthermore incapsulating such as capibilities in only one generic class requires a bit of time… But now I got it! I have this generic class (I call it CollectionView) that:

  • accepts as source any class implementing ICollection interface
  • generates a set of bindable columns based on the Type of the objects collected by the source.
  • allows the manipulation of the columns set (reordering, visibility, etc)
  • supports sorting and filtering
  • is a valid datasource for .Net WebControls

Let’s take a look to how I did this. First of all class signature and construtors:

public class CollectionView : ITypedList, IEnumerable, ICollection { public CollectionView(ICollection collection) { m_collection = collection; } public CollectionView(ICollection collection, Type collectionItemsType) { m_collection = collection; m_collectionItemsType = collectionItemsType; } // }

CollectionView implements 3 interfaces: ITypedList, IEnumerable, and ICollection. IEnumerable is obviously required for a complex databinding and ICollection (through Count property) allows operation such as paging. ITypedList allows to notify the dataconsumer (the web control that CollectionView will be bound to) which are the properties of the source collection items we want to be considered in the binding process. To be more precise: during the databinding process the dataconsumer invokes the GetItemProperties() method on ITypeList and receive the collection of property descriptors that it will use to bind it’s items (i.e: DataGrid items) to the collection items. Here ITypedList declaration:

public interface ITypedList { PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors); string GetListName(PropertyDescriptor[] listAccessors); }

Looking at the constructors you can understand that as the source collection is referenced through ICollection is possible to specify as source almost all the kind of collection you can have in the Framework. Optionally, also the Type of objects the source contains can be explicitly declared. This optional declaration is important because influences the way CollectionView builds the set of bindable columns and in turn this columns set can influence the PropertyDescriptorCollection instance returned by the GetItemProperties() method we’ve just seen. It will soon be more clear as I’m going to explain it. The return value of GetItemProperties() could have been exactly the result of a query for the public properties exposed by items in the source collection. But I decided to add the possibility, for the caller of CollectionView, to reorder these properties or to hide some of them. So what I’ve done is:

  • I created a class named CollectionViewColumnSet that is a type-safe collection (another one!) of CollectionViewColumn objects and that is exposed by CollectionView through the Columns property.
  • This column set is generated (the first time the Columns property is accessed) querying the public properties of the items in the source collection and every CollectionViewColumn will map one of these properties.
  • CollectionView callers are able to reorder the columns or hide some of them through properties such as Visible (bool) and Ordinal (double) exposed by CollectionViewColumn class.
  • When the dataconsumer calls GetItemProperties() the PropertyDescriptorCollection to be returned is build reflecting the state of each column in the column set.

Here the 2 routines I wrote to query the properties of the items in the source collection (DiscoverPropertyDescriptors) and to build the column set (EmitColumnsFromProperties):

private PropertyDescriptorCollection DiscoverPropertyDescriptors() { if (m_collectionItemsType != null) return TypeDescriptor.GetProperties(m_collectionItemsType); else { // if caller has not explict declared the Type of the items in the source collection // I try to discover if by my self: object item = GetAnyItemFromTheSourceCollection(); if (item != null) return TypeDescriptor.GetProperties(item); else { // in this case the dataconsumer will not be able to complete the binding process // but I can’t do anything abount that. May be I should throw an exception… PropertyDescriptor[] properties = new PropertyDescriptor[0]; return new PropertyDescriptorCollection(properties); } } } private CollectionViewColumnSet EmitColumnsFromProperties() { CollectionViewColumnSet cols = new CollectionViewColumnSet(); PropertyDescriptorCollection descriptors = DiscoverPropertyDescriptors(); foreach (PropertyDescriptor propertyDesc in descriptors) { CollectionViewColumn col = new CollectionViewColumn(propertyDesc.Name, propertyDesc); col.Ordinal = cols.Count; cols.Add(col); } return cols; }

Now you can understand what I meant when I was saying that the optional explicit declaration (with the second overload of the constructor) of the Type of the items in the source collection is important: in the case in which the Type is declared I call TypeDescriptor.GetProperties() passing it as argument, in the second case I have to try to find an item in the collection and use this as argument. Is important to know that: taken an instance of any class fooInstance the results of TypeDescriptor.GetProperties(fooInstance) and TypeDescriptor.GetProperties(fooInstance.GetType()) can be different. In fact, and I report what the Framework documentation says, the properties for a component can differ from the properties of a class, because the site can add or remove properties if the component is sited.

To complete the column set life-cycle, this is the implementation of ITypedList.GetItemProperties() method in which I reflect the column set state to the properties returned to the dataconsumer:

public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { return ReflectPropertiesFromColumns(); } private PropertyDescriptorCollection ReflectPropertiesFromColumns() { ArrayList descriptors = new ArrayList(); foreach (CollectionViewColumn col in this.Columns) { if (col.Visible) descriptors.Add(col.PropertyDescriptor); } PropertyDescriptor[] properties = new PropertyDescriptor[descriptors.Count]; descriptors.CopyTo(properties, 0); return new PropertyDescriptorCollection(properties); }

I have another couple of interesting things to tell about managing the properties-columns and about filtering the source collection, but it’s 4.00 AM so: to be continued…