I am back to blogging after a long pause – way too modest term for more than 7 months of break. 🙂 But you know the days when you are swamped too deep into the project and don’t do anything other than breathing and work. Everyone, almost everyone, goes through it. Anyway, back to business now. Today’s blog post is about mapping search results with Glass.Mapper pulled from search index(Lucene, SOLR etc.) to actual items in content database without writing single line of imperative code. If you don’t know about Glass.Mapper, it is an amazing OOM(Object-Object Mapping) and ORM(Object-Relational Mapping) tool that significantly cuts down both the development and testing effort through declarative programming. Check out its home to learn more about it.
Let’s talk about motivation first. I was dealing with a problem involving aggregate object where information needed to be retrieved seamlessly from various and diverse data sources including search index without spreading data access code into my models and views. Does’t it sound like classic case of data mapping problem? In fact, Sitecore 7 content search APIs similar approach where indexed content is filled into specified .Net object(typically into SearchResultItem or its derivative). And mapping is directed through IndexField, IgnoreIndexField and few other attributes. Now it begs an obvious question, why would I use Glass.Mapper? Consider following scenario:
Say you are dealing with an item with large number of fields and not all of them are being indexed(indexing comes at a cost so you like to index select item fields, for instance fields that appear in search results). And while processing search results, some of those non-indexed fields need to be referenced. One way is to make sure that the ID field gets indexed and then using ID, you retrieve related item from database. However the process could be non-transparent and messy, particularly if field is complex or nested field type and there are several fields. So instead of you pulling information from database with Sitecore APIs, Glass.Mapper can do it for you, with the help of mapping attributes. Consider following model for an article where its number, title and summary are retrieved from index and content and images are retrieved from database.
- [SitecoreType(AutoMap = true)]
- public class ArticleModel : ContentItemModel
- {
- [IndexField(“articleid”)]
- public virtual String Number { get; set; }
- [SitecoreField(FieldName = “ArticleContent”)]
- public virtual String Content { get; set; }
- [IndexField(“articletitle”)]
- public virtual String Title { get; set; }
- [IndexField(“articlesummary”)]
- public virtual String Summary { get; set; }
- [SitecoreField(FieldName = “ArticleImage”)]
- public virtual Image Image { get; set; }
- }
And add few lines of code in your search module and you have ArticleModel filled with information from index and database. As I may need to use mapping for any type, I have abstracted in under generic method “Map”. SitecoreService in following snippet is Glass type that determine which database(e.g. master, web) it is mapping against.
- public static IEnumerable<T> Map<T>(this IEnumerable<T> results, String sitecoreServiceName) where T : ContentItemModel, new()
- {
- if (results == null || (!results.Any()))
- return results;
- var sitecoreService = new SitecoreService(sitecoreServiceName);
- foreach (var result in results)
- sitecoreService.Map(result);
- return results;
- }
Mapping is immensely powerful and keeps your code flexible so it can evolve with nor or little impact on other components. In above example, if a business forces me to index ArticleContent field, I can simply replace SitecoreField attribute with IndexField. It could even alleviate burden on indexing if used smartly. In above example, if ArticleContent gets updated several times in a day and it is required to return only the latest content in search results, it can be achieved by by employing the most aggressive indexing strategy which is usually CPU and/or IO intensive. Or you can configure less aggressive indexing strategy and use mapping to pull the latest content from database before returning search results. Of course, content in index would be stale from few seconds to minutes but it is significantly less taxing on your server resources.
Mapping promotes loose coupling between layers and modules and hence make your code extensible and testable. In above example, the code that consumes Article type is completely unaware of underlying data source(s) and makes it easy to substitute mock data for testing purpose. Also it doesn’t need to be changed if field name in database or index changes.