601 lines
21 KiB
C#
601 lines
21 KiB
C#
///////////////////////////////////////////////////////////
|
|
// SearchResultList.cs
|
|
// Implementation of Class SearchResultList
|
|
// CSLA type: Read only collection
|
|
// Created on: 19-April-2005
|
|
// Object design: John
|
|
// Coded: 19-April-2005
|
|
///////////////////////////////////////////////////////////
|
|
|
|
using System;
|
|
using System.Data;
|
|
using GZTW.Data;
|
|
using CSLA.Data;
|
|
using CSLA;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
|
|
|
|
//using log4net;
|
|
namespace GZTW.AyaNova.BLL
|
|
{
|
|
/// <summary>
|
|
/// Read only collection of <see cref="SearchResultList.SearchResultListInfo"/> objects
|
|
/// </summary>
|
|
[Serializable]
|
|
public class SearchResultList : ReadOnlyCollectionBase
|
|
{
|
|
// Create a logger for use in this class
|
|
//private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
//case 981
|
|
/// <summary>
|
|
/// List of object id's if performing GetPickListForObjectType search
|
|
/// </summary>
|
|
public List<Guid> ListPickListID = new List<Guid>();
|
|
|
|
#region Data structure
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Serializable]
|
|
public struct SearchResultListInfo
|
|
{
|
|
|
|
internal Guid mRootObjectID;
|
|
internal RootObjectTypes mRootObjectType;
|
|
internal Guid mAncestorRootObjectID;
|
|
internal RootObjectTypes mAncestorRootObjectType;
|
|
internal string mExtract;
|
|
internal string mDescription;
|
|
internal float mRank;
|
|
internal GridNameValueCellItem mSource;
|
|
|
|
|
|
|
|
//Public properties
|
|
/// <summary>
|
|
/// Object id most closely associated with search result (may not be a directly openable object, that's what ancestor style is for)
|
|
/// </summary>
|
|
[Display(DisplayType.Hidden)]
|
|
public Guid RootObjectID {get{return mRootObjectID;}}
|
|
|
|
/// <summary>
|
|
/// Object type most closely associated with search result
|
|
/// </summary>
|
|
[Display(DisplayType.Hidden)]
|
|
public RootObjectTypes RootObjectType {get{return mRootObjectType;}}
|
|
|
|
/// <summary>
|
|
/// Object to open to view this search results original record
|
|
/// If this object is not a child or grandchild, then this is the same as RootObjectID
|
|
/// </summary>
|
|
[Display(DisplayType.Hidden)]
|
|
public Guid AncestorRootObjectID {get{return mAncestorRootObjectID;}}
|
|
|
|
/// <summary>
|
|
/// Object type to open to view this search results original record
|
|
/// </summary>
|
|
[Display(DisplayType.Hidden)]
|
|
public RootObjectTypes AncestorRootObjectType {get{return mAncestorRootObjectType;}}
|
|
|
|
/// <summary>
|
|
/// A brief excerpt automatically generated that tries to show the most relevant block of text
|
|
/// in the search result for the given search string
|
|
/// </summary>
|
|
[Display(DisplayType.Text)]
|
|
public string LT_SearchResult_Label_Extract {get{return mExtract;}}
|
|
|
|
/// <summary>
|
|
/// Description of search result
|
|
/// </summary>
|
|
[Display(DisplayType.Text)]
|
|
public string LT_SearchResult_Label_Description {get{return mDescription;}}
|
|
|
|
/// <summary>
|
|
/// Ranking of search result in order of how closely it matches the searched for string of text
|
|
/// See <see cref="ExtractAndRank"/> class for an explanation of this value
|
|
/// </summary>
|
|
[Display(DisplayType.DecimalNumber)]
|
|
public float LT_SearchResult_Label_Rank{get{return mRank;}}
|
|
|
|
|
|
internal SmartDate mCreated;
|
|
/// <summary>
|
|
/// Date and time object found that matches was created
|
|
/// </summary>
|
|
[Display(DisplayType.DateTime)]
|
|
public object LT_Common_Label_Created {get{return mCreated.DBValue;}}
|
|
|
|
|
|
internal SmartDate mModified;
|
|
/// <summary>
|
|
/// Date and time object found that matches was last modified
|
|
/// </summary>
|
|
[Display(DisplayType.DateTime)]
|
|
public object LT_Common_Label_Modified {get{return mModified.DBValue;}}
|
|
|
|
|
|
|
|
//Case 194
|
|
|
|
//internal Guid mCreator;
|
|
//[Display(DisplayType.ListUsers)]
|
|
//public Guid LT_Common_Label_Creator {get{return mCreator;}}
|
|
|
|
//internal Guid mModifier;
|
|
//[Display(DisplayType.ListUsers)]
|
|
//public Guid LT_Common_Label_Modifier {get{return mModifier;}}
|
|
|
|
internal string mCreator;
|
|
/// <summary>
|
|
/// Creator of matching found object record
|
|
/// </summary>
|
|
[Display(DisplayType.Text)]
|
|
public string LT_Common_Label_Creator { get { return mCreator; } }
|
|
|
|
internal string mModifier;
|
|
/// <summary>
|
|
/// Modifier of matching found object record
|
|
/// </summary>
|
|
[Display(DisplayType.Text)]
|
|
public string LT_Common_Label_Modifier { get { return mModifier; } }
|
|
|
|
|
|
/// <summary>
|
|
/// Descriptive text and id of openable item to view source of search result
|
|
/// (used to create search result list buttons in AyaNova UI)
|
|
/// </summary>
|
|
[Display(DisplayType.Button, RootObjectType = RootObjectTypes.SearchResult)]
|
|
public GridNameValueCellItem LT_SearchResult_Label_Source
|
|
{
|
|
get
|
|
{
|
|
return mSource;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
public bool Equals(SearchResultListInfo obj)
|
|
{
|
|
return this.mRootObjectID.Equals(obj.mRootObjectID);
|
|
}
|
|
|
|
}//end SearchResultListInfo
|
|
#endregion
|
|
|
|
#region Constructor
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
protected SearchResultList()
|
|
{
|
|
|
|
////case 1039 //log.Debug("SearchResultList()");
|
|
// AllowSort=false;
|
|
// AllowFind=true;
|
|
// AllowEdit=false;
|
|
// AllowNew=false;
|
|
// AllowRemove=false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Business properties and methods
|
|
|
|
/// <summary>
|
|
/// Get item by index
|
|
/// </summary>
|
|
/// <param name="Item"></param>
|
|
public SearchResultListInfo this[int Item]
|
|
{
|
|
|
|
get
|
|
{
|
|
return (SearchResultListInfo) List[Item];
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns SearchResultListInfo item that matches passed in itemid value
|
|
/// </summary>
|
|
/// <param name="ItemID"></param>
|
|
public SearchResultListInfo this[Guid ItemID]
|
|
{
|
|
|
|
get
|
|
{
|
|
foreach (SearchResultListInfo child in List)
|
|
{
|
|
if(child.mRootObjectID==ItemID) return child;
|
|
}
|
|
throw new ArgumentException("SearchResultList: ID not found:\r\n"+ItemID.ToString());
|
|
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region contains
|
|
/// <summary>
|
|
/// Check if item in collection
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
public bool Contains(SearchResultListInfo obj)
|
|
{
|
|
foreach (SearchResultListInfo child in List)
|
|
{
|
|
if(child.Equals(obj)) return true;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region Reporting and shared UI editor helpers
|
|
|
|
/// <summary>
|
|
/// Returns the report key which is a property of
|
|
/// reports used to link all reports that can be used
|
|
/// with a particular data source.
|
|
/// </summary>
|
|
public static string ReportKey
|
|
{
|
|
get
|
|
{
|
|
return "SearchResultList";
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the Detailed report key
|
|
/// which is used to determine which reports and objects
|
|
/// will be used for detailed reports
|
|
///
|
|
/// If empty string then indicates there is no detailed report object or reports
|
|
/// </summary>
|
|
public static string DetailedReportKey
|
|
{
|
|
get
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base object that this list is reporting on
|
|
/// used by shared UI editor to instantiate new objects
|
|
/// when user selects new in UI elements that display this list
|
|
///
|
|
/// (I.E. when user clicks on new in a read only list grid, this is the object type created)
|
|
/// </summary>
|
|
public static RootObjectTypes BaseObjectType
|
|
{
|
|
get
|
|
{
|
|
return RootObjectTypes.SearchResult;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Locale key so that generic list editor
|
|
/// UI code knows what title to give the list in a
|
|
/// grid
|
|
/// </summary>
|
|
public string LocaleKey
|
|
{
|
|
get
|
|
{
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Type of the struct used to store list records
|
|
/// Used to fetch the custom display attributes of the fields
|
|
/// contained within the record to modify the grid display accordingly
|
|
/// <see cref="DisplayAttribute"/>
|
|
/// </summary>
|
|
public static Type ListRecordType
|
|
{
|
|
get
|
|
{
|
|
return typeof(SearchResultListInfo);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Field that contains the ID of the objects
|
|
/// that are the basis of this list.
|
|
///
|
|
/// Used for compiling an ID list for reporting from user
|
|
/// selections in a grid.
|
|
/// </summary>
|
|
public static string IDField
|
|
{
|
|
get
|
|
{
|
|
return "RootObjectID";
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Same as IDField but for detailed reports
|
|
/// </summary>
|
|
public static string IDFieldDetailed
|
|
{
|
|
get
|
|
{
|
|
return IDField;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Static methods
|
|
|
|
/// <summary>
|
|
/// Internal method used by list factory
|
|
/// </summary>
|
|
internal static SearchResultList Get(string Filter, int MaxRecords, List<Guid> IDList)
|
|
{
|
|
if (Filter == null) return new SearchResultList();
|
|
if (Filter == "") return new SearchResultList();
|
|
//case 2073 was stripping out apostrophes when it should because that vary's from how the
|
|
//word breaker originally broked the text they are likely searching for (i.e. "Mary's place")
|
|
//return (SearchResultList)DataPortal.Fetch(new Criteria(Filter.Replace("'", "").Replace("\"", ""),IDList, MaxRecords,RootObjectTypes.Nothing));
|
|
return (SearchResultList)DataPortal.Fetch(new Criteria(Filter.Replace("\"", ""), IDList, MaxRecords, RootObjectTypes.Nothing));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process search and return list of results
|
|
/// </summary>
|
|
/// <returns>collection of <see cref="SearchResultList.SearchResultListInfo"/> objects</returns>
|
|
public static SearchResultList GetList(string Search)
|
|
{
|
|
////case 1039 //log.Debug("GetList("+Search+")");
|
|
//if empty search return empty list
|
|
if(Search==null) return new SearchResultList();
|
|
if(Search=="") return new SearchResultList();
|
|
|
|
//scrub search string for sql injection attacks
|
|
//and pass to fetch through criteria
|
|
return (SearchResultList) DataPortal.Fetch(new Criteria(Search.Replace("\"",""),null,-1,RootObjectTypes.Nothing));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return an empty list
|
|
/// used for initializing grid
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static SearchResultList GetEmptyList()
|
|
{
|
|
return new SearchResultList();
|
|
}
|
|
|
|
|
|
|
|
//case 981
|
|
/// <summary>
|
|
/// Special search format:
|
|
/// Instead of a normal search that returns the
|
|
/// object that *contains* the search text, instead,
|
|
/// this search will attempt to connect the object that does contain
|
|
/// the search term to the object type desired. If it can't
|
|
/// then no result is returned even if the object found contains the search term.
|
|
///
|
|
/// This can be used, for example, to return a list of clients who can be
|
|
/// connected in any way to related objects that contain the search term.
|
|
///
|
|
/// So for example if a user is looking for a client they can enter text they know appears
|
|
/// somewhere on a workorder for that client, the search will find the work order
|
|
/// and walk up the workorder heiarchy to retrieve the client ID
|
|
///
|
|
/// This type of search is used for the "finder" feature in AyaNova
|
|
///
|
|
/// This search format, does not return a regular search result list
|
|
/// insead it populates the ListPickListID list.
|
|
///
|
|
/// (only unique id results are returned / no duplicates and no empty id's)
|
|
/// </summary>
|
|
/// <param name="Search">Search terms</param>
|
|
/// <param name="objectType">Only return results that can be linked to this object type (object types will be added as required)</param>
|
|
/// <returns>A list with RootObjectID *only* populated, all other fields are empty</returns>
|
|
public static SearchResultList GetPickListForObjectType(string Search, RootObjectTypes objectType)
|
|
{
|
|
|
|
if (objectType != RootObjectTypes.Client)
|
|
throw new System.NotSupportedException(objectType.ToString() + " is not currently supported. Object types supported are: Client");
|
|
////case 1039 //log.Debug("GetList("+Search+")");
|
|
//if empty search return empty list
|
|
if (Search == null) return new SearchResultList();
|
|
if (Search == "") return new SearchResultList();
|
|
if (objectType == RootObjectTypes.Nothing) return new SearchResultList();
|
|
|
|
//scrub search string for sql injection attacks
|
|
//and pass to fetch through criteria
|
|
//case 2073
|
|
return (SearchResultList)DataPortal.Fetch(new Criteria(Search.Replace("\"", ""), null, -1, objectType));
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
#region DAL DATA ACCESS
|
|
///
|
|
/// <param name="Criteria"></param>
|
|
protected override void DataPortal_Fetch(object Criteria)
|
|
{
|
|
////case 1039 //log.Debug("DataPortal_Fetch");
|
|
Criteria crit = (Criteria)Criteria;
|
|
if(crit.Search=="") return;
|
|
|
|
//sql ize it
|
|
crit.Search=crit.Search.Replace("*","%");
|
|
StringBuilder sbSQL= new StringBuilder();
|
|
string[] saTerms=AyaBizUtils.BreakSearchPhrase(crit.Search).Split(',');
|
|
if(saTerms==null || saTerms.GetLength(0)==0) return;
|
|
|
|
//convert search terms to query
|
|
|
|
string q="SELECT ~MAXRECS~ aSearchKey.aSourceObjectID, aSearchKey.aSourceObjectType " +
|
|
"FROM aSearchDictionary INNER " +
|
|
"JOIN aSearchKey ON aSearchDictionary.aID " +
|
|
"= aSearchKey.aWordID WHERE ";
|
|
|
|
if (crit.MaxRecords > 0)
|
|
q = q.Replace("~MAXRECS~", "TOP " + crit.MaxRecords.ToString());
|
|
else
|
|
q = q.Replace("~MAXRECS~", "");
|
|
|
|
sbSQL.Append(q);
|
|
|
|
//case 3581
|
|
string insertNationalTypeQueryCharacter = string.Empty;
|
|
if (DBUtil.DB.DBType == DataBaseType.MSSQL)
|
|
{
|
|
insertNationalTypeQueryCharacter = "N";
|
|
}
|
|
|
|
|
|
foreach (string sTerm in saTerms)
|
|
{
|
|
if (sTerm.IndexOf("%") != -1)
|
|
sbSQL.Append(" (aSearchDictionary.aWord Like " + insertNationalTypeQueryCharacter + "'" + sTerm + "') OR");//case 3581
|
|
else
|
|
sbSQL.Append(" (aSearchDictionary.aWord = " + insertNationalTypeQueryCharacter + "'" + sTerm + "') OR");//case 3581
|
|
}
|
|
|
|
//trim off the final "OR"
|
|
sbSQL.Length=sbSQL.Length-2;
|
|
|
|
if (crit.IDList != null)
|
|
{
|
|
//Case 556
|
|
System.Text.StringBuilder sbIN = new System.Text.StringBuilder();
|
|
sbIN.Append(" AND (aSearchKey.aSourceObjectID in (");
|
|
foreach (Guid gItem in crit.IDList)
|
|
{
|
|
sbIN.Append("'");
|
|
sbIN.Append("{");
|
|
sbIN.Append(gItem.ToString().ToUpperInvariant());
|
|
sbIN.Append("}");
|
|
sbIN.Append("',");
|
|
}
|
|
sbIN.Length = sbIN.Length - 1;
|
|
sbIN.Append(")) ");
|
|
sbSQL.Append(sbIN.ToString());
|
|
}
|
|
sbSQL.Append(" GROUP BY aSearchKey.aSourceObjectID, " +
|
|
"aSearchKey.aSourceObjectType HAVING (COUNT(*) " +
|
|
"= " + saTerms.GetLength(0).ToString() +")");
|
|
//------------------
|
|
|
|
//Case 194
|
|
UserPickList upl = null;
|
|
if (crit.objectType == RootObjectTypes.Nothing)//case 981
|
|
upl = UserPickList.GetList(false);
|
|
SafeDataReader dr = null;
|
|
|
|
ListPickListID.Clear();
|
|
|
|
try
|
|
{
|
|
dr=DBUtil.GetReaderFromSQLString(sbSQL.ToString());
|
|
while (dr.Read())
|
|
{
|
|
//standard search as before?
|
|
if (crit.objectType == RootObjectTypes.Nothing)//case 981
|
|
{
|
|
//standard search
|
|
SearchResult sr = AyaBizUtils.GetSearchResultFor((RootObjectTypes)dr.GetInt16("aSourceObjectType"), dr.GetGuid("aSourceObjectID"), saTerms);
|
|
//sr.rank will equal zero if there is no good match or
|
|
//if the user has no rights to retrieve that object etc
|
|
if (sr.Rank > 0)
|
|
{
|
|
SearchResultListInfo info = new SearchResultListInfo();
|
|
|
|
info.mRootObjectID = dr.GetGuid("aSourceObjectID");
|
|
info.mRootObjectType = (RootObjectTypes)dr.GetInt16("aSourceObjectType");
|
|
info.mAncestorRootObjectID = sr.AncestorRootObjectID;
|
|
info.mAncestorRootObjectType = sr.AncestorRootObjectType;
|
|
info.mDescription = sr.Description;
|
|
info.mExtract = sr.Extract;
|
|
|
|
info.mRank = sr.Rank;
|
|
info.mModified = sr.Modified;
|
|
info.mModifier = upl[sr.Modifier];
|
|
info.mCreated = sr.Created;
|
|
info.mCreator = upl[sr.Creator];
|
|
|
|
info.mSource = new GridNameValueCellItem(
|
|
info.mAncestorRootObjectID,
|
|
EnumDescConverter.GetEnumDescription(info.mRootObjectType),
|
|
info.mAncestorRootObjectType);
|
|
InnerList.Add(info);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Picklist search
|
|
// SearchResultListInfo info = new SearchResultListInfo();
|
|
// info.mRootObjectID = dr.GetGuid("aSourceObjectID");
|
|
// info.mRootObjectType = (RootObjectTypes)dr.GetInt16("aSourceObjectType");
|
|
// info.mAncestorRootObjectID = RelatedObjectIDFetcher.GetRelatedObjectID(new TypeAndID(info.mRootObjectType, info.mRootObjectID), crit.objectType);
|
|
List<Guid> items = RelatedObjectIDFetcher.GetRelatedObjectID(new TypeAndID((RootObjectTypes)dr.GetInt16("aSourceObjectType"), dr.GetGuid("aSourceObjectID")), crit.objectType);
|
|
foreach (Guid id in items)
|
|
{
|
|
if (!(id==Guid.Empty) && !ListPickListID.Contains(id))
|
|
ListPickListID.Add(id);
|
|
}
|
|
|
|
//if (info.AncestorRootObjectID == Guid.Empty || ListPickListID.Contains(info.AncestorRootObjectID))
|
|
// continue;
|
|
//ListPickListID.Add(info.AncestorRootObjectID);
|
|
//InnerList.Add(info);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
finally
|
|
{
|
|
if(dr!=null) dr.Close();
|
|
}
|
|
|
|
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region criteria
|
|
/// <summary>
|
|
/// Criteria for identifying existing object
|
|
/// </summary>
|
|
[Serializable]
|
|
private class Criteria
|
|
{
|
|
|
|
|
|
|
|
public List<Guid> IDList;
|
|
public string Search;
|
|
public int MaxRecords;
|
|
public RootObjectTypes objectType;
|
|
public Criteria(string _Search, List<Guid> _IDList, int _MaxRecords, RootObjectTypes _objectType)
|
|
{
|
|
Search = _Search;
|
|
MaxRecords = _MaxRecords;
|
|
IDList = _IDList;
|
|
objectType = _objectType;
|
|
}
|
|
|
|
}
|
|
#endregion
|
|
|
|
}//end SearchResultList
|
|
|
|
}//end namespace GZTW.AyaNova.BLL |