【问题标题】:Can I consolidate this in a generic method?我可以将其整合到通用方法中吗?
【发布时间】:2018-03-17 15:30:26
【问题描述】:

我有一个具有以下 2 种数据访问方法的 DataAccessBase 类。一个用于 ExecuteScalar,一个用于 ExecuteNonQuery。是否有可能将其整合到一个通用方法中,甚至值得担心?

    protected static int ExecuteNonQuery(SqlCommand command)
    {
        using (SqlConnection connection = new SqlConnection(_connStr))
        {
            command.Connection = connection;
            SqlDataAdapter da = new SqlDataAdapter(command);
            command.Connection.Open();

            int result = command.ExecuteNonQuery();

            return result;
        }
    }

    protected static string ExecuteScalar(SqlCommand command)
    {
        using (SqlConnection connection = new SqlConnection(_connStr))
        {
            command.Connection = connection;
            SqlDataAdapter da = new SqlDataAdapter(command);
            command.Connection.Open();

            string result = command.ExecuteScalar().ToString();

            return result;
        }
    }

    private static DataTable GetDT(int id)
    {
        using (SqlConnection connection = new SqlConnection(_connStr))
        {
            string query = "select id, userid, name from tasks where id = @id";
            SqlCommand command = new SqlCommand(query, connection);
            SqlDataAdapter da = new SqlDataAdapter(command);
            //Parameterized query to prevent injection attacks
            command.Parameters.AddWithValue("id", id);
            DataTable dt = new DataTable();
            da.Fill(dt);

            return dt;
        }
    }

【问题讨论】:

  • 他们不做同样的事情,SqlDataAdapter的目的是什么?
  • 好点子和眼睛!
  • 问问自己在需要时如何将参数和数据类型传递给那些人
  • 我会把 ExecuteScalar 改成通用的,你怎么知道它总是返回一个字符串?
  • 这些方法在 SqlCommand 上调用两个不同的方法。不确定你会用它们制作泛型。

标签: c#


【解决方案1】:

使用泛型方法绝对可以避免当前的重复,但我不会尝试将其简化为单一方法。这是我可能做的事情:

protected static int ExecuteNonQuery(SqlCommand command) =>
    ExecuteCommand(command, cmd => cmd.ExecuteNonQuery());

protected static string ExecuteScalar(SqlCommand command) =>
    ExecuteCommand(command, cmd => cmd.ExecuteScalar().ToString());

private static T ExecuteCommand<T>(SqlCommand command, Func<SqlCommand, T> resultRetriever)
{
    using (SqlConnection connection = new SqlConnection(_connStr))
    {
        command.Connection = connection;
        command.Connection.Open();
        return resultRetriver(command);
    }
}

对于DataTable,遵循相同的模式,您首先要创建命令:

protected static DataTable GetDataTable(SqlCommand command) =>
    ExecuteCommand(cmd =>
    {
        SqlDataAdapter da = new SqlDataAdapter(cmd)
        DataTable table = new DataTable();
        da.FillTable(table);
        return table;
    });

【讨论】:

  • 所以我在另一个方法中使用了 SqlDataAdapter,该方法执行 SqlDataAdapter da = new SqlDataAdapter(); da.Fill(dataTable);
  • @Rod:所以这是在做一些不同的事情,如果没有看到更多细节,我们无法判断它是否可以使用这种方法。
  • @JonSkeet 是否值得添加一个Action&lt;SqlCommand&gt; commandInitializer 参数以便我们可以创建命令并在这些方法中处理它?
  • @vc74: 与目前为止提供给我们的代码不同,它不能使用它 - 我们不知道 OP 是否想要 处理传递的-in 命令。如果没有额外的信息,我不愿意过度概括。
  • @JonSkeet 没错,我觉得先创建命令然后创建连接看起来很奇怪,但它可能是我过时了;)谢谢
【解决方案2】:

您可以将 ExecuteScalar 转换为泛型方法,从而允许您更改返回类型。

public T ExecuteScalar<T>(SqlCommand command)
{
    using (SqlConnection connection = new SqlConnection(_connStr))
    {
        command.Connection = connection;
        //SqlDataAdapter da = new SqlDataAdapter(command); //not needed...
        command.Connection.Open();
        var result = command.ExecuteScalar();

        //rather than just returning result with an implicit cast, use Max's trick from here: https://stackoverflow.com/a/2976427/361842
        if (Convert.IsDbNull(result))
            return default(T); //handle the scenario where the returned value is null, but the type is not nullable (or remove this to have such scenarios throw an exception)
        if (result is T)
            return (T)result;
        else
            (T)Convert.ChangeType(result, typeof(T));
    }
}

不过,此方法与ExecuteNonQuery 函数中的逻辑不同,因此不能用相同的方法表示两者。


更新

关于您关于数据表的问题,我采用并改编了@JonSkeet's answer 以允许该类也处理数据表:

public class SqlDatabaseThing //: ISqlDatabaseThing
{

    // ... additional code here ... //

    public int ExecuteNonQuery(SqlCommand command, IEnumerable<SqlParameter> sqlParameters = new[]{}) =>
        ExecuteNonQuery(_connStr, command, sqlParameters);
    public static int ExecuteNonQuery(string connectionString, SqlCommand command, IEnumerable<SqlParameter> sqlParameters = new[]{}) =>
        ExecuteCommand(connectionString, command, cmd => cmd.ExecuteNonQuery());

    public T ExecuteScalar(SqlCommand command, IEnumerable<SqlParameter> sqlParameters = new[]{}) =>
        ExecuteScalar(_connStr, command, sqlParameters);
    public static T ExecuteScalar(string connectionString, SqlCommand command, IEnumerable<SqlParameter> sqlParameters = new[]{}) =>
        ExecuteCommand(connectionString, command, cmd => ConvertSqlCommandResult(cmd.ExecuteScalar()));

    public DataTable ExecuteToDataTable(SqlCommand command, IEnumerable<SqlParameter> sqlParameters = new[]{}) =>
        ExecuteToDataTable(_connStr, command, sqlParameters);
    public static DataTable ExecuteToDataTable(string connectionString, SqlCommand command, IEnumerable<SqlParameter> sqlParameters = new[]{}) =>
        ExecuteCommand(connectionString, command, cmd => PopulateDataTable(cmd));


    private static T ExecuteCommand<T>(string connectionString, SqlCommand command, IEnumerable<SqlParameter> sqlParameters, Func<SqlCommand, T> resultRetriever)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            command.Parameters.AddRange(sqlParameters);
            command.Connection = connection;
            command.Connection.Open();
            return resultRetriver(command);
        }
    }

    private static DataTable PopulateDataTable(SqlCommand command)
    {
        var da = SqlDataAdapter(command);
        var dt = new DataTable();
        da.Fill(dt);
        return dt;
    }

    private static T ConvertSqlCommandResult(object result)
    {
        if (Convert.IsDbNull(result))
            return default(T); 
        if (result is T)
            return result as T;
        (T)Convert.ChangeType(result, typeof(T));
    }

}   

注意:在您的代码中,您包含了与获取特定任务相关的逻辑。这应该与您的通用数据库逻辑分开(即假设您希望为各种查询返回数据表,并且不想每次都重写您的GetDT 代码)。因此,我在下面提供了额外的示例代码,展示了如何将该逻辑分离到另一个类中......

public class TaskRepository //: IRepository<Task>
{
    ISqlDatabaseThing db;
    public TaskRepository(ISqlDatabaseThing db)
    {
        this.db = db;
    }

    readonly string GetByIdCommand = "select id, userid, name from tasks where id = @id";
    readonly string GetByIdCommandParameterId = "@id"
    readonly SqlDbType GetByIdCommandParameterIdType = SqlDbType.BigInt;
    public Task GetById(long id)
    {
        var command = new SqlCommand(GetByIdCommand);
        var parameters = IEnumerableHelper.ToEnumerable<SqlParameter>(new SqlParameter(GetByIdCommandIdParameter, GetByIdCommandIdParameterType, id));
        var dataTable = db.ExecuteToDataTable(command, parameters);
        return DataTableToTask(dataTable)[0];
    }
    private IEnumerable<Task> DataTableToTask(DataTable dt)
    {
        foreach (var row in dt.Rows)
        {
            yield return DataRowToTask(row);
        }
    }
    private Task DataRowToTask (DataRow dr)
    {
        return new Task()
        {
            Id = dr["Id"]
            ,Name = dr["Name"]
            ,UserId = dr["UserId"]
        };
    }

}

public static class IEnumerableHelper
{
    public static IEnumerable<T> ToEnumerable<T>(params T[] parameters)
    {
        return parameters;
    } 
}

注意:此代码未经测试;有任何问题请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 2012-08-22
    • 1970-01-01
    • 2014-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多