什么是最适合执行SQL命令的方法?

时间:2012-09-24 21:16:34

标签: c# sql-server ado.net

我正在寻找使用最少量样板代码执行数据库查询的最佳方法。 SqlCommand documentation中建议的方法:

private static void ReadOrderData(string connectionString)
{
    string queryString = "SELECT OrderID, CustomerID FROM dbo.Orders;";
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        SqlCommand command = new SqlCommand(queryString, connection);
        connection.Open();
        SqlDataReader reader = command.ExecuteReader();
        try
        {
            while (reader.Read())
            {
                Console.WriteLine(String.Format("{0}, {1}",     reader[0], reader[1]));
            }
        }
        finally
        {
            reader.Close();
        }
    }
}

主要由必须在与数据库交互的每个方法中重复的代码组成。

我已经习惯于分解连接的建立,这将产生更像下面的代码。 (我也在修改它以便它返回数据,以便使示例变得不那么简单。)

private SQLConnection CreateConnection()
{
    var connection = new SqlConnection(_connectionString);
    connection.Open();
    return connection;
}

private List<int> ReadOrderData()
{
    using(var connection = CreateConnection())
    using(var command = connection.CreateCommand())
    {
        command.CommandText = "SELECT OrderID FROM dbo.Orders;";

        using(var reader = command.ExecuteReader())
        {
            var results = new List<int>();
            while(reader.Read()) results.Add(reader.GetInt32(0));
            return results;
        }
    }
}

这是一个改进,但仍然有足够的样板来唠叨我。这可以进一步减少吗?特别是,我想对程序的前两行做些什么。我不认为该方法应该负责创建SqlCommand。这是一个很小的重复,就像在示例中一样,但如果交易是手动管理或超时被更改或类似的事情,它似乎会增长。

编辑:假设,至少假设,必须返回一堆不同类型的数据。因此,解决方案不能只是一种一刀切的方法,至少需要有几个不同的方法,取决于ExecuteNonQueryExecuteScalar,{{1 }},ExecuteReader或其他任何人都被调用。我想减少那些重复。

5 个答案:

答案 0 :(得分:5)

尝试Dapper

当然,这不会让你成为DataReader,但是一旦你尝试过,你可能会更喜欢这种方式。

这是ORM最轻的重量,同时仍被称为ORM。没有更多方法可以在DataReader和强类型之间进行映射。

在所有StackExchange网站上使用。

using (var conn = new SqlConnection(cs))
{
    var dogs = connection.Query("select name, age from dogs");

    foreach (dynamic dog in dogs)
    {
        Console.WriteLine("{0} age {1}", dog.name, dog.age);
    }
}

using (var conn = new SqlConnection(cs))
{
    var dogs = connection.Query<Dog>("select Name, Age from dogs");

    foreach (Dog dog in dogs)
    {
        Console.WriteLine("{0} age {1}", dog.Name, dog.Age);
    }
}

class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }
}

答案 1 :(得分:2)

如果您想自己滚动数据访问,这种帮助方法模式可能是消除重复的一种方法:

private List<int> ReadOrderData()
{
    return ExecuteList<int>("SELECT OrderID FROM dbo.Orders;", 
        x => x.GetInt32("orderId")).ToList();
}

private IEnumerable<T> ExecuteList(string query, 
    Func<IDataRecord, T> entityCreator)
{
    using(var connection = CreateConnection())
    using(var command = connection.CreateCommand())
    {
        command.CommandText = query;
        connection.Open();
        using(var reader = command.ExecuteReader())
        {
            while(reader.Read()) 
               yield return entityCreator(reader);
        }
    }
}

你必须添加对参数的支持,这可能无法编译,但这个模式正是我试图说明的。

答案 2 :(得分:1)

我通常使用的是我之前写过的一个自定义类,它接受一个SQL字符串,可选择一个参数列表,它返回一个DataTable。

由于调用之间的变化通常只是最佳的恕我直言的SQL。

如果您确实需要使用DataReader,可以执行以下操作:

public void ExecuteWithDataReader(string sql, Action<DataReader> stuffToDo) {
    using (SqlConnection connection = new SqlConnection(connectionString)) {
        using (SqlCommand command = new SqlCommand(sql, connection)) {
            connection.Open();

            using (SqlDataReader reader = command.ExecuteReader()) {
                try {
                    while (reader.Read()) {
                        stuffToDo(reader);
                    }
                }
                finally {
                    reader.Close();
                }
            }
        }
    }
}


private static void ReadOrderData(string connectionString) {
    string sql = "SELECT OrderID, CustomerID FROM dbo.Orders;";

    ExecuteWithDataReader(sql, r => Console.WriteLine(String.Format("{0}, {1}", r[0], r[1])));
}

答案 3 :(得分:0)

前两行是你需要的最重要的事情......

但是如果你仍然希望这样做,你可以把它们变成一个数据库处理程序类,是的,它会变成更多的代码,但在重构概念中,每一件事都会转移到相关的主题......

尝试编写一个单例类,接收命令并执行操作,因此返回SqlDataReader类型的结果...

答案 4 :(得分:0)

在评论中这样做太多了。

我建议使用样板代码

    using(conn = new sqlconnection)
    using(cmd = new sqlcommand) {
          // blah blah blah
    }

不是轻微删除的东西,而是鼓励你将它保持在原来的位置。资源,尤其是非托管资源,应尽可能在最近的地点打开和发布,以便尽快执行。

在很大程度上,由于其他开发人员很难遵循相应的清理惯例。

如果您执行类似

的操作
private SQLConnection CreateConnection()   
{   
    var connection = new SqlConnection(_connectionString);   
    connection.Open();   
    return connection;   
}   

然后,您正在邀请另一个程序员调用此方法,并在执行查询后立即完全无法释放资源。我不知道你正在构建什么样的应用程序,但是在Web应用程序中,这样的事情会导致难以调试的类型的内存/连接/资源错误,除非你之前已经通过它。

相反,我建议您查看一个轻量级的ORM,例如Dapper.net或类似的,看看他们是如何接近它的。我不使用短小精悍,但我听说它非常好。我不使用它的原因很简单,我们不允许对我们的数据库执行内联sql(但这是一个非常不同的对话)。


这是我们的标准:

public static DataTable StatisticsGet( Guid tenantId ) {
    DataTable result = new DataTable();
    result.Locale = CultureInfo.CurrentCulture;

    Database db = DatabaseFactory.CreateDatabase(DatabaseType.Clients.ToString());

    using (DbCommand dbCommand = db.GetStoredProcCommand("reg.StatsGet")) {
        db.AddInParameter(dbCommand, "TenantId", DbType.Guid, tenantId);

        result.Load(db.ExecuteReader(dbCommand));
    } // using dbCommand

    return result;
} // method::StatisticsGet

我们大量使用企业库。它简短,简单,经得起考验。这个方法只返回一个数据表但你很容易让它返回一个对象集合..或者没有。