Pragmatic Developer

Ali Özgür

Bookmark Blog

Add to Technorati Favorites

Google Talk

Chat with Ali Özgür

Purchase PragmaSQL from

Calendar

«  November 2008  »
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
View posts in large calendar

Tag Cloud

Don't show

    Authors

    Recent Comments

    Banners




    I've been examining Visual Web Gui for a while. VWG promises nice functionality with an innovative but very familiar paradigm. So I decided to test capabilities of VWG with a known domain to me as a software developer and implementor of Pragma Issue Tracker Module for Cuyahoga Framework.

    Pragma Issue Tracker (PrgIT) will try to meet the following basic requirements

    • Open source
    • Multi database support
    • Use NHibernate with Castle ActiveRecord as Model Layer
    • Use VisualWebGui as View and Controller Layers
    • Service based Authentication and Membership for integration with existing web applications.
    • Define Projects
    • Define Components for each project
    • Global and project based issue type definitions
    • Global and project based priority definitions
    • Link users for special roles for each project and/or component
    • Issue comments and attachments
    • Issue change history tracking and reporting
    • Issue linking
    • Custom issue tagging
    • Custom fields for projects and components
    • Issue work logging
    • Project and component versioning
    • Predefined dashboard reports
    • Custom reports
    • Functionality exposed via web services
    • Custom and programmable issue actions per project and/or component
    • Fulltext indexing and fulltext search
    • Access to issues with user friendly url's

    You can ask that why we need another open source issue tracker software, we already have open source issue trackers developed with Asp .Net meeting the requirements stated above? We do not actually need another issue tracker, PrgIT project was established simply to test the ecosystem composed of VisualWebGui, NHibernate and ActiveRecord, this is the main motivation behind this effort. 

     


    Posted in: PrgIT , VisualWebGui  Tags:

    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    Some PragmaSQL users were notifying me about a strange but deadly serious error caused after recovering from a server connection transport-level error. To reproduce the error follow these steps:

    1- Open PragmaSQL Editor and choose a connection from Saved Connections list to connect. I will assume that Default Database of the selected connection is DatabaseA 

    2- In the opened script editor change database to DatabaseB

    3- Open to Microsoft Sql Server Management Studio and from Management node select Activity Monitor. Find the process for DatabaseB created by PragmaSQL application and kill that process.

    4- Return to PragmaSQL editor and try to execute the select statement for the selected database, that is DatabaseB.

    5- You will get transport-level error after trying to execute the statement for the first time.

    6- Try to execute the statement again and this time you will succeed. But the statement was executed on the initial database, that is DatabaseA not in DatabaseB (selected one ). This error may cause very serious problems if you were trying to modify data you would simply modify the data in wrong database.

    The problem is actually with database change code inside PragmaSQL which is not really a bug but misusage of SqlConnection class's ChangeDatabase method. While I was coding the change database logic I did not realy realised that ChangeDatabase method does not modify the Initial Catalog property of the ConnectionString, actually there is no way to modify the ConnectionString property of the SqlConnection object once it is opened. SqlConnection object in the script editor is created and opened only after the user changes the selected server, database changes does not cause a new SqlConnection object to be created and opened, we simply change the database property of the existing SqlConnection object and that is it. I did not considered SqlConnection object's error recovery scnearios (transport-level error for example) at all. The fact is that SqlConnection class recovers from transport-level error very well but uses the ConnectionString property to reopen the connection, no matter what was the database before the error SqlConnection object always restores connection to Initial Catalog property specified in the ConnectionString.

    Very ugly bug, but I am happy to resolve that one. Upcoming 1.0.0.34 version will include solution for this issue.


    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5
    aliozgur posted on March 17, 2008 15:13

    What Wikipedia says about pragmatism.

    The primacy of practice (from Wikipedia)

    The pragmatist proceeds from the basic premise that the human capability of theorizing is integral to intelligent practice. Theory and practice are not separate spheres; rather, theories and distinctions are tools or maps for finding our way in the world. As John Dewey put it, there is no question of theory versus practice but rather of intelligent practice versus uninformed, stupid practice and noted in a conversation with William Pepperell Montague that "[h]is effort had not been to practicalize intelligence but to intellectualize practice". (Quoted in Eldridge 1998, p. 5) Theory is an abstraction from direct experience and ultimately must return to inform experience in turn. Thus an organism navigating his or her environment is the grounds for pragmatist inquiry.


    Posted in: Other  Tags:

    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    BlogEngine.NET version 1.3 has a syntax highlighting extension included but is in beta, so I looked around for another syntax highlighter, since this blog heavly uses code snippets. After looking for a while I found this syntax highlighter extension. This extension uses Wilco Bauwer's syntax highlighter library which is impressive. But the main problem with this extension is, it does not handle HTML tags and special HTML characters like &nbsp &lt and &gt very well. Tags and special characters are left as garbage after extension tries to highlight the code. You see something like this

    private voidTest&nbsp{

    <p>&nbsp;private&nbsp;int&nbsp;i=0;

    <p>}

    After inspecting SyntaxHighlightingExtension.cs file I saw that the extension matches the source code with a regular epression and feeds the Wilco's highlighter with the raw html (body). This was kind of incomlete implementation causing the side effect I mentioned above. We need to clean html tags and special characters from the raw html(body). So I changed Highlight method of the extension. The resulting method is something like this.

    001 private string Highlight(HighlightOptions options)
    002 {
    003  string parsed;
    004  uint id = NextCodeID();
    005  string name = options.Language; 
    006
    007
    008  HighlighterBase highlighter = GetHighlighter(name);
    009  if (highlighter != null)
    010  {
    011   name = highlighter.FullName;
    012   highlighter.Parser = htmlParser;
    013   
    014   string body = Regex.Replace(options.Code,@"<(?![!/]?[>\s])[^>]*>",String.Empty,RegexOptions.CultureInvariant| RegexOptions.IgnoreCase | RegexOptions.Singleline);  
    015   body = HttpUtility.HtmlDecode(body);
    016   parsed = highlighter.Parse(body);
    017   highlighter.ForceReset();
    018  }
    019  else
    020  {
    021   name += " (not highlighted)";
    022   parsed = options.Code;
    023  } 
    024
    025
    026  if (options.DisplayLineNumbers)
    027  {
    028   string[] lines = parsed.Split(new char[] { '\n' });
    029   StringBuilder outputBuffer = new StringBuilder(); 
    030
    031
    032   for (int i = 0; i < lines.Length; i++)
    033   {
    034    outputBuffer.AppendFormat(linenumberingTemplate, i+1, lines[i]);
    035   } 
    036
    037
    038   return string.Format(OutputTemplate, id, name, options.Title, outputBuffer);
    039  } 
    040
    041
    042  return string.Format(OutputTemplate, id, name, options.Title, parsed);
    043 }

    The change I made resides starting from line 12 and ending in line 21. I simply stripped out the html tags with a regular expression and then used HttpUtility.Decode method to decode special HTML characters and feed the parser with normalized body text and voila the extension started performing well.

    By the way I want to remind you that if you are using the default editor (TinyMCE) you should copy and paste your source code to a plain text editor like Notepad++ and the copy from Notepad++ and paste to TinyMCE. I think TinyMCE should consider to add something like Paste as PlainText functionality to their editor like FCKEditor.


    Posted in: BlogEngine.NET  Tags:

    Currently rated 5.0 by 2 people

    • Currently 5/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5

    Introduction

    Cuyahoga provides module developers with a simple to use wrapper classes for full text indexing with Lucene.NET.
    Although wrapper classes are easy to use these classes are not generic in nature. I mean
    we, as module developers, have to convert our custom content (be it static or dynamic)
    to a SearchContent object so that wrapper classes can index that content. Another similar limitation is when we perform search on the indexed content we can only get collection of SearchResult objects. I also want to mention that we, as module developers, have no control over which fields will be stored/unstored, which fields will be used as keywords or which fields will not be indexed while wrapper objects build the full text index.

    In this article I will try to show you how we can make Cuyahoga.Core fulltext indexing capabilities more generic by providing a seperate extension assembly.

    Background

    I created two web sites using Cuyahoga framework

    While developing these sites I needed to fulltext index some dynamic content, I mean content persisted in the database. After exploring the wrapper classes in Cuyahoga.Core.Search namespace I noticed the limitations of the present search architecture and decided to make these wrapper classes more generic by appliying .NET Generics and Reflection.

    Bo.Cuyahoga.Extensions.Search Namespace

    In order to extend Cuyahoga.Core.Search namespace functionality using .NET Generics and Reflection I copied all files to my project and started refactoring. I appended Ex to class names in order to distinguish extended wrapper classes from the original classes in Cuyahoga.Core.Search namespace.

    IndexBuilder<T> class

    Refactored generic version of Cuyahoga.Core.Search.IndexBuilder class. This class is
    used as a wrapper around Lucene.NET index building functionality. Take a closer look at BuildDocumentFromSearchContent DeleteContent methods. These methods were modified so that we can use reflection to retreive fields of content object.

     
    
    
    private Document BuildDocumentFromSearchContent(T searchContent)
    {
        Document doc = new Document();
        IList fields = SearchGenericUtils.GetSearchContentFields(typeof(T), searchContent);
        for(int i = 0; i < fields.Count ; i++)
        {
            SearchContentFieldInfo fi = fields[i];
            switch (fi.FieldType)
            {
                case SearchContentFieldType.Text:
                    doc.Add(Field.Text(fi.Name, fi.Value));
                    break;
                case SearchContentFieldType.UnStored:
                    doc.Add(Field.UnStored(fi.Name, fi.Value));
                    break;
                case SearchContentFieldType.UnIndexed:
                    doc.Add(Field.UnIndexed(fi.Name, fi.Value));
                    break;
                case SearchContentFieldType.Keyword:
                    doc.Add(Field.Keyword(fi.Name, fi.Value));
                    break;
                default:
                    break;
            }
        }
        return doc;
    } 
    
    
    public void DeleteContent(T searchContent)
    {
        if (this._rebuildIndex)
        {
            throw new InvalidOperationException("Cannot delete documents when rebuilding the index.");
        }
        else
        {
            this._indexWriter.Close();
            this._indexWriter = null; 
    
    
            // Search content key field uniquely identifies a document in the index.
            SearchContentFieldInfo ki = SearchGenericUtils.GetSearchContentKeyFieldInfo(typeof(T), searchContent);
            if (String.IsNullOrEmpty(ki.Name))
                throw new Exception("SearchContentKey Field not specified on target class!"); 
    
    
            Term term = new Term(ki.Name, ki.Value);
            IndexReader rdr = IndexReader.Open(this._indexDirectory);
            rdr.Delete(term);
            rdr.Close();
        }
    } 
    
    
    

    IndexQuery<T> class

    Refactored generic version of Cuyahoga.Core.Search.IndexQuery class. This class is used as a wrapper around Lucene.NET index search functionality. Find was modified to so that we can
    create instance of the content object and set property values via reflection.

     
    
    
    public SearchResultCollection<T> Find(string queryText, Hashtable keywordFilter, int pageIndex, int pageSize)
    {
        long startTicks = DateTime.Now.Ticks;
        //We get query fileds with reflection
        string[] qryFields = SearchGenericUtils.GetSearchContentQueryFields(typeof(T));
        if (qryFields.Length == 0)
            throw new Exception("No query field specified on target class!"); 
    
    
        Query query = MultiFieldQueryParser.Parse(queryText, qryFields, new StandardAnalyzer());
        IndexSearcher searcher = new IndexSearcher(this._indexDirectory);
        Hits hits;
        if (keywordFilter != null && keywordFilter.Count > 0)
        {
            QueryFilter qf = BuildQueryFilterFromKeywordFilter(keywordFilter);
            hits = searcher.Search(query, qf);
        }
        else
        {
            hits = searcher.Search(query);
        }
        int start = pageIndex * pageSize;
        int end = (pageIndex + 1) * pageSize;
        if (hits.Length() <= end)
        {
            end = hits.Length();
        }
        SearchResultCollection<T> results = new SearchResultCollection<T>();
        results.TotalCount = hits.Length();
        results.PageIndex = pageIndex; 
    
    
        //We get filds that will be populated as a result of the search via reflection
        string[] resultFields = SearchGenericUtils.GetSearchContentResultFields(typeof(T));
        for (int i = start; i < end; i++)
        {
          // We create instance of the target type with Activator and set field(property) values with reflection
            T instance = Activator.CreateInstance<T>();
            for (int j = 0; j < resultFields.Length; j++)
            {
                SearchGenericUtils.SetSearchResultField(instance, resultFields[j], hits.Doc(i).Get(resultFields[j]));
            } 
    
    
            //If target type implements ISearchResultStat we set Boost and Score properties of the instance
            if (instance is ISearchResultStat)
            {
                SearchGenericUtils.SetSearchResultField(instance, "Boost", hits.Doc(i).GetBoost());
                SearchGenericUtils.SetSearchResultField(instance, "Score", hits.Score(i));
            }
            results.Add(instance);
        } 
    
    
        searcher.Close();
        results.ExecutionTime = DateTime.Now.Ticks - startTicks;
        return results;
    } 
    
    
    

    SearchContentFieldAttribute Class

    .NET attributes are very neat implementation of declarative programming. We use SearchContentFieldAttribute to identify how we will perform fulltext indexing on our content. A field in our fulltext index directly maps to a property of our custom content type. For example if we want to index a Person class we specify which properties of this class will be indexed, how they are indexed and which properties will be set as a result of a fulltext index search with SearchContentFieldAttribute.

    SearchContentFieldAttribute is used to declare

    • Field Type: This declaration is needed because Lucene.NET has different field declarations. For details see Field class in
      Lucene.Net.Documents namespace
    • Key Field: Key field is used to uniquely identify our copntent. Only one key field declaration is allowed. If you declare two properties
      as key fields on the content class only the first one will be used.
    • Result Field: Content class properties with IsResultField=true will be automatically set after index search, other fields will not be set.
    • Query Field: Content class properties with IsQueryField=true will be used in index searches.Index search will be performed only on these
      properties

     
    
    
    namespace Bo.Cuyahoga.Extensions.Search
    {
        [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
        public class SearchContentFieldAttribute : Attribute
        {
            private SearchContentFieldType _fieldType;
            /// 
            /// Default is Text
            /// 
            public SearchContentFieldType FieldType
            {
                get { return _fieldType; }
                set { _fieldType = value; }
            } 
    
    
            private bool _isResultField = true;
            /// 
            /// Default is true
            /// 
            public bool IsResultField
            {
                get { return _isResultField; }
                set { _isResultField = value; }
            } 
    
    
            private bool _isQueryField = false;
            /// 
            /// Default is false
            /// 
            public bool IsQueryField
            {
                get { return _isQueryField; }
                set { _isQueryField = value; }
            } 
    
    
            private bool _isKeyField = false;
            /// 
            /// Default is false
            /// 
            public bool IsKeyField
            {
                get { return _isKeyField; }
                set { _isKeyField = value; }
            } 
    
    
            public SearchContentFieldAttribute(SearchContentFieldType fieldType)
            {
                _fieldType = fieldType;
            } 
    
    
        } 
    
    
        public enum SearchContentFieldType
        {
            Text,
            UnStored,
            UnIndexed,
            Keyword
        }
    } 
    
    
    

    SearchResultCollection<T> Class

    This is the generic version of the SearchResultCollection class found in Cuyahoga.Core.Search namespace.Nothing special to mention about this class

    ISearchResultStat interface

    We use this interface in order to specify that our custom content type will hold query statistics like Boost and Score. If our custom content type implements this interface value of the Boost and Score properties are set after an index query.

     
    
    
    public interface ISearchResultStat
    {
        float Boost{get;set;}
        float Score{ get;set;}
    } 
    
    
    

    ReflectionHelper Singleton

    Reflection helper class designed to improve performance

     
    
    
    /// 
        /// Singleton reflection helper
        /// 
        public sealed class ReflectionHelper
        {
            #region Static Fields And Properties 
    
    
            static ReflectionHelper _instance = null;
            static readonly object _padlock = new object();
            public static ReflectionHelper Instance
            {
                get
                {
                    lock (_padlock)
                    {
                        if (_instance == null)
                        {
                            _instance = new ReflectionHelper();
                        }
                        return _instance;
                    }
                }
            }
            
            #endregion Static Fields And Properties 
    
    
            #region Instance Fields And Properties
            
            private Dictionary> _cache;
            
            #endregion Instance Fields And Properties 
    
    
            #region CTOR 
    
    
            private ReflectionHelper()
            {
                _cache = new Dictionary>();
            } 
    
    
            #endregion //CTOR 
    
    
            #region Reflection Helper Methods 
    
    
            public SearchContentFieldInfo[] GetKeyFields(Type t)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
    
                List keyFields =  _cache[t].FindAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.IsKeyField == true;
                    });
                return keyFields.ToArray();
            } 
    
    
            public SearchContentFieldInfo[] GetKeyFields(Type t, object instance)
            {
                SearchContentFieldInfo[] fields = GetKeyFields(t);
                if (instance == null)
                    return fields; 
    
    
                GenericGetter getMethod;
                for (int i = 0; i < fields.Length; i++)
                {
                    getMethod = CreateGetMethod(fields[i].PropertyInfo);
                    object val = getMethod(instance);
                    fields[i].Value = val == null ? String.Empty : val.ToString();
                }
                return fields;
            } 
    
    
            public IList GetFields(Type t, object instance)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
                Listresult = new List(_cache[t].ToArray()); 
    
    
                if (instance == null)
                    return result;
                
                GenericGetter getMethod;
                for (int i = 0; i < result.Count; i++)
                {
                    SearchContentFieldInfo fi = result[i];
                    getMethod = CreateGetMethod(fi.PropertyInfo);
                    object val = getMethod(instance);
                    fi.Value = val == null ? String.Empty : val.ToString();
                }
                return result;
            } 
    
    
            public string[] GetQueryFields(Type t)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
                List < SearchContentFieldInfo > qryFields = _cache[t].FindAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.IsQueryField == true;
                    }); 
    
    
                
                List result = qryFields.ConvertAll(
                    delegate(SearchContentFieldInfo fi) 
                    {
                        return fi.Name;
                    }); 
    
    
                return result.ToArray();
            } 
    
    
            public string[] GetResultFields(Type t)
            {
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t); 
    
    
                List qryFields = _cache[t].FindAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.IsResultField == true;
                    }); 
    
    
    
                List result = qryFields.ConvertAll(
                    delegate(SearchContentFieldInfo fi)
                    {
                        return fi.Name;
                    }); 
    
    
                return result.ToArray();
            } 
    
    
            public void SetSearchResultField(string fieldName, object instance, object value)
            {
                Type t = instance.GetType();
                if (!_cache.ContainsKey(t))
                    AddTypeToCache(t);
                
                SearchContentFieldInfo field = _cache[t].Find(
                delegate(SearchContentFieldInfo fi)
                {
                    return fi.Name == fieldName;
                }); 
    
    
                if (field.Name != fieldName)
                    throw new Exception(String.Format("Field with name \"{0}\" not found on type \"{1}\"!", fieldName, t)); 
    
    
                GenericSetter setter = CreateSetMethod(field.PropertyInfo);
                setter(instance, Convert.ChangeType(value,field.PropertyInfo.PropertyType));
            } 
    
    
            #endregion //Reflection Helper Methods 
    
    
            #region Cache Operations
            private void AddTypeToCache(Type t)
            {
                if (_cache.ContainsKey(t))
                    return; 
    
    
                List fields = new List(); 
    
    
                PropertyInfo[] props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                for (int i = 0; i < props.Length; i++)
                {
                    PropertyInfo pi = props[i];
                    SearchContentFieldAttribute[] atts = (SearchContentFieldAttribute[])pi.GetCustomAttributes(typeof(SearchContentFieldAttribute), true);
                    if (atts.Length > 0)
                    {
                        SearchContentFieldInfo fi = new SearchContentFieldInfo();
                        fi.Name = pi.Name;
                        fi.FieldType = atts[0].FieldType;
                        fi.IsKeyField = atts[0].IsKeyField;
                        fi.IsResultField = atts[0].IsResultField;
                        fi.IsQueryField = atts[0].IsResultField;
                        fi.PropertyInfo = pi;
                        fields.Add(fi);
                    }
                }
                if (fields.Count > 0)
                    _cache.Add(t, fields);
            }
            #endregion //Cache Operations 
    
    
            #region Emit Getter/Setter 
    
    
            /* Source for CreateSetMethod and CreateGetMethod taken from
             * http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/
             */
            private GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
            {
                /*
                * If there’s no setter return null
                */
                MethodInfo setMethod = propertyInfo.GetSetMethod();
                if (setMethod == null)
                    return null; 
    
    
                /*
                * Create the dynamic method
                */
                Type[] arguments = new Type[2];
                arguments[0] = arguments[1] = typeof(object); 
    
    
                DynamicMethod setter = new DynamicMethod(
                    String.Concat("_Set", propertyInfo.Name, "_"),
                    typeof(void), arguments, propertyInfo.DeclaringType);
                ILGenerator generator = setter.GetILGenerator();
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                generator.Emit(OpCodes.Ldarg_1); 
    
    
                if (propertyInfo.PropertyType.IsClass)
                    generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
                else
                    generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); 
    
    
                generator.EmitCall(OpCodes.Callvirt, setMethod, null);
                generator.Emit(OpCodes.Ret); 
    
    
                /*
                * Create the delegate and return it
                */
                return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
            } 
    
    
            ///
            /// Creates a dynamic getter for the property
            ///
            private static GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
            {
                /*
                * If there’s no getter return null
                */
                MethodInfo getMethod = propertyInfo.GetGetMethod();
                if (getMethod == null)
                    return null; 
    
    
                /*
                * Create the dynamic method
                */
                Type[] arguments = new Type[1];
                arguments[0] = typeof(object); 
    
    
                DynamicMethod getter = new DynamicMethod(
                    String.Concat("_Get", propertyInfo.Name, "_"),
                    typeof(object), arguments, propertyInfo.DeclaringType);
                ILGenerator generator = getter.GetILGenerator();
                generator.DeclareLocal(typeof(object));
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                generator.EmitCall(OpCodes.Callvirt, getMethod, null); 
    
    
                if (!propertyInfo.PropertyType.IsClass)
                    generator.Emit(OpCodes.Box, propertyInfo.PropertyType); 
    
    
                generator.Emit(OpCodes.Ret); 
    
    
                /*
                * Create the delegate and return it
                */
                return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
            }
            #endregion //Emit Getter/Setter
        } 
    
    
        public delegate void GenericSetter(object target, object value);
        public delegate object GenericGetter(object target); 
    
    
    

    SearchGenericUtils Static Class

     
    
    
    public class SearchContentFieldInfo
        {
            public string Name;
            public string Value;
            public SearchContentFieldType FieldType;
            public bool IsResultField;
            public bool IsQueryField;
            public bool IsKeyField;
            public PropertyInfo PropertyInfo;
        } 
    
    
        internal static class SearchGenericUtils
        {
            internal static SearchContentFieldInfo GetSearchContentKeyFieldInfo(Type t, object instance)
            {
                SearchContentFieldInfo[] keyFields =  ReflectionHelper.Instance.GetKeyFields(t,instance);
                
                if (keyFields.Length == 0)
                    throw new Exception(String.Format("No key filed defined for type {0}!", t));
                
                if(keyFields.Length > 1)
                    throw new Exception(String.Format("Only one key filed allowed for type {0}!", t)); 
    
    
                return keyFields[0];
            } 
    
    
            internal static SearchContentFieldInfo GetSearchContentKeyFieldInfo(Type t)
            {
                return GetSearchContentKeyFieldInfo(t, null);
            } 
    
    
            internal static IList GetSearchContentFields(Type t, object instance)
            {
                if (instance == null)
                    throw new Exception("Instance parameter is null!"); 
    
    
                return ReflectionHelper.Instance.GetFields(t, instance);
            } 
    
    
            internal static string[] GetSearchContentQueryFields(Type t)
            {
                return ReflectionHelper.Instance.GetQueryFields(t);
            } 
    
    
            internal static string[] GetSearchContentResultFields(Type t)
            {
                return ReflectionHelper.Instance.GetResultFields(t);
            } 
    
    
            internal static void SetSearchResultField(object instance , string fieldName, object value)
            { 
    
    
                if (instance == null)
                    throw new Exception("Object instance parameter is null!");
                if (String.IsNullOrEmpty(fieldName))
                    throw new Exception("Field name is empty!"); 
    
    
                ReflectionHelper.Instance.SetSearchResultField(fieldName, instance, value);    
            }
        } 
    
    
    

    Using The Sample Code

    Our sample console application simply creates three PersonContent objects and full text indexes these objects. Then we perform queries on the indexed content.

    In the sample application PersonContent class will be used as our custom content type.The class looks like

     
    
    
    public class PersonContent
    {
        private string _id; 
    
    
        [SearchContentField(SearchContentFieldType.Keyword,IsKeyField=true)]
        public string Id
        {
            get { return _id; }
            set { _id = value; }
        } 
    
    
        private string _keyword;
        [SearchContentField(SearchContentFieldType.Keyword)]
        public string Keyword
        {
            get { return _keyword; }
            set { _keyword = value; }
        } 
    
    
        private string _fullName;
        [SearchContentField(SearchContentFieldType.Text,IsQueryField=true)]
        public string FullName
        {
            get { return _fullName; }
            set { _fullName = value; }
        } 
    
    
        private string _notes;
        [SearchContentField(SearchContentFieldType.UnStored,IsResultField=false,IsQueryField=true)]
        public string Notes
        {
            get { return _notes; }
            set { _notes = value; }
        } 
    
    
        private int _age;
        [SearchContentField(SearchContentFieldType.UnIndexed)]
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        } 
    
    
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Id = " + _id);
            sb.AppendLine("FullName = " + _fullName);
            sb.AppendLine("Age = " + _age.ToString());
            
            return sb.ToString();
        }
    } 
    
    
    

    NOTE: For details about the meanings of UnIndexed, UnStored, Text and Keyword read
    Lucene.NET documentation. SearchContentFieldType is a utility enumeration that provides us the ability to call appropriate methods of Field found in Lucene.Net.Documents namespace.

    Tips on Merging Extended Search Classes to Cuyahoga Core

    You do not need to merge these extensions directly to Cuyahoga.Core in order to use them.
    But if you want Cuyahoga.Core to handle full text indexing in a more generic way, you can simply replace IndexQuery, IndexBuilder, SearchCollection, ISearchable, IndexEventHandler and IndexEventArgs classes with their counterparts found in Bo.Cuyahoga.Extensions.Search namespace. And obviosuly you will have to refactor other parts using the old versions of the replaced classes. One final issue you must consider is that you will have to add attributes to SearchContent and SearchResult classes.

    Dependencies

    In order to compile this code you need

    • Cuyahoga.Core project
    • Lucene.Net.ddl
    • log4net.dll

    History

    • 27 March 2008:
      • Bug in DeleteContent method resolved
      • Exception is thrown in case of null getter or setter methods in ReflectionHelper class GetKeyFields, GetFields and SetSearchResultField methods
    • 07 March 2008: First version published
    • 10 March 2008:
      • ReflectionHelper class added for performance improvement.
      • SearchContentFieldInfo methods modified to call appropriate ReflectionHelper methods.
      • SearchContentFieldInfo is not a struct anymore.

    Posted in: .NET Development , CodeProject , Cuyahoga , Lucene.NET  Tags:

    Be the first to rate this post

    • Currently 0/5 Stars.
    • 1
    • 2
    • 3
    • 4
    • 5