views:

328

answers:

3

I have a fairly complex SQL query that pulls different types of products from a database based on a customer ID. It pulls three different types of products, identified by their unique identifier number ranges (i.e., IDs 1000-1999 are one type of product, 2000-2999 are another, and 3000-3999 are yet another).

SELECT b.fldMachineName, m2.fldRotaryPressName, mids.fldMachine_ID
FROM dbo.tblCustomerGeneralInfo c
LEFT JOIN dbo.tblMachine_IDs mids ON c.fldCustomer_ID = mids.fldCustomer_ID
LEFT JOIN dbo.tblBobstFlatDieSpecs b ON mids.fldMachine_ID = b.fldMachine_ID
LEFT JOIN dbo.tblDieInfo m1 ON mids.fldMachine_ID = m1.fldMachine_ID
LEFT JOIN dbo.tblRotaryDieSpecs sm ON m1.fldMachine_ID = sm.fldMachine_ID
LEFT JOIN dbo.tblRotaryPresses m2 ON sm.fldRotaryPress_ID = m2.fldRotaryPress_ID
WHERE c.fldCustomer_ID = '20'
ORDER BY fldRotaryPressName

c.fldCustomer_ID is the only piece of user input that I need, and when I run this query against the database in SQL Server Management Studio Express, it runs fine. However, when I wrap this in a using statement in the web part I am writing in C# for SharePoint, it does not return the first row that it should, instead returning a null value. For instance, if in SSMS I get three results (say, '1001,' '2008,' and 3045') then my datareader will return only two results, with a null value for the first (i.e., 'null,' '2008,' and '3045'). Here is my code in C#:

            con.Open();
            using (SqlCommand cmd = new SqlCommand("SELECT b.fldMachineName, m2.fldRotaryPressName, mids.fldMachine_ID " +
                                                   "FROM dbo.tblCustomerGeneralInfo c " +
                                                   "LEFT JOIN dbo.tblMachine_IDs mids ON c.fldCustomer_ID = mids.fldCustomer_ID " +
                                                   "LEFT JOIN dbo.tblBobstFlatDieSpecs b ON mids.fldMachine_ID = b.fldMachine_ID " +
                                                   "LEFT JOIN dbo.tblDieInfo m1 ON mids.fldMachine_ID = m1.fldMachine_ID " +
                                                   "LEFT JOIN dbo.tblRotaryDieSpecs sm ON m1.fldMachine_ID = sm.fldMachine_ID " +
                                                   "LEFT JOIN dbo.tblRotaryPresses m2 ON sm.fldRotaryPress_ID = m2.fldRotaryPress_ID " +
                                                   "WHERE c.fldCustomer_ID = @CustomerID " +
                                                   "ORDER BY fldRotaryPressName", con))
            {
                cmd.Parameters.Add("@CustomerID", SqlDbType.VarChar, 4).Value = customers.SelectedValue.ToString();

Our SQL profiler shows the same query being passed regardless of if it's done through SSMS or from the web part, except for the literal being used versus the parameter. Also, if I change the parameter to a literal, I have the same result. Is there a discrepancy in the way that C# handles SQL queries as opposed to SSMS, or have I somehow implemented it incorrectly?

Here is the code for pulling the data from the reader into the dropdownlist, for the sake of completeness:

                    dr.read();
                    while (dr.Read())
                    {
                        try
                        {
                            string itemValue = Convert.ToString(dr["fldMachine_ID"]);
                            string flatName = Convert.ToString(dr["fldMachineName"]);
                            if (!string.IsNullOrEmpty(flatName))
                            {
                                items.Add(flatName, itemValue);
                            }
                            string rotaryName = Convert.ToString(dr["fldRotaryPressName"]);
                            if (!string.IsNullOrEmpty(rotaryName))
                            {
                                items.Add(rotaryName, itemValue);
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.ToString());
                        }
                    }

                        // Bind list to ddl.
                        machines.DataSource = items;
                        machines.DataValueField = "Value";
                        machines.DataTextField = "Key";
                        machines.DataBind();

                        machines.Enabled = true;
                    }

I am completely stumped, and I really appreciate any help I can get.

+1  A: 

update

Turned out the problem was a extra dr.Read() call before the loop. See comments.

update

Looking at the code, it seems the databind is in the wrong place -- maybe something like this? Also, I changed it to show the null items... maybe this will expose a logic problem.

while (dr.Read())
{
    try
    {
      string itemValue = dr["fldMachine_ID"].ToString();
      string flatName =  dr["fldMachineName"].ToString();
      if (string.IsNullOrEmpty(flatName)) flatName = "!NULL!";
      if (string.IsNullOrEmpty(itemValue)) itemValue = "!NULL!";
      items.Add(flatName, itemValue);

      string rotaryName = dr["fldRotaryPressName"].ToString();
      if (string.IsNullOrEmpty(rotaryName)) rotaryName= "!NULL!";
      items.Add(rotaryName, itemValue);
    }
    catch (Exception ex)
    {
      MessageBox.Show(ex.ToString());
    }

}
// Bind list to ddl.
machines.DataSource = items;
machines.DataValueField = "Value";
machines.DataTextField = "Key";
machines.DataBind();

machines.Enabled = true;

old

Could it be something silly like customers.SelectedValue.ToString().Trim()?

You can run the profiler and see EXACTLY the SQL that the server is executing... then run that in SSMS to see if you still get different results.

Hogan
I'm not seeing anything like that, although I suppose that doesn't mean it's not there. In debugging, I've attached to the SharePoint process and stepped through while checking my values, and it seems to be passing the ID parameter just fine.
Geo Ego
Also, I should mention that I also tried copying the SQL that the web part sent out from the profiler and pasting that into SSMS. I had the same result.
Geo Ego
Just so I am clear, when you take the code you see in the Profiler and run it in Enterprise Manager you get the expected results?
Hogan
I actually don't have the MS Profiler right now ... our server software is only available to the network admins so I'm waiting to talk to one to get that on my machine. However, I did use Arj Lab's open source profiler to get the results I mentioned before. I'm going to try your code above now and see if I can find some faults in the logic. Thanks for your help!
Geo Ego
Alright. It appears that nulls are part of the problem. I swapped in your code above and run some queries against the database using the web part. I'm now receiving several "!NULL!" values mixed in with my results for the customers that are giving me problems. I'm working on the logic right now, but I'll gladly accept any more input you have!
Geo Ego
how about changing this join to look like this: `LEFT JOIN dbo.tblRotaryDieSpecs sm ON mids.fldMachine_ID = sm.fldMachine_ID` since mids table is the machine table, go to the source.
Hogan
MSSQL's profiler is not part of the server software, you can run it from the local machine and should be availble in SQL Server Management Studio -- Under Tools|SQL Server Profiler. What is Arj Lab's open source one? A google search did not turn it up.
Hogan
Sorry, I meant to say our database software is also part of the network admin's domain. Anj Lab's tool is here (I see I mistyped it as Arj Lab): http://anjlab.com/en/projects/opensource/sqlprofiler. The reason I can't change the join in that way is that different types of identifiers are used to identify different machines. It's a strange data model that resulted in a unique schema in order to be properly normalized. Actually, you helped me immensely in getting this query: http://stackoverflow.com/questions/1848398/sql-joins-and-logic.
Geo Ego
I also tried changing the SQL query as per your statement. It produces identical results.
Geo Ego
Sorry Geo, I'm out of ideas -- unless I can see some sample data I can't think of anything that would help. Reducing the test data to the smallest dataset that shows the problem might actually help in tracking down the logic flaw. I think that might be the next step to finding the solution.
Hogan
Alright, I'll work with that in mind. Thanks for your help!
Geo Ego
I've been testing this out with a particular customer number that shows the problem. No dice. I copy-pasted the query directly from the profiler into SSMS and ran the two queries one after the other. Both produced the same (correct) results. Is there a way that I can view the contents of the datareader while debugging? I've looked around in its properties and can't seem to see where the actual data is stored. I'd like to see if the data is getting to the reader properly and if my logic is somehow excluding those results.
Geo Ego
Alright. Looking again over a few sets of data, I realized that I overlooked a very obvious similarity: the datareader itself is always returning 'null' for the first result. I stepped through the loop that contains the datareader and each time, the first result returns a null value instead of the first value that it should see, and then continues fine through the rest of the values. I can't seem to find this problem anywhere else. I may change this question a bit and repost it since the problem is not what I thought.
Geo Ego
Then the problem might be in the query is the query returning null for one of the rows?
Hogan
No. The query via SSMS returns the right number of rows, and does the same if I copy and paste the query from my web part and run it through SSMS. Example:@CustomerID='8'SSMS: 1004, 1005, 3007, 2012Web part: 1005, 3007, 2012@CustomerID='9'SSMS: 1015, 3019, 2061Web part: 3019, 2061I checked this over about ten customers, and it consistently does the same thing ... the query works fine, but the datareader is returning null in place of that first result. I'm baffled.
Geo Ego
and the code looks like what I put up?
Hogan
what code are you using to see the items list?
Hogan
also try this: make to lists items1 and items2 then use `items1.Add(flatName, itemValue);` and`items.Add(rotaryName, itemValue);`
Hogan
Pretty much. I went back to the null-handling that I had implemented before, because with the code you posted above it chokes when it hits a null, although that was great for exposing them. I did, however, move the databinding out to the end of the statement as you had it.
Geo Ego
I just updated the code that I have. I'll try your other ideas and get back to you shortly.
Geo Ego
@Geo: Please try it with the databind outside of the while loop. databind should only be used once!
Hogan
Sorry about that. Databind is outside of the loop; I copied it into the wrong place when I updated my current code. I tried the separate lists; same results. No matter what I do, that datareader is consistently skipping the first results in comparison to the same query through SSMS.
Geo Ego
you are sure there is no `dr.Read()` before the while loop?
Hogan
You hit it on the head. I had an errant dr.Read() just before the while loop. Took that out and now everything works like a charm. Sorry for the foolish mistake, but thanks so much for taking the time to help me out with this. I'm going to go ahead and mark this as the answer if you want to go ahead and just mention in the answer that it's the dr.read() before the while loop. Thanks again!
Geo Ego
A: 

You are doing the databind inside the read loop. Really you should bind to an enumerable after it has been populated.

Also, look at the command.AddWithValue() method.

ck
AddWithValue() will probably fail. He wants numbers represented as strings, AWV should convert to a number. (Of course, customerID should be represented as an int in the DB, but that is another story...)
Hogan
My mistake there. Hogan pointed that out the first time around and I did fix it. I just copied that chunk into the wrong spot when I updated my code. I had looked at AddWithValue, but yeah, I don't see it working since everything I'm dealing with are strings.
Geo Ego
+2  A: 

your code sample shows you calling Read on the DataReader twice at the start, that would cause the reader to skip the first row. You should only need the read call in the while loop.

  dr.read();  // unnecessary read call
  while (dr.Read())
  { }
Venr
A side note: The code looked different for a while before he found the problem and put the read() in the code example.
Hogan