WSS 3.0 & MOSS: Extendiendo las capacidades de búsqueda (II)!

Siguiendo con la serie de post, cuya primera parte podéis encontrar aquí,  acerca de las posibilidades que brinda la plataforma SharePoint para extender la funcionalidad de búsqueda que proporciona a la caja, en este segundo post vamos a ver como podemos definir aplicaciones de búsqueda más granulares que permitan el uso de caracteres comodín como * o %. Empecemos.

Query Syntax y Keyword Syntax en SharePoint

Las búsquedas en plataforma SharePoint proporcionan un nuevo modelo de objetos que se puede utilizar en web parts o aplicaciones de búsqueda personalizada para ejecutar consultas contra el servicio de búsqueda. Todo el modelo de objetos de búsqueda de la plataforma SharePoint se encuentra implementado en dos espacios de nombres clave:

  • Microsoft.SharePoint.Search.Query para Windows SharePoint Services 3.0 (WSS 3.0).
  • Microsoft.Office.Server.Search para Microsoft Office SharePoint Server (MOSS).

Este modelo de objetos habilita dos posibilidades de búsqueda en plataforma SharePoint:

  • SQL Syntax, que proporciona el soporte necesario para construir consultas complejas similares a las consultas T-SQL que enviamos a SQL Server en aplicaciones con acceso a datos. De hecho, esta modalidad es la que nos permite definir consultas en las que utilicemos caracteres comodín como * o %, operadores como <,>, AND, OR, cláusulas como SELECT, WHERE, ORDER BY, LIKE o predicados como CONTAINS() y FREETEXT (). Lógicamente, SQL Syntax es la opción que habilita poder definir escenarios de búsquedas más flexibles.
  • Keyword Syntax, que permite realizar búsquedas por palabra clave. Esta opción devuelve aquellos resultados que contienen de manera exacta el término, frase o prefijo a buscar.

Implementando Query Syntax y SQL Syntax

Para probar esta funcionalidad, vamos a crear una aplicación ASP.NET que iremos extendiendo para probar las distintas alternativas de búsqueda comentadas. Abrimos Visual Studio 2005 / 2008 y a través de menú File -> New Web Site creamos una nueva aplicación web a la que denominaremos Extending_SP_Searches. Lo primero que tenemos que hacer es referenciar el ensamblado adecuado:

  • Microsoft.SharePoint.Search.dll y Microsoft.SharePoint.dll para WSS 3.0.
  • Microsoft.Office.Server.Search.dll y Microsoft.SharePoint.Server.dll para MOSS.

En nuestro caso, vamos a añadir los ensamblados necesarios para extender las búsquedas de MOOS. Para ello, hacemos clic en el nombre del proyecto y a través de la opción Add refence añadimos las referencias a los ensamblados de MOSS comentados. Estos ensamblados se encuentran en el siguiente path del servidor:

    C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12ISAPI

Dentro del ensamblado Microsoft.Office.Server.Search, tenemos el espacio de nombres Microsoft.SharePoint.Search.Query que incluye tres clases:

  • Query, que no está pensada para uso directo, sino que es la implementación base de las clases de búsqueda dentro del Search Query Object Model.
  • FullTextSqlQuery, que permite ejecutar consultas de búsqueda utilizando SQL Syntax. Es esta clase la que soporta los elementos que nos permitirán construir consultas al estilo SQL: FREETEXT(), CONTAINS(), LIKE, ORDER BY, etc.
  • KeywordQuery, que permite ejecutar consultas de búsqueda utilizando Keyword Syntax.

A la hora de utilizar una clase u otra, tenemos que considerar cuál es el nivel de complejidad de las consultas de búsqueda que necesitamos implementar:

  • Para búsquedas por palabra clave, usaremos la clase KeywordQuery, que nos permite pasar directamente el término a buscar al componente de búsqueda. De esta forma, nos evitamos el parseo a través de los términos de búsqueda para construir la consulta.
  • Para búsquedas más complejas, usaremos la clase FullTextSqlQuery, que supera las limitaciones de la opción Keyword Syntax en cuanto a que sólo devuelve aquellos resultados que contengan de manera exacta el término de búsqueda especificado. Además, SQL Syntax permite construir agrupaciones complejas y soporta elementos de consulta adicionales que no son soportados por la opción Keyword Syntax.

Una vez añadidas las referencias, añadimos al proyecto un nuevo ítem de tipo Web Form y le damos el nombre de SQL Syntax:

image image image

Abrimos la página que acabamos de crear en modo de diseño y añadimos los siguientes elementos (ver Figura):

  • Un control de tipo Table de la sección HTML de la Toolbox.
  • Un control de tipo TextBox en el que configuramos la propiedad TextMode con la opción MultiLine.
  • Un control de tipo DrowDownList.
  • Un control de tipo Label en el que configuramos la propiedad Text con el texto Tipo de Consulta.
  • Un control de tipo TextBox en el que configuramos la propiedad TextMode con la opción MultiLine.
  • Un control de tipo Button en el que configuramos la propiedad Text con el texto Buscar.
  • Un control de tipo Label.
  • Un control de tipo GridView.

El aspecto en vista de diseño de la web debería ser el siguiente:

image 

Una vez que hemos diseñado la página de búsquedas en la que habilitaremos SQL Syntax, nos vamos a la vista de código de la misma presionando F7. En esta vista de código vamos a añadir toda la lógica necesaria para poder probar SQL Syntax fuera del entorno de la plataforma SharePoint:

  • Lo primero que vamos a hacer es añadir las correspondientes sentencias using para poder utilizar los espacios de nombres clave para acceder a las clases de búsquedas de MOSS:

using Microsoft.Office.Server;

using Microsoft.Office.Server.Search;

using Microsoft.Office.Server.Search.Query;

using Microsoft.SharePoint;

  • Añadimos las siguientes constantes a la clase parcial asociada a la página (os marco en negrita las constantes en las que he reflejado las consultas que incluye la página ASP.NET):

    #region Variables & Constates

    //Variables y constantes necesarias   

    public const int DefaultRowLimit = 10;

    public const int startRecordNum = 1;

    //Constantes valores combo

    const string ID_QUERY = “ID_Query”;

    const string NOMBRE_QUERY = “Query”;

    public const string TIPO_QUERY0_ID = “Q0”;

    public const string TIPO_QUERY0_TEXT = “Seleccione un tipo de query…”;

    public const string TIPO_QUERY1_ID = “Q1”;

    public const string TIPO_QUERY1_TEXT = “Keyword Query”;

    public const string TIPO_QUERY2_ID = “Q2”;

    public const string TIPO_QUERY2_TEXT = “Búsqueda por Scope”;

    public const string TIPO_QUERY3_ID = “Q3”;

    public const string TIPO_QUERY3_TEXT = “Búsqueda por Scope y Campo”;

    public const string TIPO_QUERY4_ID = “Q4”;

    public const string TIPO_QUERY4_TEXT = “Búsqueda con LIKE”;

    public const string TIPO_QUERY5_ID = “Q5”;

    public const string TIPO_QUERY5_TEXT = “Búsqueda con CONTAINS y *”;

    public const string TIPO_QUERY6_ID = “Q6”;

    public const string TIPO_QUERY6_TEXT = “Búsqueda con FreeText”;

    public const string TIPO_QUERY7_ID = “Q7”;

    public const string TIPO_QUERY7_TEXT = “Búsqueda con FreeText y Op. Exclusión”;

    //Constantes para las queries

    public const string QUERY1 = “Introduzca la palabra clave a buscar…”;

    public const string QUERY2 = “SELECT Rank, Title, Author, Size, Path, Description” +

                            ” FROM SCOPE() WHERE “scope”=’All Sites'”;

    public const string QUERY3 = “SELECT Rank, Title, Author, Size, Path, Description” +

                            ” FROM SCOPE() WHERE “scope”=’All Sites'” +

                            ” AND  Author=’LitwareInc Administrator’ ORDER BY Rank DESC”;

    public const string QUERY4 = “SELECT Rank, Title, Author, Size, Path, Description” +

                            ” FROM SCOPE() WHERE “scope”=’All Sites'” +

                            ” AND TITLE LIKE ‘Bla%’ ORDER BY Rank DESC”;

    public const string QUERY5 = “SELECT Rank, Title, Author, Size, Path, Description” +

                            ” FROM SCOPE() WHERE CONTAINS (‘”Bla*”‘)”;

    public const string QUERY6 = “SELECT Rank, Title, Author, Size, Path, Description” +

                            ” FROM SCOPE() WHERE “scope”=’All Sites'” +

                            “AND FreeText (‘Bla%’) ORDER BY Rank DESC”;

    public const string QUERY7 = “SELECT Rank, Title, Author, Size, Path, Description” +

                            ” FROM SCOPE() WHERE “scope”=’All Sites'” +

                            “AND FreeText (‘Bla% -CRM’) ORDER BY Rank DESC”;

    #endregion

Como vemos, estas constantes nos van a permitir realizar varios tipos de consultas mediante SQL Syntax:

  • Consultas utilizando una palabra clave.
  • Consultas a un determinado scope o ámbito (en este caso All Sites).
  • Consultas a un scope y filtrando por un cierto campo (Author).
  • Consultas utilizando la clausula LIKE que permite extender las búsquedas en SharePoint al habilitar el uso de operadores comodín (%).
  • Consultas utilizando el predicado CONTAINS(), que permite buscar el término de búsqueda utilizando caracteres comodín (*) y utilizando proximidad. Con este operador se utiliza habitualmente el comodín *.
  • Consultas utilizando el predicado FreeText(), que permite buscar el término de búsqueda dentro de los documentos.

Además, en el código anterior también podemos observar lo siguiente:

  • Los resultados se ordenan utilizando la cláusula ODER BY Rank DESC, es decir, los resultados se devuelven ordenador de forma descendente por la propiedad Rank que indica el nivel de raking de un cierto resultado devuelto por el motor de búsquedas de SharePoint (MOSS en este caso).
  • En alguna de las consultas se utiliza el operador de exclusión -, que nos permite especificar que se excluyan aquellos resultados que contengan el término que aparece a continuación del operador.

En el evento Page_Load() de la página ASP.NET añadimos el siguiente código:

if (! IsPostBack)

        {

            this.Label1.Text = “”;

            this.Button.Enabled = false;

            //Filling the combo

            DataTable dtlQueries= new DataTable();

            dtlQueries.Columns.Add(ID_QUERY);

            dtlQueries.Columns.Add(NOMBRE_QUERY);         

            DataRow dtrFila;

            //Default Data

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY0_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY0_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //First Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY1_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY1_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Second Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY2_ID;

            dtrFila[NOMBRE_QUERY] =TIPO_QUERY2_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Third Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY3_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY3_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Fourth Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY4_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY4_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Fith Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY5_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY5_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Sixth Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY6_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY6_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Seventh Query

            dtrFila = dtlQueries.NewRow();

            dtrFila[ID_QUERY] = TIPO_QUERY7_ID;

            dtrFila[NOMBRE_QUERY] = TIPO_QUERY7_TEXT;

            dtlQueries.Rows.Add(dtrFila);

            //Adding the data to the dropdownlist

            this.DropDownList1.DataSource = dtlQueries;

            this.DropDownList1.DataTextField = NOMBRE_QUERY;

            this.DropDownList1.DataValueField = ID_QUERY;

            this.DropDownList1.DataBind();

            this.DropDownList1.AutoPostBack = true;

            this.DropDownList1.SelectedItem.Value = TIPO_QUERY0_ID;

        }

Como vemos, el código anterior nos permite configurar en la primera carga de la página los parámetros iníciales de los controles de la misma. En particular, estamos realizando las siguientes tareas:

  • Rellenar el control DropDownList con los distintos tipos de consultas que vamos a realizar.
  • Configuramos la propiedad AutoPostBack a true para que la página se enteré de que el usuario ha seleccionado una cierta opción y se llame al correspondiente manejador.
  • Especificar el valor del DropDownList que se muestra por defecto.

Codificamos el manejador del evento SelectedIndexChanged del control DropDownList añadiéndolo directamente en la vista de código de la página o bien haciendo doble clic sobre dicho control desde la vista de diseño de la página:

  protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)

    {

        if (this.DropDownList1.SelectedItem.Value != “0”)

        {

            this.Label1.Text = “”;

            string sOpcionQuery = this.DropDownList1.SelectedItem.Value;

            switch (sOpcionQuery)

            {

                case TIPO_QUERY1_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY1;

                    break;

                case TIPO_QUERY2_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY2;

                    break;

                case TIPO_QUERY3_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY3;

                    break;

                case TIPO_QUERY4_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY4;

                    break;

                case TIPO_QUERY5_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY5;

                    break;

                case TIPO_QUERY6_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY6;

                    break;

                case TIPO_QUERY7_ID:

                    this.Button.Enabled = true;

                    this.TextBox1.Text = QUERY7;

                    break;

                default:

                    this.Label1.Text = “”;

                    this.Button.Enabled = false;

                    break;

            }

        }

        else

        {

            this.Label1.Text = “”;

            this.Button.Enabled = false;

        }

    }

Con el código anterior, estamos pintando en la control TextBox de la página la consulta, utilizando SQL Syntax o Keyword Syntax, seleccionada por el usuario.

  • Codificamos el manejador del evento clic del control Button utilizando una de las dos opciones comentadas, SQL Syntaxt o Keyword Syntax, para el manejador SelectedIndexChanged del control DropDownList (os marco de nuevo en negrita las partes clave del código):

      protected void Button1_Click(object sender, EventArgs e)

    {

        try

        {

            this.Label1.Text = “”;           

            int endRecordNum, totalNumRecords;

            SPSite spsSitio =

                new SPSite(http://litwaredemo:12000/sites/Intranet/default.aspx);

            //Keyword syntax

            KeywordQuery qRequest = new KeywordQuery(spsSitio);

            //SQL Syntax

            FullTextSqlQuery searchQuery = new FullTextSqlQuery(spsSitio);

            ResultTableCollection searchResultTableCollection;

            if (this.DropDownList1.SelectedItem.Value

                    == TIPO_QUERY1_ID)

            {

                qRequest.EnableStemming = true;

                qRequest.KeywordInclusion = KeywordInclusion.AllKeywords;

                qRequest.ResultTypes = ResultType.RelevantResults;

                qRequest.TrimDuplicates = true;

                qRequest.QueryText = this.TextBox1.Text;

                qRequest.StartRow = startRecordNum;

                qRequest.RowLimit = DefaultRowLimit;

                searchResultTableCollection = qRequest.Execute();

            }

            else

            {

                 searchQuery.EnableStemming = true;

                searchQuery.KeywordInclusion = KeywordInclusion.AllKeywords;

                searchQuery.ResultTypes = ResultType.RelevantResults;

                searchQuery.TrimDuplicates = true;

                searchQuery.QueryText = this.TextBox1.Text;

                searchQuery.StartRow = startRecordNum;

                searchQuery.RowLimit = DefaultRowLimit; 

                searchResultTableCollection = searchQuery.Execute();

            }        

            ResultTable searchResultTable = null;

            if ((int)ResultType.RelevantResults != 0)

            {

                searchResultTable =

                    searchResultTableCollection[ResultType.RelevantResults];

            } 

            totalNumRecords = searchResultTable.TotalRows;

            endRecordNum = startRecordNum + searchResultTable.RowCount – 1;

            if (totalNumRecords > 0)

            {

                this.Label1.Text = “Número de coincidencias ” + endRecordNum.ToString();

                DataTable resultDataTable = new DataTable();

                resultDataTable.TableName = “SearchResultTable”;

                DataSet resultDataSet = new DataSet(“SearchResultDataSet”);

                resultDataSet.Tables.Add(resultDataTable);

                resultDataSet.Load(searchResultTable,

                    LoadOption.OverwriteChanges, resultDataTable);

                this.GridView1.DataSource = resultDataSet;

                this.GridView1.DataBind();

            }

            else

            {

                this.Label1.Text = “No hay resultados para esta búsqueda”;

                this.GridView1.DataSource = null;

                this.GridView1.DataBind();

            }

        }

        catch (Exception ex)

        {

            this.Label1.Text = ex.Message;

        }

    }

Como vemos en el listado del código, los pasos necesarios para realizar consultas utilizando SQL Syntax o Keyword Syntax son los siguientes:

  • Definir un objeto de tipo SPSite en el que especificaremos el sitio de SharePoint dónde vamos a realizar búsquedas.
  • Definimos un objeto de tipo KeywordQuery para poder realizar búsquedas por palabra clave en el sitio indicado.
  • Definimos un objeto de tipo FullTextSqlQuery para poder realizar consultas en el sitio indicado utilizando SQL Syntax.
  • En función de la opción seleccionada por el usuario, búsqueda por palabra clave o mediante una consulta SQL Syntax, parametrizamos de forma adecuada el objeto correspondiente y ejecutamos la consulta:
    • En el caso de Keyword Syntax, es el propio componente de búsqueda de MOSS el que se encarga de construir la query a partir de la palabra clave especificada.
    • En el caso de SQL Syntax, es la lógica de nuestra aplicación la que se encarga de construir la consulta parametrizada y pasársela al componente de búsqueda de SharePoint.

En cualquiera de las dos situaciones, la consulta se ejecuta mediante el método Execute() y el resultado devuelto (una colección de registros que cumplen las condiciones de búsqueda definidas) lo almacenamos en un objeto de tipo ResultTableCollection.

  • A continuación, comprobamos si la búsqueda devuelve resultados considerados por el motor de búsqueda como relevantes de acuerdo a los criterios de búsqueda. Si es así, almacenamos estos resultados relevantes en un objeto de tipo ResultTable.
  • Sin más, comprobamos que hay resultados en el objeto de tipo ResultTable y los renderizamos en el control GridView e la página.

Probando la solución de búsqueda con SQL Syntax y Keyword Syntax

Una vez diseñada la aplicación de búsquedas preparada para definir búsquedas con SQL Syntax y Keyword Syntax, así como implementada la lógica de búsquedas, vamos a probar dos de las situaciones modelada:

  • Búsqueda mediante palabra clave:
    • Escogemos la opción Keyword query.
  • Búsqueda mediante SQL Syntax:
    • Seleccionamos la opción Búsqueda con FreeText y Op. de Exclusión.
image image

Como conclusión, el modelo de objetos de búsqueda de WSS 3.0 & MOSS habilita la definición de escenarios de búsqueda más flexibles y completo que la funcionalidad que viene a la caja en ambos entornos. Particularmente flexible es la funcionalidad SQL Syntax. Espero que el post os haya resultado interesante. En la próxima entrega (la última de la serie) veremos como usar el servicio web de búsqueda de SharePoint.

Publicado por

Juan Carlos González

Juan Carlos es Ingeniero de Telecomunicaciones por la Universidad de Valladolid y Diplomado en Ciencias Empresariales por la Universidad Oberta de Catalunya (UOC). Cuenta con más de 12 años de experiencia en tecnologías y plataformas de Microsoft diversas (SQL Server, Visual Studio, .NET Framework, etc.), aunque su trabajo diario gira en torno a SharePoint & Office 365. Juan Carlos es MVP de Office Servers & Services desde 2015 (anteriormente fue reconocido por Microsoft como MVP de Office 365 y MVP de SharePoint Server desde 2008 hasta 2015), coordinador del grupo de usuarios .NET de Cantabria (Nuberos.Net, www.nuberos.es), co-fundador y coordinador del Grupo de Usuarios de SharePoint de España (SUGES, www.suges.es), así como co-director de la revista gratuita en castellano sobre SharePoint CompartiMOSS (www.compartimoss.com). Hasta la fecha, ha publicado 8 libros sobre SharePoint & Office 365 y varios artículos en castellano y en inglés sobre ambas plataformas.

7 comentarios en “WSS 3.0 & MOSS: Extendiendo las capacidades de búsqueda (II)!”

  1. Muy buenas Rommy,
    Pues sí, la verdad que todo lo referente a búsquedas en SharePoint y su extensibilidad es interesante y puede dar mucho que contar.

    Un saludo

    JC’s

  2. holas me puedes ayudar e creado un web services con sharepoint sobre una lista q e creado en base window en sharepoint q se llama empleado al momento de ejecutarlo me bota la lista empleados pero ahora lo q quiero es hacer una busqueda q me bote sobre los onamasticos de la fecha de nacimiento de la lista empelado bueno espero tu respuesta.

  3. Buenas Alonso,
    Para hacer lo que me preguntas, échale un vistazo a la tercera parte de posts sobre búsquedas…de todos modos, yo no me complicaría con esto, otra posibilidad que tienes es aprovechar el código que tienes y que te da acceso a los datos de la lista para mandar una consulta CAML con el filtro que necesitas.

    Un saludo

    JC’s

  4. hola, muy buen post, pero tengo una duda: cuando trato de modificar la consulta en la aplicación,por ejemplo agregando más parametros de busqueda no obtengo resultados, sin embargo, si existen campos que cumplen esas condiciones y realizo las consultas pensando en como resultarian con el motor sql 2005, aqui va un ejemplo de consulta:

    select title, firstname, workemail, ubicacion
    from Scope() where title like ‘%juan%’
    And ubicacion = ‘Adm. Central’

    este ejemplo de consulta deberia devolverme el siguiente registro:

    Title workEmail ubicacion
    juan arevalo juan.arevalo@algo.cl Adm. central

    Pero no me devuelve nada… He probado la consulta por separado y me funciona correctamente, por ejemplo:

    select title, firstname, workemail, ubicacion
    from Scope() where title like ‘%juan%’

    Me devuelve el registro anteriormente descrito, al igual que la consulta por la ubicacion.

    ¿Hay alguna opcion en MOSS que no permita realizar estas acciones o alguna restricción?

    De antemano gracias por contestarme, cualquer cosa mi mail es afca_1064@hotmail.com

  5. Hola Juan,
    En principio te debería funcionar sin problemas…lo único que veo en tu consulta es que buscas Adm. Central y sin embargo en los resultados te sale Adm. central…quizás el problema esté ahí.

    Un saludo

    JC’s

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *