在 C# 中以相同的方法访问不同的 SQL 服务器类型,例如 TSql 或 MySql

Access different SQL Server Types such as TSql or MySql in the same method in C#

目前我正在编写一个程序来访问 'types' 个 SQL 服务器,例如 C# 中的 TSQL 或 MySQL。我创建了一个具有抽象方法的基础 class DBConnector

public abstract class DBConnector
{
    //some other code...

    protected abstract int ExecuteNonQueryOrScalar(DbConnection con, string statement, bool executeAsScalar, int timeout = 20);

    //some other code...
}

现在我有两个派生的 classes TSqlConnectorMySqlConnector 实现了这个抽象方法:

对于MySql它看起来像这样:

protected override int ExecuteNonQueryOrScalar(DbConnection con, string statement, bool executeAsScalar, int timeout = 20)
{
    int result = -1;
    using (MySqlCommand cmd = new MySqlCommand(statement, (MySqlConnection)con))
    {
        cmd.CommandTimeout = timeout;
        cmd.Connection.Open();

        if (executeAsScalar)
        {
            object resultObject = cmd.ExecuteScalar();
            if (resultObject is int tmpResult)
                result = tmpResult;
        }
        else
        {
            result = cmd.ExecuteNonQuery();
        }
    }
    return result;
}

对于 TSql,它看起来像这样:

protected override int ExecuteNonQueryOrScalar(DbConnection con, string statement, bool executeAsScalar, int timeout = 20)
{
    int result = -1;
    using (SqlCommand cmd = new SqlCommand(statement, (SqlConnection)con))
    {
        cmd.CommandTimeout = timeout;
        cmd.Connection.Open();

        if (executeAsScalar)
        {
            object resultObject = cmd.ExecuteScalar();
            if (resultObject is int tmpResult)
                result = tmpResult;
        }
        else
        {
            result = cmd.ExecuteNonQuery();
        }
    }
    return result;
}

顺便说一句,这些方法还包含一些错误处理和其他一些东西,但我为此简化了我的方法post。

此方法在我的其他自定义方法中被调用,例如:

我调用 Insert-Method 中的代码(obj 是模型 class 的一个实例,它具有与数据库 table 相同的属性,包括 ID):

obj.ID = ExecuteNonQueryOrScalar(GetConnection(), sql, true); //true for scalar-execution

我调用更新方法中的代码:

affectedLines = ExecuteNonQueryOrScalar(GetConnection(), sql, false); //false for nonQuery-execution

我调用 Delete-Method 中的代码:

affectedLines = ExecuteNonQueryOrScalar(GetConnection(), sql, false); //false for nonQuery-execution

GetConnection() returns 一个 DbConnection-对象,它在运行时是 SqlConnectionMySqlConnectionsql 是我的 sql-字符串。布尔参数决定是否在 ExecuteNonQueryOrScalar() 中调用 ExecuteNonQueryExecuteScalar,正如您在我上面的代码中看到的那样。

现在回答我的问题:

  1. 可以看到,两种实现的代码几乎是一样的。唯一的区别是连接和命令的类型。我听说你应该遵循“不要重复自己”的模式。但我在这里重复一遍。你们知道我能在这里做什么吗?还是我应该坚持我目前拥有的东西?我有想法将这两种方法移动到我的基础 class DBConnector 中的一个方法并使用通用参数,我用 where T: IDBEntitywhere K: DBConnection 限制,但是当我这样做时会出现编译时错误。我真的找不到防止这种情况的解决方案。

  2. 您对如何以不同的方式实现它有什么建议吗?你会改变什么?

如果我需要分享更多我的代码,请告诉我。

感谢您花时间阅读我的问题。

编辑:我的问题的解决方案:
阅读回复后,我意识到如何改进我的代码。我将我的方法 ExecuteNonQueryOrScalar 移到了我的基础 class DBConnector 中并添加了一个 IDbCommand-Parameter,它可以是运行时的任何 IDbCommand-Object。此方法如下所示(简化版):

protected int ExecuteNonQueryOrScalar(IDbCommand cmd, bool executeAsScalar, int timeout = 20)
{
    int result = -1;
    try
    {
        cmd.CommandTimeout = timeout;

        if (cmd.Connection.State != ConnectionState.Open)
            cmd.Connection.Open();

        if (executeAsScalar)
        {
            string resultObject = cmd.ExecuteScalar().ToString();

            if (Int32.TryParse(resultObject, out int tmpResult)) //if (resultObject is int tmpResult)
                result = tmpResult;
        }
        else
        {
            result = cmd.ExecuteNonQuery();
        }
    }
    //Some error handling...
    finally
    {
        if (cmd.Connection.State != ConnectionState.Closed)
            cmd.Connection.Close();
    }

    return result;
}

这是一个例子,我如何在我的 Update-方法(简化)中调用 ExecuteNonQueryOrScalar-方法,在相同的 DBConnector-Class:

protected int Update<T, K>(T obj, List<DBCondition> filterValues = null) where T : IDBEntity, new() where K : IDbCommand, new()
{
    int affectedLines = -1;
    if (obj != null)
    {
        using (IDbCommand cmd = new K())
        {
            cmd.Connection = GetConnection();
            cmd.CommandText = obj.GetSqlUpdateStatement(DBMapping.GetDBMapping<T>(), _sqlSpecificSymbols, filterValues);
            affectedLines = ExecuteNonQueryOrScalar(cmd, false);
        }
    }
    return affectedLines;
}

最后这里有两个例子,我如何在 MySqlConnectorTSqlConnector 中调用这个更新方法:

MySql连接器:

public override int Update<T>(T obj, List<DBCondition> filterValues = null)
{
    return base.Update<T, MySqlCommand>(obj, filterValues); //Call DbConnector.Update<T, K>() from the example above
}

TSqlConnector:

public override int Update<T>(T obj, List<DBCondition> filterValues = null)
{
    return base.Update<T, SqlCommand>(obj, filterValues); //Call DbConnector.Update<T, K>() from the example above
}

如果您想查看我的更多代码,请随时问我!我还可以将我未简化的原始代码上传到 github 等网站,如果你们中的一些人想要完成我所做的一切。

你的想法很好。但是 .net 团队中有人通过创建 interfaces that work this way, like IDBConnection and IDBCommand.

为您节省了一些时间

然后这个接口有不同的具体实现,你可以在需要的时候使用。例如 IDBConnection con = new MySqlConnection 代表 MySQL,或 IDBConnection con = new SqlConnection 代表 SQL 服务器。

这些接口公开了常用方法,例如 ExecuteNonQuery。因此,您在代码中使用的只是一堆接口。您的代码总是绕过 IDBConnections 和 IDBCommands,您只需在构造时选择您需要的实现即可。这是dependency inversion.

当然,在填充命令的实际文本时,您必须使用正确的 SQL 方言。

这是否符合您的要求?