从零打造 C# 工业视觉检测系统(九):工厂模式实现 SQL Server / MySQL / SQLite 三库切换

从零打造 C# 工业视觉检测系统(九):工厂模式实现 SQL Server / MySQL / SQLite 三库切换

系列标签C# 工厂模式 ADO.NET SQL Server MySQL SQLite

本文目标:掌握可扩展的数据访问层设计,实现多种数据库的无缝切换


一、前言

工业软件的数据库选型受客户现场环境制约:

  • 大型工厂已有 SQL Server 集群,要求数据统一入库
  • 中小客户希望零成本部署,倾向 MySQL
  • 单机设备或嵌入式场景,SQLite 是最轻量的选择

如果每个 SQL 语句都硬编码 SqlConnection,切换数据库将是一场噩梦。本文将详解本项目中的 工厂模式 + ADO.NET 架构。


二、IDatabaseProvider 接口设计

抽象出数据库操作的三个最基本动作:

csharp 复制代码
public interface IDatabaseProvider
{
    IDbConnection CreateConnection(string connectionString);
    IDbCommand CreateCommand(string query, IDbConnection connection);
    IDbDataAdapter CreateAdapter(IDbCommand command);
}

使用 IDbConnectionIDbCommandADO.NET 基接口,而非具体类型(如 SqlConnection),这是实现跨数据库的关键。


三、三种数据库的实现

3.1 SQL Server

csharp 复制代码
public class SqlServerProvider : IDatabaseProvider
{
    public IDbConnection CreateConnection(string connectionString)
    {
        return new SqlConnection(connectionString);
    }

    public IDbCommand CreateCommand(string query, IDbConnection connection)
    {
        return new SqlCommand(query, (SqlConnection)connection);
    }

    public IDbDataAdapter CreateAdapter(IDbCommand command)
    {
        return new SqlDataAdapter((SqlCommand)command);
    }
}

3.2 MySQL

csharp 复制代码
public class MySqlProvider : IDatabaseProvider
{
    public IDbConnection CreateConnection(string connectionString)
    {
        return new MySqlConnection(connectionString);
    }

    public IDbCommand CreateCommand(string query, IDbConnection connection)
    {
        return new MySqlCommand(query, (MySqlConnection)connection);
    }

    public IDbDataAdapter CreateAdapter(IDbCommand command)
    {
        return new MySqlDataAdapter((MySqlCommand)command);
    }
}

3.3 SQLite

csharp 复制代码
public class SqliteProvider : IDatabaseProvider
{
    public IDbConnection CreateConnection(string connectionString)
    {
        return new SQLiteConnection(connectionString);
    }

    public IDbCommand CreateCommand(string query, IDbConnection connection)
    {
        return new SQLiteCommand(query, (SQLiteConnection)connection);
    }

    public IDbDataAdapter CreateAdapter(IDbCommand command)
    {
        return new SQLiteDataAdapter((SQLiteCommand)command);
    }
}

四、SqlHelper:统一入口

4.1 初始化与连接字符串构建

csharp 复制代码
public static class SqlHelper
{
    private static IDatabaseProvider _provider;
    private static string _connectionString;

    public static void Initialize(string dbType, string server, 
        string database, string user, string password)
    {
        DatabaseType type = (DatabaseType)Enum.Parse(typeof(DatabaseType), dbType);
        _connectionString = BuildConnectionString(type, server, database, user, password);
        
        switch (type)
        {
            case DatabaseType.SqlServer:
                _provider = new SqlServerProvider();
                break;
            case DatabaseType.MySQL:
                _provider = new MySqlProvider();
                break;
            case DatabaseType.SQLite:
                _provider = new SqliteProvider();
                break;
            default:
                throw new ArgumentException("Unsupported database type");
        }
    }

    private static string BuildConnectionString(DatabaseType dbType, ...)
    {
        switch (dbType)
        {
            case DatabaseType.SqlServer:
                return $"Server={server};Database={database};Uid={user};Password={password};";
            case DatabaseType.MySQL:
                return $"Server={server};Database={database};Uid={user};Pwd={password};";
            case DatabaseType.SQLite:
                return $"Data Source={database};Version=3;";
            default:
                throw new ArgumentException("Unsupported database type");
        }
    }
}

4.2 连接管理

csharp 复制代码
private static IDbConnection OpenConnection()
{
    _connection = _provider.CreateConnection(_connectionString);
    if (_connection.State != ConnectionState.Open)
        _connection.Open();
    return _connection;
}

public static bool ConnectTest()
{
    using (var conn = OpenConnection())
    {
        return _connection.State == ConnectionState.Open;
    }
}

4.3 执行查询

csharp 复制代码
public static DataTable GetDataTable(string sql)
{
    DataTable dt = new DataTable();
    try
    {
        using (var conn = OpenConnection())
        {
            using (var cmd = _provider.CreateCommand(sql, conn))
            {
                var adapter = _provider.CreateAdapter(cmd);
                
                switch (_provider)
                {
                    case SqlServerProvider _:
                        ((SqlDataAdapter)adapter).Fill(dt);
                        break;
                    case MySqlProvider _:
                        ((MySqlDataAdapter)adapter).Fill(dt);
                        break;
                    case SqliteProvider _:
                        ((SQLiteDataAdapter)adapter).Fill(dt);
                        break;
                }
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("执行 SQL 异常: " + ex.ToString());
    }
    return dt;
}

4.4 执行非查询

csharp 复制代码
public static int ExecuteNonQuery(string sql)
{
    int rowsAffected = -1;
    try
    {
        using (var conn = OpenConnection())
        {
            using (var cmd = _provider.CreateCommand(sql, conn))
            {
                rowsAffected = cmd.ExecuteNonQuery();
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("执行 SQL 异常: " + ex.ToString());
    }
    return rowsAffected;
}

五、数据库表创建

CreateTableSql.cs 中定义了建表语句(SQL Server 语法):

csharp 复制代码
public static string createProductSql = 
    "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.Product')) " +
    "BEGIN " +
    "CREATE TABLE dbo.Product (" +
    "id int identity(1,1) primary key NOT NULL, " +
    "product_name nvarchar(50) NOT NULL, " +
    "product_type int NOT NULL, " +
    "product_length decimal(18,1) NOT NULL, " +
    "length_up_offset decimal(18,1) NOT NULL, " +
    "length_down_offset decimal(18,1) NOT NULL, " +
    "product_width decimal(18,1) NOT NULL, " +
    "width_up_offset decimal(18,1) NOT NULL, " +
    "width_down_offset decimal(18,1) NOT NULL, " +
    "delay_time decimal(18,1) NOT NULL, " +
    "hold_time decimal(18,1) NOT NULL, " +
    "create_time datetime, " +
    "update_time datetime); " +
    "END";

注意 :SQLite 和 MySQL 的建表语法略有不同,实际项目中需要根据 dbType 切换对应的 SQL。


六、SQL 注入防范

6.1 当前项目的风险点

本项目为了简化,部分 SQL 采用字符串拼接:

csharp 复制代码
string deleteSql = $"delete from Product where product_name = '{selectedLabel.Text}'";

如果 selectedLabel.Text 包含 ',将导致语法错误或注入攻击。

6.2 修复方案:参数化查询

csharp 复制代码
public static int ExecuteNonQuerySafe(string sql, params IDbDataParameter[] parameters)
{
    using (var conn = OpenConnection())
    {
        using (var cmd = _provider.CreateCommand(sql, conn))
        {
            foreach (var param in parameters)
            {
                cmd.Parameters.Add(param);
            }
            return cmd.ExecuteNonQuery();
        }
    }
}

// 调用示例
ExecuteNonQuerySafe(
    "DELETE FROM Product WHERE product_name = @name",
    new SqlParameter("@name", selectedLabel.Text));

七、总结

本文深入讲解了数据访问层的设计:

  1. IDatabaseProvider 接口:抽象三种数据库的共同操作
  2. 工厂模式SqlHelper.Initialize 根据配置动态创建对应 Provider
  3. 连接管理using 语句确保连接及时释放
  4. SQL 注入:参数化查询是生产环境的必备措施

下篇预告:《从零打造 C# 工业视觉检测系统(十):检测记录入库与 Excel 导出实战》

我们将完成检测数据的保存、历史记录的分页查询,以及 EPPlus Excel 导出功能。


欢迎点赞、收藏、关注,你的支持是我持续输出的动力!

相关推荐
ha_lydms1 小时前
Hologres分区表
大数据·mysql·阿里云·实时数仓·hologres·调度·aliyun
JLWcai202510092 小时前
核电打磨 “安全卫士”|金利威不锈钢专用树脂砂轮,合规高效双在线
mysql·mongodb·eureka·sqlite·rabbitmq·nosql·memcached
LccKyI3 小时前
C#学习day04
经验分享·笔记·学习·c#
程序员-Benothing4 小时前
MySQL 的覆盖索引是什么?
数据库·mysql
LccKyI4 小时前
C#学习day05(开发福彩双色球系统附思维导图)
开发语言·学习·c#
星恒讯工业路由器5 小时前
MIMO与多天线技术:从5G到WiFi的工程解析
数据库·mysql·5g·工业物联网·mimo技术·天线隔离度·5g/wifi多天线
小Ti客栈5 小时前
MySQL查询原理:从Server到InnoDB
android·mysql·adb
-银雾鸢尾-5 小时前
C#中的协变和逆变
开发语言·c#
2501_937860945 小时前
MySQL数据库入门|从零搞懂数据库基础、架构与存储引擎
数据库·mysql·架构