Sin*_*tic 6 c# methods return object
我不确定是否已被删除,但我想要做的是创建一个返回查询结果的方法,以便我可以重用连接代码.据我了解,查询返回一个对象,但我如何传回该对象?我想将查询作为字符串参数发送到方法中,并让它返回结果,以便我可以使用它们.这就是我在黑暗中刺伤的东西,它显然不起作用.这个例子是我尝试使用查询结果填充列表框; 工作表名称为Employees,字段/列是名称.我得到的错误是"Complex DataBinding接受IList或IListSource作为数据源.".有任何想法吗?
public Form1()
{
InitializeComponent();
openFileDialog1.ShowDialog();
openedFile = openFileDialog1.FileName;
lbxEmployeeNames.DataSource = Query("Select [name] FROM [Employees$]");
}
public object Query(string sql)
{
System.Data.OleDb.OleDbConnection MyConnection;
System.Data.OleDb.OleDbCommand myCommand = new System.Data.OleDb.OleDbCommand();
string connectionPath;
//build connection string
connectionPath = "provider=Microsoft.Jet.OLEDB.4.0;Data Source='" + openedFile + "';Extended Properties=Excel 8.0;";
MyConnection = new System.Data.OleDb.OleDbConnection(connectionPath);
MyConnection.Open();
myCommand.Connection = MyConnection;
myCommand.CommandText = sql;
return myCommand.ExecuteNonQuery();
}
Run Code Online (Sandbox Code Playgroud)
Joe*_*orn 24
在学习与数据库交谈时,每个程序员必须做两件基本事情:关闭连接并参数化查询.这些项与运行sql语句和接收结果的实际过程是分开的,但它们仍然是绝对必要的.出于某种原因,互联网上提供的大多数教程只是掩盖了它们,甚至使它们完全错误,也许是因为对于任何足以编写教程的人来说,这是第二天性.我的目标是向您展示如何构建整个过程,包括这些额外的基础知识,以便更容易实现这一目标,并且每次都能正确完成.
首先要做的是意识到在一个方法中隐藏数据访问代码是不够的:我们实际上想要为此构建一个单独的类(甚至是类库).通过创建一个单独的类,我们可以在该类中使我们的实际连接方法保持私有,这样只有类中的其他方法才能连接到数据库.这样,我们设置了一个网守,强制程序中的所有数据库代码都通过批准的通道运行.关于我上面谈到的两个问题,获取关守代码是正确的,并且整个程序也将始终如一地正确.所以这是我们的开始:
public class DataLayer
{
private DbConnection GetConnection()
{
//This could also be a connection for OleDb, ODBC, Oracle, MySQL,
// or whatever kind of database you have.
//We could also use this place (or the constructor) to load the
// connection string from an external source, like a
// (possibly-encrypted) config file
return new SqlConnection("connection string here");
}
}
Run Code Online (Sandbox Code Playgroud)
到目前为止,我们还没有真正解决引言中的基本问题.到目前为止,我们所做的就是编写代码,以便我们以后执行良好实践.所以让我们开始吧.首先,我们将担心如何强制关闭您的连接.我们这样做是通过添加一个运行查询的方法,返回结果,并确保在完成后关闭连接:
private DataTable Query(string sql)
{
var result = new DataTable();
using (var connection = GetConnection())
using (var command = new SqlCommand(sql, connection)
{
connection.Open();
result.Load(command.ExecuteReader(CommandBehavior.CloseConnection));
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
您可以添加其他类似的方法来返回标量数据或根本不返回数据(用于更新/插入/删除).暂时不要太依赖这段代码,因为它仍然没有被破坏.我会在一分钟内解释原因.现在,让我指出这种方法仍然是私有的.我们尚未完成,因此我们不希望此代码可用于程序的其他部分.
我要强调的另一件事是using关键字.此关键字是在.Net和C#中声明变量的强大方法.的using关键字创建变量声明下方的范围块.在范围块的末尾,处理您的变量.请注意,这有三个重要部分.首先,这实际上只适用于非托管资源,如数据库连接; 记忆仍以通常的方式收集.第二个是即使抛出异常也会处理变量.这使得关键字适合与时间敏感或紧密约束的资源(如数据库连接)一起使用,而无需在附近使用单独的try/catch块.最后一部分是关键字利用.Net中的IDisposable模式.您现在不需要了解IDisposable的所有内容:只知道数据库连接实现(想想:继承)IDisposable接口,因此将使用using块.
您不必using在代码中使用关键字.但是如果你不这样做,处理连接的正确方法如下:
SqlConnection connection;
try
{
connection = new SqlConnection("connection string here");
SqlCommand command = new SqlCommand("sql query here", connetion);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
//do something with the data reader here
}
finally
{
connection.Close();
}
Run Code Online (Sandbox Code Playgroud)
即使这仍然是简单的版本.您还需要在finally块中进行额外检查,以确保您的连接变量有效.该using关键字是一个更简洁的方式来表达这一点,它可以确保得到每一次正确的模式.我想在这里展示的是,如果你只是打电话connection.Close(),没有保护,以确保程序实际到达那一行,你就失败了.如果您的sql代码抛出异常而没有try/finally或using的保护,您将永远不会到达.Close()调用,从而可能使连接保持打开状态.经常这样做,你可以锁定自己的数据库!
现在让我们构建一些公共的东西:你可以从其他代码中实际使用它.正如我之前提到的,您为应用程序编写的每个SQL查询都将使用它自己的方法.这是一个简单查询的示例方法,用于从Employee表中获取所有记录:
public DataTable GetEmployeeData()
{
return Query("SELECT * FROM Employees");
}
Run Code Online (Sandbox Code Playgroud)
哇,这很简单......单行函数调用,我们从数据库中获取数据.我们真的到了某个地方.不幸的是,我们仍然缺少一个难题:你知道,很难想要归还整个表格.通常,您需要以某种方式过滤该表,并可能将其与另一个表连接.让我们更改此查询以返回名为"Fred"的虚构员工的所有数据:
public DataTable GetFredsEmployeeData()
{
return Query("SELECT * FROM Employees WHERE Firstname='Fred'");
}
Run Code Online (Sandbox Code Playgroud)
仍然很容易,但是错过了我们想要完成的精神.您不希望为每个可能的员工姓名构建另一种方法.你想要更像这样的东西:
public DataTable GetEmployeeData(string FirstName)
{
return Query("SELECT * FROM Employees WHERE FirstName='" + FirstName + "'");
}
Run Code Online (Sandbox Code Playgroud)
哦,哦.现在我们遇到了问题.有一个讨厌的字符串连接,只是等待有人来,并将文本';Drop table employees;--(或更糟)输入您的应用程序中的FirstName字段.处理这个的正确方法是使用查询参数,但这是它变得棘手的地方,因为我们构建了一个只接受完成的sql字符串的查询方法.
很多人都想像 Query方法一样编写一个方法.我认为几乎每个数据库程序员都会在他们职业生涯的某个阶段受到这种模式的诱惑,不幸的是,在你添加一种接受sql参数数据的方法之前,这是完全错误的.幸运的是,有许多不同的方法来解决这个问题.最常见的是向方法添加一个参数,允许我们传入要使用的sql数据.为此,我们可以传递一个SqlParameter对象数组,一组键/值对,甚至只是一个对象数组.任何这些都足够了,但我认为我们可以做得更好.
我花了很多时间研究不同的选项,并且我已经缩小了我认为最简单,最有效,(更重要的)最准确和可维护的C#选项.不幸的是,它确实需要你理解C#中一个更高级的语言特性的语法:anonymous methods/lambdas(真的:委托,但我很快就会展示一个lambda).这个功能允许你做的是在另一个函数中定义一个函数,用变量保持它,将它传递给其他函数,并在闲暇时调用它.这是一个有用的功能,我将尝试演示.以下是我们将如何修改原始Query()函数以利用此功能:
private DataTable Query(string sql, Action<SqlParameterCollection> addParameters)
{
var result = new DataTable();
using (var connection = GetConnection())
using (var command = new SqlCommand(sql, connection)
{
//addParameters is a function we can call that was as an argument
addParameters(command.Parameters);
connection.Open();
result.Load(command.ExecuteReader(CommandBehavior.CloseConnection));
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
请注意新Action<SqlParameterCollection>参数.不要介意这个< >部分.如果您不熟悉泛型,那么您现在可以假装它是类名的一部分.重要的是,这个特殊的Action类型允许我们将一个函数(在这种情况下,一个以SqlParameterCollection作为参数的函数)传递给另一个函数.以下是从GetEmployeeData()函数中使用时的外观:
public DataTable GetEmployeeData(string firstName)
{
return Query("SELECT * FROM Employees WHERE FirstName= @Firstname",
p =>
{
p.Add("@FirstName", SqlDbType.VarChar, 50).Value = firstName;
});
}
Run Code Online (Sandbox Code Playgroud)
所有这一切的关键是Query()函数现在有一种方法将firstName传递给它的父GetEmployeeData()函数的参数连接到sql字符串中的@FirstName表达式.这是使用ADO.Net和sql数据库引擎内置的功能完成的.最重要的是,它以防止sql注入攻击的任何可能性的方式发生.同样,这种奇怪的语法并不是发送参数数据的唯一有效方法.发送一个迭代的集合可能会更舒服.但我确实认为这段代码可以很好地将参数代码保存在查询代码附近,同时避免额外的工作构建,然后再迭代(重建)参数数据.
我将完成(最后!)两个短项目.第一个是调用没有参数的新查询方法的语法:
public DataTable GetAllEmployees()
{
return Query("SELECT * FROM Employees", p => {});
}
Run Code Online (Sandbox Code Playgroud)
虽然我们也可以将它作为原始Query()函数的重载提供,但在我自己的代码中我不喜欢这样做,因为我想与其他开发人员沟通他们应该寻求参数化他们的代码,而不是偷偷摸摸用字符串连接.
其次,这个答案中概述的代码仍未完成.还有一些重要的弱点尚待解决.例如,使用数据表而不是数据加载器会强制您将每个查询的整个结果集一次性加载到内存中.我们可以采取一些措施来避免这种情况.我们还没有讨论插入,更新,删除或更改,我们还没有解决如何组合复杂参数情况,我们可能希望添加代码以过滤姓氏,但仅限于数据对于姓氏过滤器实际上是从用户可用的.虽然这可以很容易地适应所有这些场景,但我认为在这一点上我已经完成了原始目标,因此我将把它留给读者.
总之,请记住您必须做的两件事:通过finally块关闭您的连接,并参数化您的查询.希望这篇文章能帮助你做好准备.
嘿!试试这个,如果您只想将所有员工的姓名显示到列表框中,这应该可行。我刚刚编辑了你的代码中的一些行...
Form1()
{
InitializeComponent();
openFileDialog1.ShowDialog();
openedFile = openFileDialog1.FileName;
lbxEmployeeNames.DataSource = Query("Select [name] FROM [Employees$]");
lbxEmployeeNames.DisplayMember = "name"; // The column you want to be displayed in your listBox.
}
// Return a DataTable instead of String.
public DataTable Query(string sql)
{
System.Data.OleDb.OleDbConnection MyConnection;
string connectionPath;
//build connection string
connectionPath = "provider=Microsoft.Jet.OLEDB.4.0;Data Source='" + openedFile + "';Extended Properties=Excel 8.0;";
MyConnection = new System.Data.OleDb.OleDbConnection(connectionPath);
MyConnection.Open();
System.Data.OleDb.OleDbDataAdapter myDataAdapter = new System.Data.OleDb.OleDbDataAdapter(sql, MyConnection);
DataTable dt = new DataTable();
myDataAdapter.Fill(dt);
return dt;
}
Run Code Online (Sandbox Code Playgroud)