// TODO: This is a complicated method, and the fact that it directly uses reflection to read attribute
        // data indicates that it is a good candidate for refactoring. In the future, we should research migrating
        // to either a code-generated or database-driven approach for storing this data.

        public SearchQuery GetQuery <T>(T dto)
        {
            var query = new SearchQuery()
            {
                RuleSets = new List <SearchRuleSet>()
            };

            // Get the Master Index Type.
            var masterIndexTypeAttr = dto.GetType().GetCustomAttribute <MasterIndexTypeAttribute>(true);

            if (masterIndexTypeAttr != null)
            {
                query.TableName = masterIndexTypeAttr.MasterType.GetDescription();

                // Evaluate object properties.
                foreach (PropertyInfo prop in dto.GetType().GetProperties())
                {
                    EvaluateProperty(query, prop, dto);
                }

                // Add a field for master link.
                SearchQueryField masterLinkField;
                switch (masterIndexTypeAttr.MasterType)
                {
                case MasterIndexType.Name:
                    masterLinkField = new SearchQueryField {
                        Name = MasterIndexSettings.MASTER_LINK_FIELD, ColumnName = MasterIndexSettings.MASTER_NAME_LINK_FIELD
                    };
                    break;

                case MasterIndexType.Address:
                    masterLinkField = new SearchQueryField {
                        Name = MasterIndexSettings.MASTER_LINK_FIELD, ColumnName = MasterIndexSettings.MASTER_ADDRESS_LINK_FIELD
                    };
                    break;

                case MasterIndexType.Vehicle:
                    masterLinkField = new SearchQueryField {
                        Name = MasterIndexSettings.MASTER_LINK_FIELD, ColumnName = MasterIndexSettings.MASTER_VEHICLE_LINK_FIELD
                    };
                    break;

                case MasterIndexType.Property:
                    masterLinkField = new SearchQueryField {
                        Name = MasterIndexSettings.MASTER_LINK_FIELD, ColumnName = MasterIndexSettings.MASTER_PROPERTY_LINK_FIELD
                    };
                    break;

                // TODO: Others...

                default:
                    masterLinkField = new SearchQueryField();
                    break;
                }
                query.RuleSets.ForEach(s => s.Fields.Add(masterLinkField));
            }
            return(query);
        }
        /// <summary>
        /// Recursively generates query fields from the given property.
        /// </summary>
        private void EvaluateProperty(SearchQuery query, PropertyInfo prop, object dto)
        {
            // Find the Search attribute.
            var searchAttr = Attribute.GetCustomAttribute(prop, typeof(MasterIndexSearchAttribute)) as MasterIndexSearchAttribute;

            if (searchAttr != null && searchAttr.Searchable)
            {
                // If the property is not marked as including nested properties (or if it is a value type),
                // then create query fields using its value.
                if (!searchAttr.IncludeNestedProperties || prop.PropertyType.IsValueType)
                {
                    // Only create a field if it has a non-null RMS column name.
                    if (!String.IsNullOrWhiteSpace(searchAttr.RmsClassicColumnName))
                    {
                        // Get the value of the property.
                        string searchValue = GetPropertyValue(prop, dto);

                        // Create the query field.
                        var field = new SearchQueryField()
                        {
                            QueryValue = searchValue,
                            Name       = prop.Name,
                            ColumnName = searchAttr.RmsClassicColumnName,
                            Weight     = searchAttr.FieldWeight,
                            SearchType = searchAttr.SearchType,
                            Required   = searchAttr.Required
                        };

                        // Create the appropriate rule sets for the field's priority.
                        foreach (var priority in searchAttr.Priorities)
                        {
                            SearchRuleSet existing = query.RuleSets.FirstOrDefault(r => r.Priority == priority);
                            if (existing == null)
                            {
                                existing = new SearchRuleSet()
                                {
                                    Priority = priority,
                                    Fields   = new List <SearchQueryField>()
                                };
                                query.RuleSets.Add(existing);
                            }
                            existing.Fields.Add(field);
                        }
                    }
                }
                else
                {
                    // If the property is marked to include nested properties (or if it is a reference type), then
                    // recursively look through all of its properties to see if any of them should be included.
                    // TODO: This ignores the MasterIndexType attribute of the property's containing class. Therefore,
                    // this will assume that all column names defined by all nested properties exist on the table
                    // defined by the MasterIndexType on the root instance. For instance, if Organization uses table
                    // "dbo.organization", then Organization.Address.StreetAddress will use the same table even
                    // though StreetAddress is a member of a type that is configured to look at "dbo.masterAddress".
                    foreach (var nestedProp in prop.PropertyType.GetProperties())
                    {
                        EvaluateProperty(query, nestedProp, prop.GetValue(dto));
                    }
                }
            }
        }