2007/11/06

From The Book of Ideas: Searchable ActiveRecord-Entities

One of the feature requests that I hear most often from my users that they want a search function for the grids. I then show them a search form where they can specify field and values exactly. Most often, I then hear: "No, I meant a search field. I enter some text and the software shows all relevant entries." I usually answer by asking whether it should also save the climate and overcome poverty or if simple mindreading is sufficient. Ok, but last night I had an idea how searching with a single textfield interface can be accomplished without redeveloping that nasty thing for each view. I use the Castle stack for .Net for my applications, which means that my entities are created with Castle ActiveRecord and displayed in MonoRail web applications. The idea works like this: There is only one who knows which properties must be included into the search and this the developer that created the entities. Therefore we need some mechanism to specify the searchable properties. My idea was adding a Searchable attribute to the ActiveRecord classes' properties that need to be included in such searches:
    [ActiveRecord]
    public class Entity : ActiveRecordBase<Entity>
    {
        private int id;
        private string name;
        private string entityDesc;
        private string internalName;

        [PrimaryKey]
        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        [Searchable][Property]
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [Searchable(FriendlyName = "Description")][Property]
        public string EntityDesc
        {
            get { return entityDesc; }
            set { entityDesc = value; }
        }

        [Property]
        public string InternalName
        {
            get { return internalName; }
            set { internalName = value; }
        }
    }
 
The Searchable takes an optional parameter, FriendlyName that allows to specify a localized or other, more user-friendly name. The attribute can be applied to both properties and relations. If applied to a relation attribute (BelongsTo, HasMany, HasAndBelongsToMany etc.) the properties tagged in that type will be added to the search as well. Then there is a SearchMediator, which will create a NHibernate Criteria Query that will check for every search term whether it can be parsed by the datatype of a tagged property and adds it to the query if this is the case. The code below sketches how the query could be created:
public static T[] Search(string text)
{
    string[] words = text.Split();
    DetachedCriteria criteria = DetachedCriteria.For<T>();
    foreach (string word in words)
    {
        Disjunction group = Expression.Disjunction();
        foreach (PropertyInfo info in typeof (T).GetProperties())
        {
            if (info.GetCustomAttributes(typeof (SearchableAttribute), true).Length > 0)
                group.Add(Expression.Like(info.Name, word, MatchMode.Anywhere));
        }
        criteria.Add(group);
    }
    return ActiveRecordMediator<T>.FindAll(criteria);
}
This can be extended by many means. Examples include:
  • Allowing propertyName:searchTerm to narrow search to a specific property by its name or friendly name.
  • Allowing other operators than the colon like price>10.00 or name!Foo
  • Add globbing to specify whether exact matches or all matches should be found
  • Specify the MatchMode behavior for string properties as part of SearchableAttribute

Using that mechanism, I can add searching to almost any entity similar to validation. What do you think, would such a Search component benefit the Castle Framework?

That is only some thoughts. I didn't start implementing anything yet. However, anyone interested in helping is welcome, of course.

2 comments:

Darius Damalakas said...

The idea is logical, but, i don't get what is the idea of having to put a searchable attribute?

Do you have a requirement that a search must be made only on some specific properties?

Basically, what I say is that maybe users will want to search through all properties in all cases, and thus that attributed is rendered redundant, we could simply take all castle property and relation attributes and add that to criteria.

Of course, this way we run into problem that if object graph is too deep, the query gets very big. Probably this might be avoided by specifying object graph depth value when constructing search criteria.

The idea is good, but i wonder what are the exact bussiness needs and use cases that drive this functionality

Markus Zywitza said...

@Darius

Here is the user need:

I have users here looking at a list of 200+ entities of a type. I built in some filters to narrow that down, but in interviews I perceived that they don't want any filter forms, but "simply" searching through a single text field. It turns out that such a search functionality will be needed on 10+ types for my current application, so I need some generic solution.

Attribute Usage:
Even when specifying graph depth, I will have way to many properties to include them all in a generic search. Some of my entities have 20+ properties, if I including only one relation level, I come to 50+. I don't want to put 50 disjunctions in a single query because I fear that it kills my DBMS.

Please do not forget that this functionality is additional to other, specific filters. It is mainly a "type and go" requirement of lazy users.

-Markus