views:

39

answers:

2

This is a fun one.

I have written a custom search page that provides faster, more user friendly searches than the default Contacts view and also allows searching of Leads and Contacts simultaneously. It uses GridViews bound to SqlDataSources that query filtered views. I'm sure someone will complain that I'm not using the web services for this, but this is just the design decision we made.

These GridViews live in UpdatePanels to enable very slick AJAX updates upon search.

It's all working great. Nearly ready to be deployed, except for one thing: Some long running searches are triggering an uncatchable SQL timeout exception.

    [SqlException: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.]
  at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
  at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
  at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
  at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
  at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
  at System.Data.SqlClient.SqlDataReader.get_MetaData()
  at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
  at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
  at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
  at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
  at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
  at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
  at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
  at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
  at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
  at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet, String srcTable)
  at System.Web.UI.WebControls.SqlDataSourceView.ExecuteSelect(DataSourceSelectArguments arguments)
  at System.Web.UI.DataSourceView.Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback)
  at System.Web.UI.WebControls.DataBoundControl.PerformSelect()
  at System.Web.UI.WebControls.BaseDataBoundControl.DataBind()
  at System.Web.UI.WebControls.GridView.DataBind()
  at System.Web.UI.WebControls.BaseDataBoundControl.EnsureDataBound()
  at System.Web.UI.WebControls.CompositeDataBoundControl.CreateChildControls()
  at System.Web.UI.Control.EnsureChildControls()
  at System.Web.UI.Control.PreRenderRecursiveInternal()
  at System.Web.UI.Control.PreRenderRecursiveInternal()
  at System.Web.UI.Control.PreRenderRecursiveInternal()
  at System.Web.UI.Control.PreRenderRecursiveInternal()
  at System.Web.UI.Control.PreRenderRecursiveInternal()
  at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

I found that CRM is doing a server.transfer to capture this error because my UpdatePanels started throwing JavaSript errors when this error would occur. I was only able to get the full error message by using the JavaScript debugger in IE.

Having found this error, I thought the solution would be simple. I just needed to wrap my databind calls in try/catch blocks to capture any errors.

Unfortunately it seems CRM's IIS configuration has the magic ability to capture this error before it ever gets back to my code. Using the debugger I never see it. It never gets to my catch blocks, but it's clearly happening in the SQL Data Source which is clearly (by the stack trace) being triggered by my GridView bind.

Any ideas on this? It's driving me crazy.

Code Behind (with some irrelevant functions omitted):

  protected void Page_Load(object sender, EventArgs e)
  {
    //Initialize some stuff
    this.bannerOracle = new OdbcConnection(ConfigurationManager.ConnectionStrings["OracleConnectionString"].ConnectionString);

    //Prospect default
    HideProspects();
    HideProspectAddressColumn();

    //Contacts default
    HideContactAddressColumn();

    //Default error messages
    gvContacts.EmptyDataText = "Sad day. Your search returned no contacts.";
    gvProspects.EmptyDataText = "Sad day. Your search returned no prospects.";

    //New search
    try
    {
      SearchContact(null, -1);
    }
    catch
    {
      gvContacts.EmptyDataText = "Oops! An error occured. This may have been a timeout. Please try your search again.";
      gvContacts.DataSource = null;
      gvContacts.DataBind();
    }
  }
  protected void txtSearchString_TextChanged(object sender, EventArgs e)
  {
    if(!String.IsNullOrEmpty(txtSearchString.Text))
    {
      try
      {
        SearchContact(txtSearchString.Text, Convert.ToInt16(lstSearchType.SelectedValue));
      }
      catch
      {
        gvContacts.EmptyDataText = "Oops! An error occured. This may have been a timeout. Please try your search again."; 
        gvContacts.DataSource = null;
        gvContacts.DataBind();
      }

      if (chkProspects.Checked == true)
      {
        try
        {
          SearchProspect(txtSearchString.Text, Convert.ToInt16(lstSearchType.SelectedValue));
        }
        catch
        {
          gvProspects.EmptyDataText = "Oops! An error occured. This may have been a timeout. Please try your search again.";
          gvProspects.DataSource = null;
          gvProspects.DataBind();
        }
        finally
        {
          ShowProspects();
        }
      }
      else
      {
        HideProspects();
      }
    }
  }
  protected void SearchContact(string search, int type)
  {
    SqlCRM_Contact.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQLConnectionString"].ConnectionString;
    gvContacts.DataSourceID = "SqlCRM_Contact";

    string strQuery = "";
    string baseQuery = @"SELECT filteredcontact.contactid,
                 filteredcontact.new_libertyid,
                 filteredcontact.fullname,
                 'none' AS line1,
                 filteredcontact.emailaddress1,
                 filteredcontact.telephone1,
                 filteredcontact.birthdateutc AS birthdate,
                 filteredcontact.gendercodename
              FROM filteredcontact "; 
    switch(type)
    {
      case LASTFIRST:
        strQuery = baseQuery + "WHERE fullname LIKE @value AND filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case LAST:
        strQuery = baseQuery + "WHERE lastname LIKE @value AND filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case FIRST:
        strQuery = baseQuery + "WHERE firstname LIKE @value AND filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case LIBERTYID:
        strQuery = baseQuery + "WHERE new_libertyid LIKE @value AND filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case EMAIL:
        strQuery = baseQuery + "WHERE emailaddress1 LIKE @value AND filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case TELEPHONE:
        strQuery = baseQuery + "WHERE telephone1 LIKE @value AND filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case BIRTHDAY:
        strQuery = baseQuery + "WHERE filteredcontact.birthdateutc BETWEEN @dateStart AND @dateEnd AND filteredcontact.statecode = 0";
        try
        {
          DateTime temp = DateTime.Parse(search);
          if (temp.Year < 1753 || temp.Year > 9999)
          {
            search = string.Empty;
          }
          else
          {
            search = temp.ToString("yyyy-MM-dd");
          }
        }
        catch
        {
          search = string.Empty;
        }
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("dateStart", DbType.String, search.Trim() + " 00:00:00.000");
        SqlCRM_Contact.SelectParameters.Add("dateEnd", DbType.String, search.Trim() + " 23:59:59.999");
        break;
      case SSN:
        //Do something
        break;
      case ADDRESS:
        strQuery = @"SELECT contactid,
               new_libertyid,
               fullname,
               line1,
               emailaddress1,
               telephone1,
               birthdate,
               gendercodename 
               FROM (SELECT FC.contactid,
                     FC.new_libertyid,
                     FC.fullname,
                     FA.line1,
                     FC.emailaddress1,
                     FC.telephone1,
                     FC.birthdateutc AS birthdate,
                     FC.gendercodename,
                     ROW_NUMBER() OVER(PARTITION BY FC.contactid ORDER BY FC.contactid DESC) AS rn 
                    FROM filteredcontact FC 
                    INNER JOIN FilteredCustomerAddress FA
                    ON FC.contactid = FA.parentid
                    WHERE FA.line1 LIKE @value AND FA.addressnumber <> 1 AND FC.statecode = 0 ) AS RESULTS
               WHERE rn = 1";
        SqlCRM_Contact.SelectCommand = strQuery;
        SqlCRM_Contact.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        ShowContactAddressColumn();
        break;
      default:
        strQuery = @"SELECT TOP 500 filteredcontact.contactid,
                 filteredcontact.new_libertyid,
                 filteredcontact.fullname,
                 'none' AS line1,
                 filteredcontact.emailaddress1,
                 filteredcontact.telephone1,
                 filteredcontact.birthdateutc AS birthdate,
                 filteredcontact.gendercodename
              FROM filteredcontact 
              WHERE filteredcontact.statecode = 0";
        SqlCRM_Contact.SelectCommand = strQuery;
        break;
    }
    if (type != ADDRESS)
    {
      HideContactAddressColumn();
    }
    gvContacts.PageIndex = 0;
    //try
    //{
    //  SqlCRM_Contact.DataBind();
    //}
    //catch
    //{
    //  SqlCRM_Contact.DataBind();
    //}
    gvContacts.DataBind();
  }
  protected void SearchProspect(string search, int type)
  {
    SqlCRM_Prospect.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQLConnectionString"].ConnectionString;
    gvProspects.DataSourceID = "SqlCRM_Prospect";

    string strQuery = "";
    string baseQuery = @"SELECT filteredlead.leadid,
                 filteredlead.fullname,
                 'none' AS address1_line1,
                 filteredlead.emailaddress1,
                 filteredlead.telephone1,
                 filteredlead.lu_dateofbirthutc AS lu_dateofbirth,
                 filteredlead.lu_gendername
              FROM filteredlead ";

    switch (type)
    {
      case LASTFIRST:
        strQuery = baseQuery + "WHERE fullname LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case LAST:
        strQuery = baseQuery + "WHERE lastname LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case FIRST:
        strQuery = baseQuery + "WHERE firstname LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case LIBERTYID:
        strQuery = baseQuery + "WHERE new_libertyid LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case EMAIL:
        strQuery = baseQuery + "WHERE emailaddress1 LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case TELEPHONE:
        strQuery = baseQuery + "WHERE telephone1 LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        break;
      case BIRTHDAY:
        strQuery = baseQuery + "WHERE filteredlead.lu_dateofbirth BETWEEN @dateStart AND @dateEnd AND filteredlead.statecode = 0";
        try
        {
          DateTime temp = DateTime.Parse(search);
          if (temp.Year < 1753 || temp.Year > 9999)
          {
            search = string.Empty;
          }
          else
          {
            search = temp.ToString("yyyy-MM-dd");
          }
        }
        catch
        {
          search = string.Empty;
        }
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("dateStart", DbType.String, search.Trim() + " 00:00:00.000");
        SqlCRM_Prospect.SelectParameters.Add("dateEnd", DbType.String, search.Trim() + " 23:59:59.999");
        break;
      case SSN:
        //Do nothing
        break;
      case ADDRESS:
        strQuery = @"SELECT filteredlead.leadid,
                 filteredlead.fullname,
                 filteredlead.address1_line1,
                 filteredlead.emailaddress1,
                 filteredlead.telephone1,
                 filteredlead.lu_dateofbirthutc AS lu_dateofbirth,
                 filteredlead.lu_gendername
              FROM filteredlead WHERE address1_line1 LIKE @value AND filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        SqlCRM_Prospect.SelectParameters.Add("value", DbType.String, search.Trim() + "%");
        ShowProspectAddressColumn();
        break;
      default:
        strQuery = @"SELECT TOP 500 filteredlead.leadid,
                 filteredlead.fullname,
                 'none' AS address1_line1
                 filteredlead.emailaddress1,
                 filteredlead.telephone1,
                 filteredlead.lu_dateofbirthutc AS lu_dateofbirth,
                 filteredlead.lu_gendername
              FROM filteredlead WHERE filteredlead.statecode = 0";
        SqlCRM_Prospect.SelectCommand = strQuery;
        break;
    }
    if (type != ADDRESS)
    {
      HideProspectAddressColumn();
    }
    gvProspects.PageIndex = 0;
    //try
    //{
    //  SqlCRM_Prospect.DataBind();
    //}
    //catch (Exception ex)
    //{
    //  SqlCRM_Prospect.DataBind();
    //}
    gvProspects.DataBind();
  }
A: 

I'm not sure why you can't catch the exception, but my guess is that it is because of data-binding magic. However, you could at least increase the timeout:

http://blog.customereffective.com/blog/2008/07/increase-crm-sq.html

Hope that helps!

Josh Painter
A: 

Use the Selected event of the SqlDataSource, and examine the Exception property to see if an exception has occurred. You can also use the ExceptionHandled property to indicate that you have handled the event.

P.S. Note how much of your posted code was necessary for me to answer the question.

John Saunders
Hi John,I will try that.I posted the amount of code I did because I was sure someone was going to claim I wasn't using try/catch properly. Sorry if it ruined your day :)
clifgriffin
@clifgriffin: it didn't ruin my day - I ignored it. That could have ruined _your_ day if the code had been necessary in order to solve your problem.
John Saunders
John, Thank you for your help. I was able to solve my problem with the following code: protected void SqlCRM_Contact_Selected(object sender, SqlDataSourceStatusEventArgs e){ if (e.Exception != null) { ResetContactOnError(); e.ExceptionHandled = true; }}I was also able to trap the error by setting a page level error handler, but this wasn't catching it until after Render(), so it was unable to manipulate what the user sees at that point in the process.protected override void OnError(EventArgs e){ //Do stuff}Thanks again,Clif
clifgriffin