我最近开始重构由OOP经验不足的人设计的旧系统。幸运的是,(几乎)对数据库的所有访问都在一个3000行长的文件内。该文件包含一个Dictionary<string, SqlCommand>
,SqlConnection
,这是一个很长的函数,它将每个SQL查询添加到字典中,如下所示:
cmd = new SqlCommand(null, _sqlConnection);
cmd.CommanText = "SELECT * FROM User WHERE User.UserID = @id;" // Most queries are far from being this simple
cmd.Parameters.Add(new SqlParameter("@id", SqlDbType.Int, 0));
cmd.Prepare();
_cmds.Add("getUser", cmd);
这些查询由同一文件中的函数使用,如下所示:
public void deleteUser(int userId)
{
if (_cmds.TryGetValue("deleteUser", out SqlCommand cmd))
{
lock(cmd)
{
cmd.Parameters[0].Value = userId;
cmd.ExecuteNonQuery();
}
}
}
public int isConnected(int userId, out int amount)
{
bool result = false;
amount = 0;
if (_cmds.TryGetValue("userInfo", out SqlCommand cmd))
{
lock (cmd)
{
cmd.Parameters[0].Value = userId;
using (SqlDataReader reader = new cmd.ExecuteReader())
{
if (reader.HasRows)
while (reader.Read())
{
amount = (int)Math.Round(reader.GetDecimal(0));
result = reader.GetInt32(1);
}
}
}
}
return result;
}
现在,使用和维护它太可怕了。我终于有时间重构它。我想将其转换为带有存储库的适当DAL,该存储库将由服务使用并且可以注入依赖项。
我不太在乎更改功能或查询(例如使用ORM)。我更感兴趣的是将文件拆分为许多文件,使我可以更轻松地模拟,测试和修改它。我正在寻找一种更好地构造现有代码的方法,尽管我知道将需要大量复制/粘贴和重新编码。
答案 0 :(得分:1)
建议使用诸如 NHibernate 之类的对象关系映射器替换手动编写的对象映射代码,这将节省创建和维护数据访问层的时间和精力。
答案 1 :(得分:1)
签出Dapper。它是一个“微型ORM”,可提供高性能的面向对象的数据访问。您可以继续使用所有现有查询,但是用Dapper替换所有样板ADO.NET代码。
答案 2 :(得分:1)
这将需要一些重复的工作,但是这里有一些如何处理它的想法。这不会使代码处于理想状态,但是可能会使代码更易于管理。一个挑战是,每种方法在两个地方都有部分-一个在方法中,一个在命令存储在字典中。
那是容易的部分。如何重构整个类而不破坏它的任何一部分?这些步骤只是一些想法:
第一步只是注入类所需的连接字符串:
public class YourDataAccessClass
{
private readonly string _connectionString;
public YourDataAccessClass(string connectionString)
{
_connectionString = connectionString;
}
}
您一次只能使用一种方法。最初,您可以按原样离开大部分班级,包括字典。这样,您尚未修改的方法将继续起作用。
接下来,您可以在两个单独的窗口中打开该类,以便可以看到包含SQL的字典函数和并排使用它的函数。如果必须向上和向下滚动,这将变得更加困难。
您可能希望将每个函数的SQL移入该函数。您可以在重构每个函数时执行此操作,但是一次完成所有操作可能会比较省心,从而可以从重复中获得效率。
您可以在每个函数中定义一个新变量,然后复制并粘贴:
var sql = "SELECT * FROM User WHERE User.UserID = @id;";
(再次,不是我通常写的方式。)
现在您拥有一个或多个看起来像这样的函数:
public void deleteUser(int userId)
{
var sql = "DELETE User WHERE User.UserID = @id;";
if (_cmds.TryGetValue("deleteUser", out SqlCommand cmd))
{
lock(cmd)
{
cmd.Parameters[0].Value = userId;
cmd.ExecuteNonQuery();
}
}
}
对于非查询命令,您可以在您的类中编写这样的函数,该函数将消除用于打开连接,创建命令等的重复代码:
private void ExecuteNonQuery(string sql, Action<SqlCommand> addParameters = null)
{
using (var connection = new SqlConnection(_connectionString))
using (var command = new SqlCommand(sql))
{
addParameters?.Invoke(command);
connection.Open();
command.ExecuteNonQuery();
}
}
保存以下代码段。您甚至可能大部分时间都只能将其保存在剪贴板中。将其粘贴到SQL下方的每个非查询方法中:
ExecuteNonQuery(sql, command =>
{
});
粘贴后,将添加参数的一行移动到cmd
参数的主体(命名为cmd
,以便可以在不更改变量名称的情况下移动这些行),然后然后删除以前执行查询的现有代码。
ExecuteNonQuery(sql, cmd =>
{
cmd.Parameters[0].Value = userId;
});
现在您的函数如下:
public void deleteUser(int userId)
{
var sql = "DELETE User WHERE User.UserID = @id;";
ExecuteNonQuery(sql, cmd =>
{
cmd.Parameters[0].Value = userId;
});
}
我并不是说这很有趣,但是它将使编辑这些功能的过程更加高效,因为您键入的内容更少,并且只是一遍又一遍地反复移动内容。
实际返回数据的方法虽然不那么有趣,但是仍然易于管理。
首先,采用几乎相同的样板代码。可能会有所改善,因为它仍然有些重复,但至少它是独立的:
using (var connection = new SqlConnection(_connectionString))
using (var cmd = new SqlCommand(sql)) // again, named "cmd" on purpose
{
connection.Open();
}
从此开始:
public int isConnected(int userId, out int name)
{
var sql = "SELECT * FROM User WHERE User.UserID = @id;";'
bool result = false;
amount = 0;
if (_cmds.TryGetValue("userInfo", out SqlCommand cmd))
{
lock (cmd)
{
cmd.Parameters[0].Value = userId;
using (SqlDataReader reader = new cmd.ExecuteReader())
{
if (reader.HasRows)
while (reader.Read())
{
amount = (int)Math.Round(reader.GetDecimal(0));
result = reader.GetInt32(1);
}
}
}
}
}
将样板粘贴到方法中
public int isConnected(int userId, out int name)
{
var sql = "SELECT * FROM User WHERE User.UserID = @id;";'
bool result = false;
amount = 0;
using (var connection = new SqlConnection(_connectionString))
using (var cmd = new SqlCommand(sql)) // again, named "cmd" on purpose
{
connection.Open();
}
if (_cmds.TryGetValue("userInfo", out SqlCommand cmd))
{
lock (cmd)
{
cmd.Parameters[0].Value = userId;
using (SqlDataReader reader = new cmd.ExecuteReader())
{
if (reader.HasRows)
while (reader.Read())
{
amount = (int)Math.Round(reader.GetDecimal(0));
result = reader.GetInt32(1);
// was this a typo? The code in the question doesn't
// return anything or set the "out" variable. But
// if that's in the method then that will be part of
// what gets copied.
}
}
}
}
}
然后,像以前一样,将要在其中添加参数的部分移到connection.Open();
上方,并将要使用命令的部分移到connection.Open();
下方,然后删除剩下的部分。结果是这样的:
public int isConnected(int userId, out int name)
{
var sql = "SELECT * FROM User WHERE User.UserID = @id;";'
bool result = false;
amount = 0;
using (var connection = new SqlConnection(_connectionString))
using (var cmd = new SqlCommand(sql)) // again, named "cmd" on purpose
{
cmd.Parameters[0].Value = userId;
connection.Open();
using (SqlDataReader reader = new cmd.ExecuteReader())
{
if (reader.HasRows)
while (reader.Read())
{
amount = (int)Math.Round(reader.GetDecimal(0));
result = reader.GetInt32(1);
}
}
}
}
您可能会陷入困境,每隔一两分钟就能做一次,这意味着只需要几个小时。
完成所有这些操作后,您可以删除大量的词典功能。现在,该类依赖于注入的连接字符串,并且可以正常打开和关闭连接,而不是反复存储和使用连接。
您也可以将其分解。一种方法是将连接字符串和helper函数移到基类中(或仅复制helper函数-它确实很小),并且您可以将任何查询函数移到较小的类中,因为每个函数都是独立的。 / p>