2

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

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. 就像在示例中一样,这是一个很小的重复,但是如果手动管理事务或更改超时或类似的事情,它似乎会增长。

编辑:假设,至少假设,必须返回一堆不同类型的数据。因此,解决方案不能只是一种千篇一律的方法,必须有几种不同的方法,至少取决于是否调用ExecuteNonQuery, ExecuteScalar, ExecuteReader,ExecuteReaderAsync或其他任何方法。我想减少其中的重复。

4

5 回答 5

5

试过小巧玲珑

当然,这不会让您获得 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; }
}
于 2012-09-24T21:47:42.553 回答
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);
        }
    }
}

您必须添加对参数的支持,这可能无法编译,但模式正是我想要说明的。

于 2012-09-24T21:33:26.687 回答
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])));
}
于 2012-09-24T21:25:06.210 回答
0

前两行是你需要的最重要的东西......

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

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

于 2012-09-24T21:35:41.493 回答
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或类似的,看看他们是如何处理它的。我不使用 dapper,但我听说它非常好。我不使用它的原因仅仅是我们不允许对我们的数据库执行内联 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

我们大量使用企业库。它简短、简单、中肯,并且经过了很好的测试。这个方法只返回一个数据表,但你可以很容易地让它返回一个对象集合......或者什么都不返回。

于 2012-09-24T22:11:27.787 回答