数组索引超出了范围异常

5

这是我从一个平面文件中获取数据并插入到SQL Server的代码。它会生成一个异常(数组索引超出了范围)。

string path = string.Concat(Server.MapPath("~/TempFiles/"), Fileupload1.FileName);                       
string text = System.IO.File.ReadAllText(path);               
string[] lines = text.Split(' ');                                  
con.Open();                 
SqlCommand cmd = new SqlCommand();                 
string[] Values = new string[3];                                 
foreach (string line1 in lines)                 
{                     
    Values = line1.Split(';');                                           
    string query = "INSERT INTO demooo VALUES ('" + Values[0] + "','" + Values[1] + "','" + Values[2] + "')";                     
    cmd = new SqlCommand(query,con);                     
    cmd.ExecuteNonQuery();                  
} 
3个回答

2
异常发生的原因是你的一行中使用分号分隔的元素不足三个。尽管你将“Values”声明为一个具有三个元素的字符串数组,但将变量赋值给“String.Split()”函数的结果使其无关紧要:你的数组将具有返回数组的任何长度。如果它更少,你的代码肯定会失败。
如果这不应该发生,我建议你在代码中进行断言来帮助调试:
// ...
Values = line1.Split(';');
// the following will make the debugger stop execution if line.Length is smaller than 3
Debug.Assert(line1.Length >= 3);
// ...

作为一则附注,我应该提到批量插入会更加高效。另外,您声明和重新赋值cmd变量的方式不太正确。最后,您应该在值上调用String.Replace以确保任何撇号都被加倍。否则,您的代码将容易受到SQL注入攻击的威胁。

2
一些关于你的代码在运行时表现的细节:
// This line declares a variable named Values and sets its value to 
// a new array of strings. However, this new array is never used 
// because the loop overwrites Values with a new array before doing 
// anything else with it.
string[] Values = new string[3];                                 
foreach (string line1 in lines)                 
{                     
    Values = line1.Split(';');        
// At this point in the code, whatever was previously stored in Values has been
// tossed on the garbage heap, and Values now contains a brand new array containing
// the results of splitting line1 on semicolons.
// That means that it is no longer safe to assume how many elements the Values array has.
// For example, if line1 is blank (which often happens at the end of a text file), then
// Values will be an empty array, and trying to get anything out of it will throw an
// exception                                   
    string query = "INSERT INTO demooo VALUES ('" + Values[0] + "','" + Values[1] + "','" + Values[2] + "')";                     
    cmd = new SqlCommand(query,con);                     
    cmd.ExecuteNonQuery();                  
} 

与Values不断被覆盖的方式类似,循环外创建的SqlCommand也永远不会被使用。将这两个声明放在循环内是安全的。以下代码实现了这一点,并添加了一些错误检查以确保从行中检索到了可用的值。如果跳过任何长度不足的行是可以接受的,那么这将简单地跳过这些行——如果不可以,那么您可能需要编写自己的更复杂的错误处理代码。
foreach(string line in lines) 
{
    string[] values = line.split[';'];
    if(values.Length >= 3)
    {
        string query = "INSERT INTO demooo VALUES ('" + Values[0] + "','" + Values[1] + "','" + Values[2] + "')";      
        using (SqlCommand command = new SqlCommand(query, con))
        {
            cmd.ExecuteNonQuery();
        }
    }
}

作为最后一点说明,如果您在类似Web应用程序中使用上述代码,它可能会受到黑客攻击。请考虑如果您正在处理一个看起来像这样的文件,可能会向服务器发送什么命令:
1;2;3
4;5;6
7;8;9') DROP TABLE demooo SELECT DATALENGTH('1    

更加安全的选择是使用参数化查询,这有助于防止此类攻击。它通过将命令与其参数分开来完成此操作,从而帮助您防止传递像SQL代码一样的参数值。设置方式示例如下:

string query = "INSERT INTO demooo VALUES (@val1, @val2, @val3);
using (var command = new SqlCommand(query, con))
{
    command.Parameters.AddWithValue("@val1", Values[0]);
    command.Parameters.AddWithValue("@val2", Values[1]);
    command.Parameters.AddWithValue("@val3", Values[2]);
    command.ExecuteNonQuery();
}

+1 是因为提到了 SQL 注入。然而,OP 的问题中的文本是通过空格分隔的(而不是像您的示例那样通过换行符),这使得此类攻击更加困难。 - Mr Lister
实际上,我认为最好的做法是让OP进行批量插入。不过,对于非常详细的答案,我还是要点赞的。 - Crono

1
尝试这个。
string path = string.Concat(Server.MapPath("~/TempFiles/"), Fileupload1.FileName);
string text = System.IO.File.ReadAllText(path);
string[] lines = text.Split(' ');
con.Open();
string[] Values;
foreach (string line1 in lines)
{
    Values = line1.Split(';');

    if (Values.Length >= 3)
    {
        string query = "INSERT INTO demooo VALUES ('" + Values[0] + "','" + Values[1] + "','" + Values[2] + "')";
    }
    else
    {
      //Some error occured
    }

    using (var cmd = new SqlCommand(query,con))
    {
        cmd.ExecuteNonQuery();
    }
}

1
你肯定是指 >= - Mr Lister
@MrLister,即使如此,在我看来,这仍然是错误的建议。这只会悄悄地跳过少于4个值的行,而不是提供有关文件可能已损坏或其他问题的线索。我认为可以安全地假设一行中应该没有少于四个分号分隔值。 - Crono
1
这就是为什么有else部分的原因。你可以在else子句中添加错误消息。 - 9T9
1
@MoraRockey 啊,没错。我漏掉了那部分,抱歉。我会自行重新格式化您的答案。+1 - Crono
@Crono 但你仍然应该说 >=4(或者交换 ifelse 中的操作)。顺便说一下,应该是3而不是4。 - Mr Lister

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接