Common Custom data source
Common_CustomDataSource_CSharp\REXMLDataSource_CSharp\QueryFunctionality.cs
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 


namespace REXMLDataSource_CSharp
{
    // Along with IGISDataSource and IGISResource, IGISFunctionality is one of the three required
    // interfaces for any Web ADF Data Source implementation.  This interface is responsible for
    // providing members to functionally interact with the underlying data.  Essentially, an 
    // IGISFunctionality implementation can be thought of as describing what can be done with the 
    // data.
    //
    // This particular implementation inherits from IQueryFunctionality, which implements 
    // IGISFunctionality.  IQueryFunctionality provides methods and properties that allow the
    // extraction of attributes and geometry from the underlying data source via query expressions
    // and geometries.
    public class QueryFunctionality : ESRI.ArcGIS.ADF.Web.DataSources.IQueryFunctionality
    {
        #region Instance Variable Declarations

        private string m_name = string.Empty;
        private ESRI.ArcGIS.ADF.Web.DataSources.IGISResource m_gisResource = null;
        [System.NonSerialized]
        System.Web.UI.WebControls.WebControl m_webControl;
        bool m_initialized = false;


        #endregion

        #region Constructor 

        public QueryFunctionality(string name, REXMLDataSource_CSharp.MapResource resource)
        {
            m_name = name;
            m_gisResource = resource;
        }

        #endregion

        #region REXML QueryFunctionality Members

        // Enables convenient retrieval of the graphics dataset underlying either (a) the resource 
        // associated with the QueryFunctionality instance (if no functionality name is passed into 
        // the function) or (b) the specific MapFunctionality indicated by the mapFunctionalityName 
        // parameter
        private ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet 
            GetGraphicsDataSet(string mapFunctionalityName)
        {
            REXMLDataSource_CSharp.MapResource mapResource = m_gisResource as 
                REXMLDataSource_CSharp.MapResource;
            if (mapResource == null)
                return null;

            // If no functionality name was passed in, return the graphics dataset underlying the REXML
            // MapResource associated with the QueryFunctionality instance.  Otherwise, retrieve the
            // REXML MapFunctionality indicated by the passed-in name and return the graphics dataset
            // underlying that functionality.
            if (mapFunctionalityName == null)
            {
                return mapResource.Graphics;
            }
            else
            {
                REXMLDataSource_CSharp.MapFunctionality mapFunctionality =
                    mapResource.Functionalities.Find(mapFunctionalityName) as 
                    REXMLDataSource_CSharp.MapFunctionality;
                return mapFunctionality == null ? null : mapFunctionality.GraphicsDataSet;
            }
        }

        // Enables convenient retrieval of the graphics layer with the specified ID from the REXML
        // MapFunctionality with the specified name
        private ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer 
            GetGraphicsLayer(string mapFunctionalityName, string layerID)
        {
            // Make sure a layerID was specified
            if (layerID == null)
                return null;

            // Retrieve the graphics dataset from the REXML MapFunctionality with the passed-in name
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet graphicsDataSet = 
                GetGraphicsDataSet(mapFunctionalityName);

            // Make sure a graphics dataset was found
            if (graphicsDataSet == null)
                return null;

            // Return the table in the graphics dataset that has a name matching the passed-in layer ID 
            // as a Web ADF GraphicsLayer
            return graphicsDataSet.Tables[layerID] as ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer;
        }

        // Copies records satisfying a Web ADF Query Filter from a graphics layer to a data table.
        // Also returns a list of the geometries for the rows that satsify the query
        private System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.Geometry.Geometry>
            InitialAttributeQuery(ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer,
            System.Data.DataTable resultsDataTable, ESRI.ArcGIS.ADF.Web.QueryFilter adfQueryFilter)
        {
            System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.Geometry.Geometry> adfGeometryList =
                new System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.Geometry.Geometry>();
            System.Data.DataRow[] dataRowArray = graphicsLayer.Select(adfQueryFilter.WhereClause);
            foreach (System.Data.DataRow dataRow in dataRowArray)
            {
                resultsDataTable.ImportRow(dataRow);
                adfGeometryList.Add(graphicsLayer.GeometryFromRow(dataRow));
            }

            return adfGeometryList;
        }

        // Copies records satisfying a Web ADF Spatial Filter from a graphics layer to a data table.
        private void InitialSpatialQuery(ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer,
            System.Data.DataTable resultsDataTable, ESRI.ArcGIS.ADF.Web.SpatialFilter adfSpatialFilter)
        {
            foreach (System.Data.DataRow dataRow in graphicsLayer.Rows)
            {
                ESRI.ArcGIS.ADF.Web.Geometry.Geometry adfGeometry = graphicsLayer.GeometryFromRow(dataRow);

                // Evaluate feature geometry and spatial filter geometry.  Designed to work if the spatial 
                // filter geometry is an Envelope - not implemented for other geometry types.  
                if ((adfGeometry != null) &&
                    (adfSpatialFilter.Geometry is ESRI.ArcGIS.ADF.Web.Geometry.Envelope) &&
                        (ESRI.ArcGIS.ADF.Web.Geometry.Utility.GeometryInExtent(adfGeometry,
                        (ESRI.ArcGIS.ADF.Web.Geometry.Envelope)adfSpatialFilter.Geometry)))
                {
                    resultsDataTable.ImportRow(dataRow);
                }

            }
        }

        // Allows filtering of rows in a data table based on a Web ADF QueryFilter
        private void AttributeQueryOnSpatialResults(System.Data.DataTable dataTable,
            ESRI.ArcGIS.ADF.Web.QueryFilter adfQueryFilter)
        {
            // The passed-in query filter has no query, so no filtering will be performed
            if (adfQueryFilter.WhereClause == "")
                return;

            // Get the rows from the passed-in data table that satisfy the query filter's where clause
            System.Data.DataRow[] selectionDataRowArray = dataTable.Select(adfQueryFilter.WhereClause);

            // Iterate through all the rows in the passed-in data table, removing rows that are not
            foreach (System.Data.DataRow dataRow in dataTable.Rows)
            {
                if (System.Array.IndexOf(selectionDataRowArray, dataRow) == -1)
                    dataTable.Rows.Remove(dataRow);
            }
        }

        // Allows filtering of rows in a data table based on whether the passed-in geometries intersect
        // the spatial filter geometry.  The passed-in geometries are meant to be the geoemtries from
        // the rows in the data table.
        private void SpatialQueryOnAttributeResults(System.Data.DataTable dataTable,
            ESRI.ArcGIS.ADF.Web.SpatialFilter adfSpatialFilter,
            System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.Geometry.Geometry> geometries)
        {
            if (geometries == null || geometries.Count == 0)
                return;

            for (int i = 0, j = 0; i < dataTable.Rows.Count; ++i, ++j)
            {
                // To implement, evaluate if feature geometry intersects spatial filter geometry

                /*if (!GeometriesIntersect(geometries[j], sf.Geometry))
                {
                    dataTable.Rows.Remove(dataTable.Rows[i]);
                    --i;
                }*/
            }
        }

        // Clones the passed-in data column
        private System.Data.DataColumn CloneColumn(System.Data.DataColumn sourceDataColumn)
        {
            // Create a data column instance that matches the type of the passed-in data column
            System.Data.DataColumn clonedDataColumn = (System.Data.DataColumn)System.Activator.CreateInstance(
                sourceDataColumn.GetType());

            // Copy the properties from the passed-in data column to the new data column
            clonedDataColumn.AllowDBNull = sourceDataColumn.AllowDBNull;
            clonedDataColumn.AutoIncrement = sourceDataColumn.AutoIncrement;
            clonedDataColumn.AutoIncrementStep = sourceDataColumn.AutoIncrementStep;
            clonedDataColumn.AutoIncrementSeed = sourceDataColumn.AutoIncrementSeed;
            clonedDataColumn.Caption = sourceDataColumn.Caption;
            clonedDataColumn.ColumnName = sourceDataColumn.ColumnName;
            clonedDataColumn.DataType = sourceDataColumn.DataType;
            clonedDataColumn.DefaultValue = sourceDataColumn.DefaultValue;
            clonedDataColumn.ColumnMapping = sourceDataColumn.ColumnMapping;
            clonedDataColumn.ReadOnly = sourceDataColumn.ReadOnly;
            clonedDataColumn.MaxLength = sourceDataColumn.MaxLength;
            clonedDataColumn.DateTimeMode = sourceDataColumn.DateTimeMode;
            clonedDataColumn.Namespace = sourceDataColumn.Namespace;
            clonedDataColumn.Prefix = sourceDataColumn.Prefix;
            clonedDataColumn.Unique = sourceDataColumn.Unique;

            // Copy the extended properties from the passed-in data column to the new data column
            if (sourceDataColumn.ExtendedProperties != null)
            {
                foreach (object extendedPropertyKey in sourceDataColumn.ExtendedProperties.Keys)
                {
                    clonedDataColumn.ExtendedProperties[extendedPropertyKey] = 
                        sourceDataColumn.ExtendedProperties[extendedPropertyKey];
                }
            }

            return clonedDataColumn;
        }

        // Gets the REXML MapFunctionality of the passed-in name from the MapResource associated with the
        // current QueryFunctionality instance
        private REXMLDataSource_CSharp.MapFunctionality GetMapFunctionality(string mapFunctionalityName)
        {
            if (m_gisResource == null) return null;
            REXMLDataSource_CSharp.MapResource mapResource = m_gisResource as REXMLDataSource_CSharp.MapResource;
            if (mapFunctionalityName == null)
            {
                return null;
            }
            else
            {
                REXMLDataSource_CSharp.MapFunctionality mapFunctionality =
                    mapResource.Functionalities.Find(mapFunctionalityName) as 
                    REXMLDataSource_CSharp.MapFunctionality;
                return mapFunctionality;
            }
        }
        #endregion

        #region IQueryFunctionality Members

        // Allows querying of the REXML MapFunctionality indicated by the passed-in name with the parameters
        // specified by the passed-in FindParameters object.  More specifically, Find searches the layers
        // and fields specified by FindParameters for values that completely or partly match the FindString
        // of the FindParameters object.
        public System.Data.DataTable[] Find(string mapFunctionalityName, 
            ESRI.ArcGIS.ADF.Web.FindParameters findParameters)
        {
            System.Collections.Generic.List<System.Data.DataTable> dataTableList = 
                new System.Collections.Generic.List<System.Data.DataTable>();
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet graphicsDataSet = 
                GetGraphicsDataSet(mapFunctionalityName);
            if (graphicsDataSet == null) return null;

            // Initialize Web ADF QueryFilter based on passed-in FindParameters
            ESRI.ArcGIS.ADF.Web.QueryFilter adfQueryFilter = new ESRI.ArcGIS.ADF.Web.QueryFilter();
            adfQueryFilter.MaxRecords = findParameters.MaxRecords;
            adfQueryFilter.ReturnADFGeometries = findParameters.ReturnADFGeometries;

            // Get an enumerator of the layers and fields to be searched from the FindParameters object
            System.Collections.IDictionaryEnumerator dictionaryEnumerator = 
                findParameters.LayersAndFields.GetEnumerator();
            while (dictionaryEnumerator.MoveNext())
            {
                // Each index of the enumerator will have the layer ID as its key and a string array
                // of field names as its value
                string layerID = dictionaryEnumerator.Key as string;
                string[] searchFields = dictionaryEnumerator.Value as string[];

                // Iterate through the fields to be searched for the current layer and build the where clause
                System.Text.StringBuilder whereExpression = new System.Text.StringBuilder();
                for (int i = 0; i < searchFields.Length; i++)
                {
                    if (findParameters.UseSqlContains)
                    {
                        // todo: change to use SQL CONTAINS statement
                        whereExpression.Append(string.Format("{0} like '%{1}%'", searchFields[i], 
                            findParameters.FindString));
                    }
                    else
                    {
                        whereExpression.Append(string.Format("{0} like '%{1}%'", 
                            searchFields[i], findParameters.FindString));
                    }
                    if (i != searchFields.Length - 1) whereExpression.Append(" OR ");
                }

                adfQueryFilter.WhereClause = whereExpression.ToString();

                // Get the graphics layer corresponding to the current layer ID from the map functionality's
                // graphics dataset
                ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = 
                    graphicsDataSet.Tables[layerID] as ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer;
                if (graphicsLayer != null)
                {
                    // Make sure the current graphics layer satisfies the specified visibility option
                    if (findParameters.FindOption != ESRI.ArcGIS.ADF.Web.FindOption.VisibleLayers || 
                        (findParameters.FindOption == ESRI.ArcGIS.ADF.Web.FindOption.VisibleLayers && 
                        graphicsLayer.Visible == true))
                    {
                        // Execute the query for the current layer and add the results to the list of data tables
                        dataTableList.Add(Query(mapFunctionalityName, graphicsLayer.TableName, adfQueryFilter));
                    }
                }
            }

            return dataTableList.ToArray();
        }

        // Allows querying of multiple layers of a REXML MapFunctionality based on an input point
        public System.Data.DataTable[] Identify(string mapFunctionalityName, 
            ESRI.ArcGIS.ADF.Web.Geometry.Geometry adfGeometry, int tolerance, 
            ESRI.ArcGIS.ADF.Web.IdentifyOption identifyOption, string[] layerIDs)
        {
            System.Collections.Generic.List<System.Data.DataTable> dataTableList = 
                new System.Collections.Generic.List<System.Data.DataTable>();

            // Get the graphics dataset for the REXML MapFunctionality indicated by the passed-in name
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet graphicsDataSet = 
                GetGraphicsDataSet(mapFunctionalityName);
            if (graphicsDataSet == null)
                return null;

            // Get the REXML MapFunctionality object
            REXMLDataSource_CSharp.MapFunctionality rexmlMapFunctionality = 
                GetMapFunctionality(mapFunctionalityName);
            
            if (rexmlMapFunctionality == null) return null;
            
            // Get the screen dimensions and the Web ADF extent of the map displaying the data source.  
            // These will be used in calculating a search tolerance around the input point
            int viewWidth, viewHeight;
            rexmlMapFunctionality.GetMapDimensions(out viewWidth, out viewHeight);           
            ESRI.ArcGIS.ADF.Web.Geometry.Envelope mapExtent = rexmlMapFunctionality.GetMapExtent();
            if (mapExtent == null) return null;   

            ESRI.ArcGIS.ADF.Web.SpatialFilter adfSpatialFilter = new ESRI.ArcGIS.ADF.Web.SpatialFilter();
            adfSpatialFilter.SearchOrder = ESRI.ArcGIS.ADF.Web.SearchOrder.Spatial;

            // Make sure the passed-in geometry is a Web ADF Point.  Other geometry types are not supported in
            // this implementation
            if (adfGeometry is ESRI.ArcGIS.ADF.Web.Geometry.Point)
            {
                // Calculate the tolerance to add around the point for the search geometry
                double pixelsPerMapUnit = viewWidth / mapExtent.Width;
                double mapTolerance = tolerance / pixelsPerMapUnit;

                // Create the search geometry by initializing a Web ADF Envelope with the passed-in point's
                // coordinates plus and minus the calculated search tolerance
                ESRI.ArcGIS.ADF.Web.Geometry.Point adfPoint = adfGeometry as ESRI.ArcGIS.ADF.Web.Geometry.Point;
                adfSpatialFilter.Geometry = new ESRI.ArcGIS.ADF.Web.Geometry.Envelope(adfPoint.X - mapTolerance, 
                    adfPoint.Y - mapTolerance, adfPoint.X + mapTolerance, adfPoint.Y + mapTolerance);
                
            }
            else
            {
                throw new System.NotSupportedException("GraphicsLayer only supports Points in the " +
                    "Identify method.");
            }

            // Check whether any layer IDs were specified in the operation.  If so, only query those layers.  If
            // not, query all the layers.  Add the results of each query to the list of data tables.
            if (layerIDs == null)
            {
                foreach (ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer in graphicsDataSet.Tables)
                {
                    dataTableList.Add(Query(mapFunctionalityName, graphicsLayer.TableName, adfSpatialFilter));
                }
            }
            else
            {
                foreach (string layerID in layerIDs)
                {
                    dataTableList.Add(Query(mapFunctionalityName, layerID, adfSpatialFilter));
                }
            }

            return dataTableList.ToArray();
        }

        // Executes a query based on the passed-in parameters and returns the results as a DataTable
        public System.Data.DataTable Query(string mapFunctionalityName, string layerID, 
            ESRI.ArcGIS.ADF.Web.QueryFilter adfQueryFilter)
        {
            // Get the graphics layer specified by the passed-in layer ID and map functionality name
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = 
                GetGraphicsLayer(mapFunctionalityName, layerID);

            if (graphicsLayer == null)
                return null;

            // Declare a data table.  This table will hold the query results
            System.Data.DataTable resultsDataTable = null;
            
            // Declare a list of data columns.  This will hold the columns to be included in the query results
            System.Collections.Generic.List<System.Data.DataColumn> resultsDataColumnList = 
                new System.Collections.Generic.List<System.Data.DataColumn>();

            // Iterate through the columns in the graphics layer, adding each one to be included in
            // the query results to the results data column list
            foreach (System.Data.DataColumn dataColumn in graphicsLayer.Columns)
            {
                // If no fields were specified in the query; the current column name matches one of
                // the specified field names; or the current column is a geometry column and geometries 
                // are to be returned in the query, add a copy of the current column to the results column 
                // list
                if (adfQueryFilter.SubFields.Count == 0 || 
                    adfQueryFilter.SubFields.IndexOf(dataColumn.ColumnName) != -1)
                    resultsDataColumnList.Add(CloneColumn(dataColumn));
                else if (adfQueryFilter.ReturnADFGeometries && (dataColumn.DataType == 
                    typeof(ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicElement) || 
                    dataColumn.DataType == typeof(ESRI.ArcGIS.ADF.Web.Geometry.Geometry)))
                    resultsDataColumnList.Add(CloneColumn(dataColumn));
            }

            // If the query is to return geometries, initialize the results data table as a Web ADF graphics
            // layer of the appropriate type
            if (adfQueryFilter.ReturnADFGeometries)
            {
                if (graphicsLayer is ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer)
                {
                    ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer featureGraphicsLayer = 
                        graphicsLayer as ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer;
                    resultsDataTable = new ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer(
                        featureGraphicsLayer.TableName, resultsDataColumnList.ToArray(), 
                        featureGraphicsLayer.GeometryColumnName, featureGraphicsLayer.GraphicsIDColumn.ColumnName, 
                        featureGraphicsLayer.FeatureType);
                }
                else
                {
                    ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer elementGraphicsLayer = 
                        graphicsLayer as ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer;
                    resultsDataTable = new ESRI.ArcGIS.ADF.Web.Display.Graphics.ElementGraphicsLayer(
                        elementGraphicsLayer.TableName, resultsDataColumnList.ToArray(), elementGraphicsLayer.GraphicsColumn.ColumnName, 
                        elementGraphicsLayer.GraphicsIDColumn.ColumnName);
                }
            }

            // If the query is not to return geometries, the results data table will not yet be intialized.
            // Initialize it as an ordinary DataTable.
            if (resultsDataTable == null)
            {
                resultsDataTable = new System.Data.DataTable(graphicsLayer.TableName);
                resultsDataTable.Columns.AddRange(resultsDataColumnList.ToArray());
            }

            // Execute the query using the appropriate private functions.  
            if (adfQueryFilter is ESRI.ArcGIS.ADF.Web.SpatialFilter)
            {
                // Get a reference to the passed-in query filter as a Web ADF Spatial Filter
                ESRI.ArcGIS.ADF.Web.SpatialFilter adfSpatialFilter = adfQueryFilter as 
                    ESRI.ArcGIS.ADF.Web.SpatialFilter;

                if (adfSpatialFilter.SearchOrder == ESRI.ArcGIS.ADF.Web.SearchOrder.Spatial)
                {
                    // Execute the spatial query first, then filter the spatial query based on the
                    // query filter's where clause
                    InitialSpatialQuery(graphicsLayer, resultsDataTable, adfSpatialFilter);
                    AttributeQueryOnSpatialResults(resultsDataTable, adfQueryFilter);
                }
                else
                {
                    // Execute the attribute query first, then filter the query results based on the filter
                    // geometry.
                    System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.Geometry.Geometry> adfGeometryList = 
                        InitialAttributeQuery(graphicsLayer, resultsDataTable, adfQueryFilter);
                    SpatialQueryOnAttributeResults(resultsDataTable, adfSpatialFilter, adfGeometryList);
                }
            }
            else
            {
                // The query is non-spatial, so simply execute an attribute query based on the query filter's
                // where clause
                InitialAttributeQuery(graphicsLayer, resultsDataTable, adfQueryFilter);
            }

            // If the results data table contains more records than the number specified by the query filter
            // as the maximum, remove the extra results from the end of the table
            if (resultsDataTable.Rows.Count > adfQueryFilter.MaxRecords && adfQueryFilter.MaxRecords > 0)
            {
                while (resultsDataTable.Rows.Count > adfQueryFilter.MaxRecords)
                    resultsDataTable.Rows.RemoveAt(resultsDataTable.Rows.Count - 1);
            }

            return resultsDataTable;
        }

        // Gets layers of the passed-in feature type that can be queried
        public void GetQueryableLayers(string mapFunctionalityName, out string[] layerIDs, 
            out string[] layerNames, ESRI.ArcGIS.ADF.Web.FeatureType featureType)
        {
            layerIDs = null;
            layerNames = null;

            System.Collections.Generic.List<System.Data.DataTable> queryableDataTableList = 
                new System.Collections.Generic.List<System.Data.DataTable>();

            // Get the graphics dataset underlying the map functionality having the passed-in name
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet graphicsDataSet = 
                GetGraphicsDataSet(mapFunctionalityName);
            if (graphicsDataSet == null)
                return;

            // Iterate through the graphics layers in the graphics dataset, adding layers that are 
            // FeatureGraphicsLayers of the passed-in feature type to the data table list of queryable layers
            foreach (ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer in graphicsDataSet.Tables)
            {
                ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer featureGraphicsLayer = 
                    graphicsLayer as ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer;
                // If the layer is not a feature graphics layer or is not of the passed-in type, skip to the
                // next layer
                if (featureGraphicsLayer != null && featureGraphicsLayer.FeatureType != featureType)
                    continue;

                queryableDataTableList.Add(graphicsLayer);
            }

            // Re-dimension the output arrays with the number of queryable layers found
            layerIDs = new string[queryableDataTableList.Count];
            layerNames = new string[queryableDataTableList.Count];

            // Iterate through the queryable table list, adding the name of each table to the output arrays
            for (int i = 0; i < queryableDataTableList.Count; ++i)
            {
                layerIDs[i] = queryableDataTableList[i].TableName;
                layerNames[i] = queryableDataTableList[i].TableName;
            }
        }

        // Gets all the layers referenced by the map functionality of the specified name that can be queried
        public void GetQueryableLayers(string mapFunctionalityName, out string[] layerIDs, 
            out string[] layerNames)
        {
            layerIDs = null;
            layerNames = null;

            // Get the graphics dataset underlying the map functionality having the passed-in name
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet graphicsDataSet = 
                GetGraphicsDataSet(mapFunctionalityName);
            if (graphicsDataSet == null)
                return;

            // Copy the names of the tables in the graphics dataset to the output arrays
            layerIDs = new string[graphicsDataSet.Tables.Count];
            layerNames = new string[graphicsDataSet.Tables.Count];
            for (int i = 0; i < graphicsDataSet.Tables.Count; ++i)
            {
                layerIDs[i] = graphicsDataSet.Tables[i].TableName;
                layerNames[i] = graphicsDataSet.Tables[i].TableName;
            }
        }

        // Gets the names of the fields in the layer specified by the passed-in ID
        public string[] GetFields(string mapFunctionalityName, string layerID)
        {
            // Get the Web ADF graphics layer belonging to the map functionality specified by the passed-in
            // name with the passed-in ID
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = 
                GetGraphicsLayer(mapFunctionalityName, layerID);
            if (graphicsLayer == null)
                return null;

            // Copy the names of all the columns in the graphics layer to a string array
            string[] fieldNameArray = new string[graphicsLayer.Columns.Count];
            for (int i = 0; i < graphicsLayer.Columns.Count; ++i)
            {
                fieldNameArray[i] = graphicsLayer.Columns[i].ColumnName;
            }

            return fieldNameArray;
        }

        // Gets the names and types of the fields in the layer specified by the passed-in ID
        public string[] GetFields(string mapFunctionalityName, string layerID, out System.Type[] fieldTypes)
        {
            // Get the Web ADF graphics layer belonging to the map functionality specified by the passed-in
            // name with the passed-in ID
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = 
                GetGraphicsLayer(mapFunctionalityName, layerID);
            if (graphicsLayer == null)
            {
                fieldTypes = null;
                return null;
            }

            // Copy the names of all the columns in the graphics layer to a string array, and the types of
            // the columns to the passed-in type array
            string[] fieldNameArray = new string[graphicsLayer.Columns.Count];
            fieldTypes = new System.Type[graphicsLayer.Columns.Count];
            for (int i = 0; i < graphicsLayer.Columns.Count; ++i)
            {
                fieldNameArray[i] = graphicsLayer.Columns[i].ColumnName;
                fieldTypes[i] = graphicsLayer.Columns[i].DataType;
            }

            return fieldNameArray;
        }

        #endregion

        #region IGISFunctionality implementation

        public System.Web.UI.WebControls.WebControl WebControl
        {
            get { return m_webControl; }
            set { m_webControl = value; }
        }

        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        public ESRI.ArcGIS.ADF.Web.DataSources.IGISResource Resource
        {
            get { return m_gisResource; }
            set { m_gisResource = value; }
        }

        public bool Initialized
        {
            get { return m_initialized; }
        }

        public void LoadState() { }

        public void Initialize() { m_initialized = true; }

        public void SaveState() { }

        // Set the flag indicating whether the functionality is intitialized to false.  Any necessary
        // disposal logic (e.g. releasing object references) should go here.  Note that, if there is
        // additional logic here, users of this class will have to EXPLCITLY call dispose.  It is not
        // invoked by other Web ADF components or the Page life-cycle.
        public void Dispose() { m_initialized = false; }

        public bool Supports(string operation)
        {
            return true;
        }

        #endregion
    }
}