0

I have several portions of code in different classes that use this or very similar code to pull back some data from an SQL Server database. Most of them work, but I'm running into a problem on a few and really don't understand why. The code above will execute for 60-64 times, retrieving rows but then it blows up with a invalid read error. ive traced down the records for anything hinkey, but it appears to just hit anywhere between those records and stops. There are no calls out from this class to any other class functions, essentially, this is the onyl thing running.

My connection string has a time out of 600 seconds and I have mars set as well. another note; this is being called from a Parallel.ForEach with the parallelism set to 2.

As i said, I use this structure of code in multiple places through out the program, most of it working great, what worries me is not just the error, but the inconsistancy of its occurance.

when I get rid of the Parallel.ForEach and just run it as foreach, it works. I need those threads though, or this takes far too long. And I'm also curious where the reader is closing.

Exception: System.InvalidOperationException--Invalid attempt to call Read when reader is closed.

Called Code:

 public string GetUnderwritingType(string id)
    {
        UnderwritingType uwt = new UnderwritingType();
        GetRequiredPercent(id, ref uwt);
        GetParticipation(id, ref uwt);
        return CalculateUnderwritingType(ref uwt);
    }
    private void GetRequiredPercent(string id, ref UnderwritingType uwt)
    {
        DataCommon common = new DataCommon(Settings.Default.ReqPercentSP, id);
        try
        {
      /******* Error on this reader statement ********/      
using (SqlDataReader reader = common.Command.ExecuteReader(CommandBehavior.CloseConnection))               {
                /* no data */
                if (reader.HasRows == false) return;
                while (reader.Read())
                {
                    uwt.RequiredPercent.Add(Double.Parse(reader[REQ_PERCENT_FIELD].ToString()));
                    uwt.MinimumCount.Add(Int32.Parse(reader[MIN_COUNT_FIELD].ToString()));
                    uwt.UWType.Add(Int32.Parse(reader[UW_LEVEL_FIELD].ToString()));
                }
            }
        }
        catch (InvalidOperationException)
        {
            if (common.Connection.State == ConnectionState.Open) common.Connection.Close();
            throw;
        }
        catch (SqlException)
        {
            if (common.Connection.State == ConnectionState.Open) common.Connection.Close();
            throw;
        }
    }  

Code that sets up connection:

   public class DataCommon
    {
        private readonly SqlCommand _command;
        private SqlConnection _connection;

        public DataCommon(string storedProcedure, string id)
        {
            SetConnection();
            _command = new SqlCommand(storedProcedure, Connection);
            if (_command != null) _command.CommandType = CommandType.StoredProcedure;
            _command.Parameters.Add("@Id", SqlDbType.NVarChar).Value = id;
        }
        /// <summary>
        ///   Gets the database connection.
        /// </summary>
        public SqlConnection Connection
        {
            get { return _connection; }
        }
        /// <summary>
        ///   Gets the database command object.
        /// </summary>
        public SqlCommand Command
        {
            get { return _command; }
        }
        /// <summary>
        ///   Sets the database connection.
        /// </summary>
        private void SetConnection()
        {
            _connection = new SqlConnection {ConnectionString = Settings.Default.TestConnection};
            _connection.Open();
            if (_connection.State != ConnectionState.Open)
                throw new DataException(Settings.Default.ConnectionErrorMessage);
        }
    }

Connection string: Data Source=xxxxx;Initial Catalog=yyyyy;Trusted_Connection=True;MultipleActiveResultSets=True;Connection Timeout=600;

Stack Trace: .Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout) at System.Data.SqlClient.SqlDataReader.Read() at GroupDataAccess.EligibilityClassData.GetEligibilityClass(String id) in C:\Subversion\branches\Fimmas\FimmasToInfoHub\GroupHubFeed\GroupData\EligibilityClassData.cs:line 95 at GroupLogic.GroupExtract.BuildEligibilityClass(String id, Group& group) in C:\Subversion\branches\Fimmas\FimmasToInfoHub\GroupHubFeed\GroupLogic\GroupExtract.cs:line 188 at GroupLogic.GroupExtract.<>c_DisplayClass2.b_1() in C:\Subversion\branches\Fimmas\FimmasToInfoHub\GroupHubFeed\GroupLogic\GroupExtract.cs:line 113 at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.Execute()

Initial Call to go through loop (when converted to normal foreach, everythings fine):

ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 2;
Parallel.ForEach(ids, options, id => BuildGroup(id));

calls

private void BuildGroup(string id)
{
   Group[] group = { new Group() };
    Task eligibilityBlock = Task.Factory.StartNew(() =>
                                            BuildEligibilityClass(id, ref @group[0]));
            Task.WaitAny(eligibilityBlock);
             GroupList.Add(@group[0]);
}

Which calls

    private static void BuildEligibilityClass(string id, ref Group group)
    {
        EligibilityClassData eligClassData = new EligibilityClassData();
        group.EligibilityClassList = eligClassData.GetEligibilityClass(id);

    }

which hits into one of those reads.

H H
  • 263,252
  • 30
  • 330
  • 514
rigamonk
  • 1,179
  • 2
  • 17
  • 45
  • 2
    I think that the first parallel that finishes Closes the connection and the second can't read, you are using CommandBehavior.CloseConnection within your using statement. try not to close the connection but the reader only. Close the connection to the database after your parallel code finishes. – Nikola Davidovic Oct 29 '12 at 14:20
  • 1
    Can you show the code for `BuildGroup`. Also, as a side note, your `DataCommon` class should implement `IDisposable` since you are holding both a `SqlCommand` and `SqlConnection` which should be disposed. – pstrjds Oct 29 '12 at 14:21
  • According to the error message, it is more likely that the exception occurs at `reader.Read()`. But the stack trace seems to correspond to another method (`GetEligibilityClass`) than the one shown here (`GetRequiredPercent`). – Olivier Jacot-Descombes Oct 29 '12 at 14:28
  • The statement `if (reader.HasRows == false) return;` is completely superfluous. The idiomatic way of writing it would be `if (!reader.HasRows) return;` – Olivier Jacot-Descombes Oct 29 '12 at 14:31
  • Have you tried to close the connection after you complete the both parallel executions? – Nikola Davidovic Oct 29 '12 at 14:35
  • Nick - I have tried closing the connections at various times. In its current iteration it doesn't close it at all (experimentation).I would expect a connection pool error...I have also tried it without CommandBehavior.CloseConnection but it seems to make more difference. Oliver - The "GetEligibilityClass has a structure similar to this one as far as the connection and reader goes, it pops an error periodically as well, I just had to pick an example of code to show. Also, thanks for the tip on the "HasRows". – rigamonk Oct 29 '12 at 14:44

2 Answers2

0

Can you increase timeout values in following two locations and try?

  1. MSDTC timeout in Component Services.
  2. System.Transactions in machine.config of the application server.

Reference:

  1. "Invalid attempt to call Read when reader is closed" error (for lengthy operations only)
Community
  • 1
  • 1
LCJ
  • 22,196
  • 67
  • 260
  • 418
-1

I am with nick on this... "CommandBehavior.CloseConnection" will close your connection on the first thread that finishes (which explain the inconsistency). Try removing this And Remove Closing the connections in the "catch"s (just for testing).

Ori Price
  • 3,593
  • 2
  • 22
  • 37