-2

Hope I can get some help here.

On a DEV server I created a C# windows service in Visual Studio 2013 Community. I have tested it in debug mode: In Main()

#if DEBUG
   ...run debug code...
#else
   ...run service code...
#endif

In debug mode it runs perfectly fine. I then added an installer class and successfully installed the service on the same server and started it in the Services window. However, it doesn't do anything. I checked the Event log and saw this error message:

Application: SharenetIFF.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.NullReferenceException
at SharenetIFF.RunValues.GetRunValues()
at SharenetIFF.SearchFiles.LookforIFFFiles(Int32)
at SharenetIFF.Program.DoThis()
at System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Threading.ThreadHelper.ThreadStart()

Here is the code in RunValues:

class RunValues
{
    public int id { get; set; }
    public int runTimeSpan { get; set; }
    public int numberOfRunTime { get; set; }
    private SqlConnection myCon = new SqlConnection();

    public List<int> GetRunValues()
    {
        List<int> values = null;
        string destPath = "";

        try
        {
            string mySQL = "select RunFreq, RunTimes from IFFRunValues";

            myCon.ConnectionString = ConfigurationManager.ConnectionStrings["DatabaseConn"].ConnectionString;                
            myCon.Open();

            SqlCommand myCmd = new SqlCommand(mySQL, myCon);
            SqlDataReader runValuesReader = myCmd.ExecuteReader();
            if (runValuesReader.HasRows)
            {
                while (runValuesReader.Read())
                {
                    runTimeSpan = Convert.ToInt16(runValuesReader["RunFreq"]);
                    numberOfRunTime = Convert.ToInt16(runValuesReader["RunTimes"]);
                }
                values = new List<int>();
                values.Add(runTimeSpan);
                values.Add(numberOfRunTime);
            }
            runValuesReader.Close();
            myCon.Close();
            runValuesReader.Dispose();
            myCmd.Dispose();
            myCon.Dispose();
        }
        catch (Exception ex)
        {
            destPath = Path.Combine("C:\\",  "error_log.txt");
            File.AppendAllText(destPath, ex.Message);
            values.Clear();
        }

        return values;
    }
}

I am thinking it is failing on connection string, mostly because there is nothing else here. But no idea why. All the code is in the try/catch blocks, so how is an unhandled exception even possible? If the service is released to the same development machine it was developed on, does a service need different permissions if running outside of visual studio?

Steve
  • 213,761
  • 22
  • 232
  • 286
jchristo
  • 11
  • 2
  • 2
    Yes, you do have everything wrapped in try/catch, but don't forget code within `catch` can throw its own exceptions.The unhandled `NullReferenceException` is almost definitely coming from the `catch` block's `values.Clear()`. Before the `try`, you initialize `values` to `null`. You must not be getting far enough in the `try` to initialize `values`, so that's a hint. – p e p Mar 28 '18 at 21:03
  • Anything you found in `error_log.txt`? – Chetan Mar 28 '18 at 21:05
  • @ChetanRanpariya based on what I am saying in my first comment, I expect there should definitely be useful info (the original exception's error message) logged in that text file. – p e p Mar 28 '18 at 21:06
  • 1
    Creating a file in the root of the system drive is not wise. You need to have a lot of permissions to do it. Move the log file in a folder where you can give read/write permissions to everyone. Do you have a file with that name in the root of the system drive? – Steve Mar 28 '18 at 21:22
  • Also, use a local variable inside a using statement for SQLConnection. Currently, when you have an exception, nothing will be disposed. – Zohar Peled Mar 29 '18 at 06:42

1 Answers1

0

This started out as a comment, but got too long, so...

It's not entirely clear what the method should do. On one hand, you populate the properties of the instance. On the other hand, you return a list of int, but you convert the values to Int16 (that's short) instead of to Int32. You populate that list after the loop, meaning you get only the last values returned from your select statement, that has no order by clause meaning the rows are returned in an arbitrary order, and you do not use the Id property at all.

Also, please note Steve's comment about saving files directly to C:\.

That's before we even touched the fact that you are using a field for SQLConnection, instead of a local variable inside the using statement, which will make sure it will be closed and disposed once you are done with it, or the fact that you only dispose everything inside the try block, meaning that if you have an exception before your code reaches there, nothing gets disposed, or the fact that table columns in the database might be nullable, so Convert.ToInt might fail if the reader contains DBNull.Value instead of an actual value.

That being said, Here is an improved version of your current code. I've only modified it to the point it should dispose everything correctly, gets the values from the dataReader safely and not throw the NullReferenceException, but as I said, it's not very clear to me what you try to achieve in this method.

class RunValues
{
    public int id { get; set; }
    public int runTimeSpan { get; set; }
    public int numberOfRunTime { get; set; }

    public List<int> GetRunValues()
    {
        var values = new List<int>();

        // I'm guessing it needs " where id = @id" here...
        var mySQL = "select RunFreq, RunTimes from IFFRunValues"; 
        using(var myCon = new SqlConnection(ConfigurationManager.ConnectionStrings["DatabaseConn"].ConnectionString))
        {
            using(var myCmd = new SqlCommand(mySQL, myCon))
            {
                try
                {
                    myCon.Open();
                    SqlDataReader runValuesReader = myCmd.ExecuteReader();
                    if (runValuesReader.HasRows)
                    {
                        while (runValuesReader.Read())
                        {
                            runTimeSpan = runValuesReader.GetValueOrDefault<int>("RunFreq");
                            numberOfRunTime = runValuesReader.GetValueOrDefault<int>("RunTimes");
                            values.Add(runTimeSpan);
                            values.Add(numberOfRunTime);
                        }
                    }
                }
                catch (Exception ex)
                {
                    var destPath = Path.Combine("C:\\",  "error_log.txt");
                    File.AppendAllText(destPath, ex.Message);
                    values.Clear();
                }
            }
        }            
        return values;
    }
}

The GetValueOrDefault<T> is an extension method I've been using so long I can't even remember where I first encountered it - here is the class that contains it:

/// <summary>
/// Provides extension methods for IDataReader.
/// </summary>
public static class IDataReaderExtensions
{
    /// <summary>
    /// Gets the value of type T from the column specified by the index parameter, or default(T) if it's null.
    /// </summary>
    /// <typeparam name="T">The type of the value to get.</typeparam>
    /// <param name="reader">An instance of a class implementing IDataReader.</param>
    /// <param name="index">The index of the column from where to get the value.</param>
    /// <returns>The value of type T from the specified column, default(T) if null.</returns>
    public static T GetValueOrDefault<T>(this IDataReader reader, int index)
    {
        return (Convert.IsDBNull(reader[index])) ? default(T) : (T)reader.GetValue(index);
    }

    /// <summary>
    /// Gets the value of type T from the column specified by the name parameter, or default(T) if it's null.
    /// </summary>
    /// <typeparam name="T">The type of the value to get.</typeparam>
    /// <param name="reader">An instance of a class implementing IDataReader.</param>
    /// <param name="name">The name of the column from where to get the value.</param>
    /// <returns>The value of type T from the specified column, default(T) if null.</returns>
    public static T GetValueOrDefault<T>(this IDataReader reader, string name)
    {
        return reader.GetValueOrDefault<T>(reader.GetOrdinal(name));
    }
}
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
  • Thank you @zohar-peled and everyone else for your responses. I will incorporate your changes into my code and test in my system. – jchristo Mar 29 '18 at 18:10
  • Okay, I made the changes but was still getting a nullreference error at SharenetIFF.SearchFiles.LookforIFFFiles(). I went back to the ServiceInstallerProcess and switched account from 'User' to 'LocalSystem', then rebuilt and reinstalled. Now I get this in the EventLog: 'Login failed for user 'NT AUTHORITY\SYSTEM'. Reason: Failed to open the explicitly specified database 'Sharenet'. [CLIENT: ]'. Any ideas? – jchristo Mar 29 '18 at 18:38
  • Now the exception is in another method, isn't it? I thonk The login faild because the system user does not have access to the database, but that's a good thing - you want to restrict acess to the databse as much as possible. Change the user back and Debug the method that throw the exception – Zohar Peled Mar 29 '18 at 18:59
  • Found the null reference! Now I only get this Event log: Login failed for user 'NT AUTHORITY\SYSTEM'. Reason: Failed to open the explicitly specified database 'Sharenet'. [CLIENT: ] – jchristo Mar 29 '18 at 20:55