青少年编程与数学 02-020 C#程序设计基础 13课题、数据访问

news2025/6/1 21:31:07

青少年编程与数学 02-020 C#程序设计基础 13课题、数据访问

  • 一、使用数据库
    • 1. 使用ADO.NET连接数据库
      • 连接SQL Server示例
      • 连接其他数据库
    • 2. 使用Entity Framework (EF Core)
      • 安装EF Core
      • 示例代码
    • 3. 数据绑定到WinForms控件
      • DataGridView绑定
      • 简单控件绑定
    • 4. 使用本地数据库(SQLite)
      • 安装SQLite
      • 示例代码
    • 5. 最佳实践
    • 6. 配置文件存储连接字符串
  • 二、使用SQL Server数据库
    • 1. 优势分析
    • 2. 劣势与挑战
    • 3. 适用场景评价
    • 4. 技术实现建议
    • 5. 替代方案对比
    • 6. 结论
  • 三、.NET Framework ADO.NET 详解
    • (一)ADO.NET 核心组件
      • 1. 两大核心组件集
      • 2. 核心对象模型
    • (二)详细使用指南
      • 1. 连接数据库 (SqlConnection)
      • 2. 执行命令 (SqlCommand)
        • 基本CRUD操作
        • 参数化查询的重要性
      • 3. 数据读取器 (SqlDataReader)
      • 4. 数据适配器与数据集 (SqlDataAdapter + DataSet)
    • (三)高级主题
      • 1. 事务处理
      • 2. 存储过程调用
      • 3. 批量操作
      • 4. 异步操作
    • (四)性能优化技巧
    • (五)安全实践
    • (六)ADO.NET vs Entity Framework
    • (七)实际应用示例 - WinForms数据绑定
  • 四、.NET 8 ADO.NET 详解
    • (一)现代 ADO.NET 架构
      • 1. 组件模型升级
      • 2. 核心命名空间
    • (二)连接管理与配置
      • 1. 现代连接字符串配置
      • 2. 高级连接选项
    • (三)命令执行增强
      • 1. 参数化查询现代化
      • 2. 批量操作优化
    • (四)数据读取高级技术
      • 1. 现代DataReader模式
      • 2. 结果集流式处理
    • (五)事务与隔离级别
      • 1. 现代事务管理
      • 2. 分布式事务支持
    • (六)安全增强特性
      • 1. Always Encrypted 集成
      • 2. Azure AD 集成
    • (七)性能优化技巧
      • 1. 连接池优化配置
      • 2. 命令准备与重用
    • (八)诊断与监控
      • 1. 活动追踪配置
      • 2. 获取性能统计
    • (九)与 Entity Framework Core 集成
      • 1. 混合使用原始SQL
      • 2. 批量操作集成
    • (十)迁移指南
      • 从传统 ADO.NET 迁移步骤:
    • 小结
  • 五、综合示例
    • 1. 项目设置
      • 1.1 创建项目
    • 2. 完整代码实现
      • 2.1 数据库帮助类 (`Data/DatabaseHelper.cs`)
      • 2.2 主窗体 (`MainForm.cs`)
      • 2.3 产品详情窗体 (`ProductDetailForm.cs`)
      • 2.4 分类管理窗体 (`CategoryForm.cs`)
      • 2.5 订单处理窗体 (`OrderForm.cs`)
    • 3. 主要特点
    • 4. 与传统ADO.NET的区别
  • 总结

摘要:本文深入探讨了在C# WinForms应用程序中使用ADO.NET和Entity Framework Core进行数据库操作的方法。详细介绍了ADO.NET的核心组件、使用方法、性能优化和安全实践,以及Entity Framework Core的安装和使用。文章还提供了数据绑定到WinForms控件的示例,并展示了.NET 8中ADO.NET的现代化改进和性能优化技巧。通过一个完整的WinForms应用程序示例,展示了如何使用ADO.NET进行数据库操作,为开发者提供了实用的技术参考。

关键词:C# WinForms、ADO.NET、Entity Framework Core、数据库操作、数据绑定、安全实践、.NET 8

AI助手:DeepSeek、Kimi


一、使用数据库

在C# WinForms应用程序中使用数据库是常见的需求,下面我将介绍几种主要的方法:

1. 使用ADO.NET连接数据库

ADO.NET是.NET框架中访问数据库的核心技术。

连接SQL Server示例

using System.Data.SqlClient;

// 连接字符串
string connectionString = "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;";

// 执行查询
using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    
    // 执行SQL命令
    string sql = "SELECT * FROM Customers";
    using (SqlCommand command = new SqlCommand(sql, connection))
    {
        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                Console.WriteLine(reader["CustomerName"].ToString());
            }
        }
    }
}

连接其他数据库

对于MySQL、Oracle等数据库,需要使用相应的提供程序(如MySql.Data.MySqlClient)。

2. 使用Entity Framework (EF Core)

EF Core是一个流行的ORM框架,可以简化数据库操作。

安装EF Core

通过NuGet包管理器安装:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer (或其他数据库提供程序)

示例代码

// 定义模型
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// 创建DbContext
public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("your_connection_string");
    }
}

// 使用示例
using (var context = new AppDbContext())
{
    // 查询
    var customers = context.Customers.Where(c => c.Name.Contains("John")).ToList();
    
    // 添加
    var newCustomer = new Customer { Name = "Alice", Email = "alice@example.com" };
    context.Customers.Add(newCustomer);
    context.SaveChanges();
    
    // 更新
    var customer = context.Customers.First();
    customer.Email = "newemail@example.com";
    context.SaveChanges();
}

3. 数据绑定到WinForms控件

DataGridView绑定

// 使用ADO.NET DataTable
DataTable dataTable = new DataTable();
using (SqlConnection connection = new SqlConnection(connectionString))
{
    SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Customers", connection);
    adapter.Fill(dataTable);
}

dataGridView1.DataSource = dataTable;

// 使用EF Core
dataGridView1.DataSource = dbContext.Customers.ToList();

简单控件绑定

// 绑定到TextBox
textBoxName.DataBindings.Add("Text", dataTable, "CustomerName");

4. 使用本地数据库(SQLite)

对于小型应用程序,SQLite是一个不错的选择。

安装SQLite

通过NuGet安装:

  • Microsoft.Data.Sqlite
  • Microsoft.EntityFrameworkCore.Sqlite

示例代码

public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=mydatabase.db");
    }
}

5. 最佳实践

  1. 连接管理:始终使用using语句确保连接和命令对象被正确释放
  2. 异常处理:捕获和处理数据库异常(SqlException等)
  3. 参数化查询:防止SQL注入
    string sql = "SELECT * FROM Customers WHERE Id = @Id";
    command.Parameters.AddWithValue("@Id", customerId);
    
  4. 异步操作:对于长时间运行的数据库操作,使用异步方法
    var customers = await dbContext.Customers.ToListAsync();
    
  5. 配置管理:将连接字符串存储在app.config或appsettings.json中

6. 配置文件存储连接字符串

在app.config中:

<configuration>
    <connectionStrings>
        <add name="MyDb" connectionString="Server=.;Database=MyDb;Integrated Security=True;" providerName="System.Data.SqlClient" />
    </connectionStrings>
</configuration>

在代码中读取:

string connectionString = ConfigurationManager.ConnectionStrings["MyDb"].ConnectionString;

以上是在C# WinForms应用程序中使用数据库的主要方法。根据项目规模和需求,可以选择ADO.NET直接操作或使用ORM框架如EF Core来简化开发。

二、使用SQL Server数据库

在C# WinForms应用程序中使用SQL Server数据库是一种常见且强大的组合,以下是全面评价:

1. 优势分析

高性能与稳定性

  • SQL Server是成熟的商业数据库,特别适合处理大量数据和复杂查询
  • 对于企业级WinForms应用,能够提供可靠的性能表现
  • 支持存储过程、触发器等高级功能,可将业务逻辑部分放在数据库层

与.NET生态完美集成

  • ADO.NET原生支持SQL Server,提供最优化的访问方式
  • Entity Framework对SQL Server的支持最全面,功能实现最完整
  • Visual Studio提供SQL Server的直接工具支持(Server Explorer等)

开发效率高

  • 丰富的可视化工具(SQL Server Management Studio)
  • LINQ to SQL和Entity Framework可显著减少数据访问层代码量
  • 数据集设计器(DataSet Designer)可快速创建类型化数据集

安全特性完善

  • 集成Windows身份验证(Integrated Security)
  • 细粒度的权限控制
  • 数据加密功能完善

企业级功能支持

  • 事务处理能力强
  • 支持分布式查询
  • 完善的备份恢复机制

2. 劣势与挑战

部署复杂度

  • 需要安装SQL Server(Express版可减轻此问题)
  • 相比SQLite等文件数据库,部署更复杂
  • 需要管理数据库服务器

成本考量

  • 标准版和企业版成本较高(但Express版免费)
  • 需要专门的数据库服务器硬件

不适合小型应用

  • 对于单机小型应用可能"杀鸡用牛刀"
  • 简单的数据存储需求使用SQLite或Access可能更合适

版本兼容性

  • 不同SQL Server版本间有时存在兼容性问题
  • 连接字符串配置可能需要调整

3. 适用场景评价

✅ 推荐使用场景

  • 企业级内部管理系统(ERP、CRM等)
  • 多用户并发访问的系统
  • 需要处理复杂业务逻辑和大量数据的应用
  • 已有SQL Server基础设施的环境

❌ 不推荐场景

  • 单机小型工具类应用
  • 需要简单部署的共享软件
  • 移动端或需要离线使用的应用

4. 技术实现建议

架构选择

  • 对于简单应用:ADO.NET + DataSet
  • 对于中型应用:Entity Framework Core
  • 对于复杂企业应用:Repository模式 + Dapper

连接管理最佳实践

// 使用配置文件的连接字符串
string connStr = ConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;

// 使用using确保资源释放
using (SqlConnection conn = new SqlConnection(connStr))
{
    await conn.OpenAsync();
    // 数据库操作
}

性能优化建议

  • 使用异步方法(async/await)避免UI冻结
  • 合理使用连接池
  • 对常用查询添加索引
  • 考虑使用存储过程处理复杂逻辑

安全建议

  • 永远使用参数化查询防止SQL注入
  • 不要硬编码连接字符串
  • 实施最小权限原则

5. 替代方案对比

特性SQL ServerSQLiteMySQLPostgreSQL
部署难度
性能
适合规模中大型小型中大中大
.NET支持最佳
成本中高(Express免费)免费免费免费

6. 结论

C# WinForms + SQL Server组合在适合的场景下是非常强大的选择,特别是对于企业级Windows桌面应用开发。它提供了性能、可靠性和开发效率的良好平衡,但开发者需要考虑部署复杂度和成本因素。对于不适用SQL Server的场景,可以考虑SQLite(单机简单应用)或其他替代方案。

三、.NET Framework ADO.NET 详解

ADO.NET 是 .NET Framework 中用于数据访问的核心组件,为 C# WinForms 应用程序提供了一套强大而灵活的数据库操作方式。下面我将从基础到高级全面解析 ADO.NET。

(一)ADO.NET 核心组件

1. 两大核心组件集

  • .NET Framework 数据提供程序 (专用于特定数据库类型)

    • SQL Server 提供程序: System.Data.SqlClient
    • OLE DB 提供程序: System.Data.OleDb
    • ODBC 提供程序: System.Data.Odbc
    • Oracle 提供程序: System.Data.OracleClient (已过时,推荐使用 Oracle 官方提供程序)
  • DataSet (独立于数据库的内存数据容器)

2. 核心对象模型

连接对象 (Connection) → 命令对象 (Command)
                         ↓
数据读取器 (DataReader) ← 执行查询
                         ↓
数据适配器 (DataAdapter) → DataSet/DataTable

(二)详细使用指南

1. 连接数据库 (SqlConnection)

// 基本连接字符串格式
string connStr = "Server=服务器名;Database=数据库名;User Id=用户名;Password=密码;";

// 使用Windows身份验证
string connStr = "Server=服务器名;Database=数据库名;Integrated Security=True;";

// 创建连接对象
using (SqlConnection connection = new SqlConnection(connStr))
{
    try
    {
        connection.Open();
        // 执行数据库操作...
    }
    catch (SqlException ex)
    {
        MessageBox.Show($"数据库错误: {ex.Message}");
    }
}

最佳实践:

  • 始终使用 using 语句确保连接关闭
  • 将连接字符串存储在配置文件中
  • 启用连接池(默认开启)

2. 执行命令 (SqlCommand)

基本CRUD操作
// 插入数据
string insertSql = "INSERT INTO Customers (Name, Email) VALUES (@Name, @Email)";
using (SqlCommand cmd = new SqlCommand(insertSql, connection))
{
    cmd.Parameters.AddWithValue("@Name", "张三");
    cmd.Parameters.AddWithValue("@Email", "zhangsan@example.com");
    int rowsAffected = cmd.ExecuteNonQuery();
}

// 更新数据
string updateSql = "UPDATE Customers SET Email = @Email WHERE Id = @Id";
using (SqlCommand cmd = new SqlCommand(updateSql, connection))
{
    cmd.Parameters.AddWithValue("@Email", "new@example.com");
    cmd.Parameters.AddWithValue("@Id", 1);
    cmd.ExecuteNonQuery();
}

// 删除数据
string deleteSql = "DELETE FROM Customers WHERE Id = @Id";
using (SqlCommand cmd = new SqlCommand(deleteSql, connection))
{
    cmd.Parameters.AddWithValue("@Id", 1);
    cmd.ExecuteNonQuery();
}
参数化查询的重要性
  • 防止SQL注入攻击
  • 提高查询性能(执行计划可重用)
  • 自动处理数据类型转换

3. 数据读取器 (SqlDataReader)

string sql = "SELECT Id, Name, Email FROM Customers";
using (SqlCommand cmd = new SqlCommand(sql, connection))
{
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        while (reader.Read())
        {
            int id = reader.GetInt32(0);           // 按索引获取
            string name = reader["Name"].ToString(); // 按列名获取
            string email = reader.GetString(2);     // 使用类型特定方法
            
            // 处理数据...
        }
    }
}

特点:

  • 只进、只读的快速数据访问
  • 非常节省内存(不缓存整个结果集)
  • 需要保持连接打开状态

4. 数据适配器与数据集 (SqlDataAdapter + DataSet)

// 创建适配器和数据集
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Customers", connection);
DataSet ds = new DataSet();

// 填充数据
adapter.Fill(ds, "Customers");  // "Customers"是DataTable名称

// 访问数据
foreach (DataRow row in ds.Tables["Customers"].Rows)
{
    Console.WriteLine($"{row["Id"]}: {row["Name"]}");
}

// 更新数据
DataTable dt = ds.Tables["Customers"];
dt.Rows[0]["Name"] = "修改后的名字";

// 配置命令生成器自动生成更新命令
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.Update(dt);

DataSet 特点:

  • 断开式数据访问模型
  • 内存中的数据缓存
  • 可以包含多个表和数据关系
  • 支持数据绑定

(三)高级主题

1. 事务处理

using (SqlTransaction transaction = connection.BeginTransaction())
{
    try
    {
        using (SqlCommand cmd = connection.CreateCommand())
        {
            cmd.Transaction = transaction;
            cmd.CommandText = "INSERT INTO Orders...";
            cmd.ExecuteNonQuery();
            
            cmd.CommandText = "UPDATE Inventory...";
            cmd.ExecuteNonQuery();
            
            transaction.Commit();  // 提交事务
        }
    }
    catch
    {
        transaction.Rollback();   // 回滚事务
        throw;
    }
}

2. 存储过程调用

using (SqlCommand cmd = new SqlCommand("sp_GetCustomerOrders", connection))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.AddWithValue("@CustomerId", 123);
    
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        // 处理结果...
    }
}

3. 批量操作

// 使用SqlBulkCopy高效导入数据
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
    bulkCopy.DestinationTableName = "TargetTable";
    bulkCopy.WriteToServer(dataTable);  // 可以从DataTable、IDataReader等导入
}

4. 异步操作

// 异步方法
public async Task<List<Customer>> GetCustomersAsync()
{
    List<Customer> customers = new List<Customer>();
    
    using (SqlConnection conn = new SqlConnection(connStr))
    {
        await conn.OpenAsync();
        
        using (SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", conn))
        {
            using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    customers.Add(new Customer
                    {
                        Id = reader.GetInt32(0),
                        Name = reader.GetString(1),
                        Email = reader.GetString(2)
                    });
                }
            }
        }
    }
    
    return customers;
}

(四)性能优化技巧

  1. 连接池优化

    • 默认已启用,不要手动开关池
    • 保持连接字符串一致(区分大小写)
  2. 命令对象重用

    • 对于重复执行的命令,重用SqlCommand对象
    • 使用 Prepare() 方法预编译命令
  3. 适当选择数据访问方式

    • 大量只读数据 → DataReader
    • 离线编辑数据 → DataAdapter + DataSet
    • 单值查询 → ExecuteScalar
  4. 批处理操作

    • 合并多个操作为一个SQL语句
    • 使用表值参数(TVP)传递多行数据

(五)安全实践

  1. 防范SQL注入

    • 永远使用参数化查询
    • 不要拼接SQL字符串
    // 错误做法(危险!)
    string sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";
    
    // 正确做法
    string sql = "SELECT * FROM Users WHERE Name = @UserName";
    cmd.Parameters.AddWithValue("@UserName", userName);
    
  2. 连接字符串安全

    • 不要硬编码在代码中
    • 使用配置文件和加密技术
  3. 最小权限原则

    • 应用程序使用最小必要权限的数据库账户

(六)ADO.NET vs Entity Framework

特性ADO.NETEntity Framework
抽象级别低级别,接近SQL高级别,面向对象
开发速度较慢,需要更多代码更快,自动生成很多代码
性能更高效,更可控良好,但可能有额外开销
灵活性完全控制SQL有限制,但可以混合使用原始SQL
学习曲线需要了解SQL需要理解ORM概念
适用场景高性能需求,复杂查询快速开发,简单到中等复杂度应用

(七)实际应用示例 - WinForms数据绑定

// 绑定DataGridView
public void LoadCustomersToGrid()
{
    string sql = "SELECT * FROM Customers";
    SqlDataAdapter adapter = new SqlDataAdapter(sql, connectionString);
    DataTable dt = new DataTable();
    adapter.Fill(dt);
    
    dataGridView1.DataSource = dt;
    
    // 设置列标题
    dataGridView1.Columns["Id"].HeaderText = "编号";
    dataGridView1.Columns["Name"].HeaderText = "姓名";
    dataGridView1.Columns["Email"].HeaderText = "电子邮箱";
}

// 主从表绑定
public void BindMasterDetail()
{
    DataSet ds = new DataSet();
    
    // 主表
    SqlDataAdapter masterAdapter = new SqlDataAdapter("SELECT * FROM Customers", connectionString);
    masterAdapter.Fill(ds, "Customers");
    
    // 从表
    SqlDataAdapter detailAdapter = new SqlDataAdapter("SELECT * FROM Orders", connectionString);
    detailAdapter.Fill(ds, "Orders");
    
    // 建立关系
    DataRelation relation = new DataRelation("CustOrders",
        ds.Tables["Customers"].Columns["Id"],
        ds.Tables["Orders"].Columns["CustomerId"]);
    ds.Relations.Add(relation);
    
    // 绑定
    dataGridView1.DataSource = ds.Tables["Customers"];
    dataGridView2.DataSource = ds.Tables["Customers"];
    dataGridView2.DataMember = "CustOrders";  // 显示关联的订单
}

ADO.NET 为 WinForms 数据库开发提供了坚实的基础,理解其核心概念和最佳实践将帮助您构建高效、安全的数据库应用程序。对于新项目,可以考虑结合使用 ADO.NET 和更高级的 ORM 工具以获得更好的开发体验。

四、.NET 8 ADO.NET 详解

ADO.NET 在 .NET 8 中经过现代化改造,保留了核心数据访问能力的同时,引入了诸多改进和新特性。以下是对 .NET 8 中 ADO.NET 的全面详解:

(一)现代 ADO.NET 架构

1. 组件模型升级

  • 模块化设计:通过 NuGet 分发 (Microsoft.Data.SqlClient)
  • 分层架构:
    应用程序
    Microsoft.Data.SqlClient
    TDS协议层
    网络传输
    安全层

2. 核心命名空间

  • Microsoft.Data.SqlClient (替代旧的 System.Data.SqlClient)
  • System.Data.Common (通用接口)
  • Microsoft.Data.SqlClient.AlwaysEncrypted (专门加密支持)

(二)连接管理与配置

1. 现代连接字符串配置

// 推荐的安全连接字符串
var connectionString = new SqlConnectionStringBuilder
{
    DataSource = "your-server.database.windows.net",
    InitialCatalog = "your-database",
    UserID = "your-username",
    Password = "your-password",
    Encrypt = SqlConnectionEncryptOption.Mandatory,  // 强制加密
    TrustServerCertificate = false,                 // 生产环境应为false
    ConnectTimeout = 15,
    Pooling = true,                                // 启用连接池
    MaxPoolSize = 100                              // 连接池大小
}.ConnectionString;

2. 高级连接选项

// 使用连接属性配置
var connection = new SqlConnection(connectionString)
{
    AccessToken = azureAdToken,  // Azure AD集成
    RetryLogicProvider = SqlConfigurableRetryFactory.CreateFixedRetryProvider(
        new SqlRetryLogicOption
        {
            NumberOfTries = 3,
            DeltaTime = TimeSpan.FromSeconds(1)
        })
};

(三)命令执行增强

1. 参数化查询现代化

// 现代参数化查询(防注入)
var command = new SqlCommand(
    "SELECT * FROM Products WHERE CategoryId = @Category AND Price > @MinPrice", 
    connection);

// 强类型参数添加(推荐方式)
command.Parameters.Add(new SqlParameter
{
    ParameterName = "@Category",
    SqlDbType = SqlDbType.Int,
    Value = categoryId
});

// 简化方式(自动推断类型)
command.Parameters.AddWithValue("@MinPrice", minimumPrice);

2. 批量操作优化

// 高性能批量插入(使用SqlBulkCopy)
using var bulkCopy = new SqlBulkCopy(connection)
{
    DestinationTableName = "Products",
    BatchSize = 1000,
    BulkCopyTimeout = 30
};

// 从DataReader流式传输
await bulkCopy.WriteToServerAsync(dataReader);

// 从DataTable批量写入
await bulkCopy.WriteToServerAsync(dataTable);

(四)数据读取高级技术

1. 现代DataReader模式

await using var reader = await command.ExecuteReaderAsync();

// 获取列元数据(避免魔法字符串)
var idOrdinal = reader.GetOrdinal("ProductId");
var nameOrdinal = reader.GetOrdinal("ProductName");

while (await reader.ReadAsync())
{
    var product = new Product
    {
        // 按序号访问(性能最优)
        Id = reader.GetInt32(idOrdinal),
        
        // 处理可能为NULL的值
        Name = reader.IsDBNull(nameOrdinal) ? null : reader.GetString(nameOrdinal),
        
        // 新型GetFieldValue方法
        Price = await reader.GetFieldValueAsync<decimal>(2)
    };
}

2. 结果集流式处理

// 返回IAsyncEnumerable实现流式处理
public async IAsyncEnumerable<Product> StreamLargeProducts()
{
    await using var reader = await command.ExecuteReaderAsync(
        CommandBehavior.SequentialAccess);  // 大对象优化
    
    while (await reader.ReadAsync())
    {
        yield return new Product
        {
            Id = reader.GetInt32(0),
            Name = reader.GetString(1),
            // 分块读取大文本/二进制数据
            Description = await reader.GetTextReader(2).ReadToEndAsync()
        };
    }
}

(五)事务与隔离级别

1. 现代事务管理

// 使用TransactionScope(推荐)
await using var scope = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions
    {
        IsolationLevel = IsolationLevel.Snapshot,
        Timeout = TransactionManager.DefaultTimeout
    },
    TransactionScopeAsyncFlowOption.Enabled);

try
{
    // 多个操作...
    await connection1.ExecuteNonQueryAsync(cmd1);
    await connection2.ExecuteNonQueryAsync(cmd2);
    
    scope.Complete();  // 提交事务
}
catch
{
    // 自动回滚
}

2. 分布式事务支持

// 启用MSDTC或使用新式分布式事务
var options = new SqlConnectionOptions
{
    EnlistDistributedTransaction = true
};

await using var conn = new SqlConnection(connectionString, options);

(六)安全增强特性

1. Always Encrypted 集成

// 配置加密列处理
var connectionString = "Server=...; Column Encryption Setting=enabled;";
var command = new SqlCommand("SELECT * FROM Customers WHERE SSN = @SSN", connection);

// 自动加密/解密
command.Parameters.AddWithValue("@SSN", "123-45-6789"); 

// 查询结果自动解密
await using var reader = await command.ExecuteReaderAsync();

2. Azure AD 集成

// 使用Azure AD令牌认证
var connection = new SqlConnection(connectionString)
{
    AccessToken = await GetAzureAdTokenAsync()
};

// 或者使用连接字符串配置
var connStr = "Server=...; Authentication=Active Directory Default;";

(七)性能优化技巧

1. 连接池优化配置

// 在应用程序启动时配置全局连接池
SqlConnection.Configure(
    new SqlConnectionConfiguration
    {
        PoolSettings = new SqlConnectionPoolSettings
        {
            MaxPoolSize = 200,
            MinPoolSize = 10,
            LoadBalanceTimeout = 30
        }
    });

2. 命令准备与重用

// 准备常用命令(查询计划缓存)
var cmd = new SqlCommand("SELECT * FROM Products WHERE Id = @Id", connection);
cmd.Parameters.Add("@Id", SqlDbType.Int);
await cmd.PrepareAsync();  // 显式准备

// 重用命令对象
for (int i = 0; i < ids.Length; i++)
{
    cmd.Parameters["@Id"].Value = ids[i];
    await using var reader = await cmd.ExecuteReaderAsync();
    // ...
}

(八)诊断与监控

1. 活动追踪配置

// 启用诊断监听
using var listener = new SqlClientListener(
    new SqlClientListenerOptions
    {
        CommandTimeout = true,
        ConnectionOpen = true,
        Statistics = true
    });

listener.EventWritten += (sender, e) => 
{
    Console.WriteLine($"{e.EventId}: {e.Message}");
};

2. 获取性能统计

connection.StatisticsEnabled = true;
await connection.OpenAsync();

// 执行操作...

var stats = connection.RetrieveStatistics();
Console.WriteLine($"Bytes received: {stats["BytesReceived"]}");

(九)与 Entity Framework Core 集成

1. 混合使用原始SQL

var products = await context.Products
    .FromSqlRaw("SELECT * FROM Products WITH (NOLOCK) WHERE Price > {0}", minPrice)
    .ToListAsync();

// 使用SqlParameter防止注入
var param = new SqlParameter("@category", categoryId);
var products = await context.Products
    .FromSqlRaw("EXEC GetProductsByCategory @category", param)
    .AsNoTracking()
    .ToListAsync();

2. 批量操作集成

// 使用EF Core + ADO.NET批量插入
var bulkConfig = new BulkConfig
{
    BatchSize = 4000,
    SqlBulkCopyOptions = SqlBulkCopyOptions.TableLock
};

await context.BulkInsertAsync(products, bulkConfig);

(十)迁移指南

从传统 ADO.NET 迁移步骤:

  1. 更新NuGet包:

    dotnet add package Microsoft.Data.SqlClient
    
  2. 替换命名空间:

    // 替换
    using System.Data.SqlClient;
    // 为
    using Microsoft.Data.SqlClient;
    
  3. 异步化改造:

    • 将同步方法替换为异步版本
    • 在调用链中添加 await
  4. 安全配置更新:

    • 添加 EncryptTrustServerCertificate 选项
    • 考虑启用 Always Encrypted
  5. 性能优化调整:

    • 配置连接池
    • 实现重试逻辑
    • 考虑使用批处理操作

小结

.NET 8 中的 ADO.NET 提供了:

  • 现代化的异步编程模型
  • 增强的安全特性(如 Always Encrypted)
  • 深度云集成(Azure AD、托管身份)
  • 显著性能优化
  • 更好的诊断和监控能力
  • 与 EF Core 的无缝集成

虽然基础编程模型保持熟悉,但开发者应充分利用新特性和最佳实践,以构建高性能、安全的现代数据访问层。

五、综合示例

下面是一个完整的.NET 8 WinForms应用程序示例,直接使用ADO.NET DataSet进行数据库操作。

1. 项目设置

1.1 创建项目

dotnet new winforms -n ProductManagementApp
cd ProductManagementApp
dotnet add package Microsoft.Data.SqlClient

2. 完整代码实现

2.1 数据库帮助类 (Data/DatabaseHelper.cs)

using Microsoft.Data.SqlClient;
using System.Data;

namespace ProductManagementApp.Data
{
    public class DatabaseHelper
    {
        private readonly string _connectionString;

        public DatabaseHelper(string connectionString)
        {
            _connectionString = connectionString;
        }

        public async Task<SqlConnection> CreateConnectionAsync()
        {
            var connection = new SqlConnection(_connectionString);
            await connection.OpenAsync();
            return connection;
        }

        public async Task<DataSet> GetProductsDataSetAsync()
        {
            var ds = new DataSet();
            
            await using var connection = await CreateConnectionAsync();
            
            // 获取产品数据
            var productsAdapter = new SqlDataAdapter(
                "SELECT p.*, c.Name as CategoryName FROM Products p LEFT JOIN Categories c ON p.CategoryId = c.CategoryId", 
                connection);
            productsAdapter.Fill(ds, "Products");
            
            // 获取分类数据
            var categoriesAdapter = new SqlDataAdapter(
                "SELECT * FROM Categories", 
                connection);
            categoriesAdapter.Fill(ds, "Categories");
            
            // 添加关系
            ds.Relations.Add("ProductCategory",
                ds.Tables["Categories"].Columns["CategoryId"],
                ds.Tables["Products"].Columns["CategoryId"]);
            
            return ds;
        }

        public async Task<int> AddProductAsync(DataRow productRow)
        {
            await using var connection = await CreateConnectionAsync();
            
            var adapter = new SqlDataAdapter("SELECT * FROM Products WHERE 1=0", connection);
            var builder = new SqlCommandBuilder(adapter);
            
            var dt = new DataTable();
            adapter.Fill(dt);
            
            var newRow = dt.NewRow();
            newRow.ItemArray = productRow.ItemArray;
            dt.Rows.Add(newRow);
            
            return await adapter.UpdateAsync(dt);
        }

        public async Task<int> UpdateProductAsync(DataRow productRow)
        {
            await using var connection = await CreateConnectionAsync();
            
            var adapter = new SqlDataAdapter("SELECT * FROM Products WHERE ProductId = @ProductId", connection);
            adapter.SelectCommand.Parameters.AddWithValue("@ProductId", productRow["ProductId"]);
            
            var builder = new SqlCommandBuilder(adapter);
            var dt = new DataTable();
            adapter.Fill(dt);
            
            if (dt.Rows.Count == 1)
            {
                dt.Rows[0].ItemArray = productRow.ItemArray;
                return await adapter.UpdateAsync(dt);
            }
            
            return 0;
        }

        public async Task<int> DeleteProductAsync(int productId)
        {
            await using var connection = await CreateConnectionAsync();
            
            var adapter = new SqlDataAdapter("SELECT * FROM Products WHERE ProductId = @ProductId", connection);
            adapter.SelectCommand.Parameters.AddWithValue("@ProductId", productId);
            
            var builder = new SqlCommandBuilder(adapter);
            var dt = new DataTable();
            adapter.Fill(dt);
            
            if (dt.Rows.Count == 1)
            {
                dt.Rows[0].Delete();
                return await adapter.UpdateAsync(dt);
            }
            
            return 0;
        }

        public async Task<int> AddCategoryAsync(string name, string description)
        {
            await using var connection = await CreateConnectionAsync();
            
            var adapter = new SqlDataAdapter("SELECT * FROM Categories WHERE 1=0", connection);
            var builder = new SqlCommandBuilder(adapter);
            
            var dt = new DataTable();
            adapter.Fill(dt);
            
            var newRow = dt.NewRow();
            newRow["Name"] = name;
            newRow["Description"] = description;
            dt.Rows.Add(newRow);
            
            return await adapter.UpdateAsync(dt);
        }

        public async Task<bool> ProcessOrderAsync(int productId, int quantity)
        {
            await using var connection = await CreateConnectionAsync();
            await using var transaction = await connection.BeginTransactionAsync();
            
            try
            {
                // 1. 检查库存
                var checkCmd = new SqlCommand(
                    "SELECT StockQuantity FROM Products WHERE ProductId = @ProductId", 
                    connection, transaction);
                checkCmd.Parameters.AddWithValue("@ProductId", productId);
                
                var currentStock = (int)await checkCmd.ExecuteScalarAsync();
                
                if (currentStock < quantity)
                {
                    throw new Exception("库存不足");
                }

                // 2. 更新库存
                var updateCmd = new SqlCommand(
                    "UPDATE Products SET StockQuantity = StockQuantity - @Quantity WHERE ProductId = @ProductId", 
                    connection, transaction);
                updateCmd.Parameters.AddWithValue("@ProductId", productId);
                updateCmd.Parameters.AddWithValue("@Quantity", quantity);
                
                await updateCmd.ExecuteNonQueryAsync();
                
                // 3. 记录订单 (示例)
                // var orderCmd = new SqlCommand("INSERT INTO Orders...", connection, transaction);
                // await orderCmd.ExecuteNonQueryAsync();
                
                await transaction.CommitAsync();
                return true;
            }
            catch
            {
                await transaction.RollbackAsync();
                throw;
            }
        }
    }
}

2.2 主窗体 (MainForm.cs)

using ProductManagementApp.Data;
using System.Data;

namespace ProductManagementApp
{
    public partial class MainForm : Form
    {
        private readonly DatabaseHelper _dbHelper;
        private DataSet _dataSet;
        private DataView _productsView;

        public MainForm()
        {
            InitializeComponent();
            
            // 配置连接字符串 (实际项目中应该放在配置文件中)
            var connectionString = "Server=(local);Database=ProductDB;Integrated Security=true;Encrypt=False;";
            _dbHelper = new DatabaseHelper(connectionString);
        }

        private async void MainForm_Load(object sender, EventArgs e)
        {
            await LoadDataAsync();
            ConfigureDataGridView();
            ConfigureCategoryComboBox();
        }

        private async Task LoadDataAsync()
        {
            try
            {
                _dataSet = await _dbHelper.GetProductsDataSetAsync();
                _productsView = new DataView(_dataSet.Tables["Products"]);
                dataGridView1.DataSource = _productsView;
            }
            catch (SqlException ex)
            {
                MessageBox.Show($"数据库错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ConfigureDataGridView()
        {
            dataGridView1.AutoGenerateColumns = false;
            dataGridView1.Columns.Clear();

            // 添加列
            dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "ProductId",
                HeaderText = "ID",
                Name = "colProductId",
                ReadOnly = true
            });

            dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "Name",
                HeaderText = "产品名称",
                Name = "colName"
            });

            dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "CategoryName",
                HeaderText = "分类",
                Name = "colCategory",
                ReadOnly = true
            });

            dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "Price",
                HeaderText = "价格",
                Name = "colPrice",
                DefaultCellStyle = new DataGridViewCellStyle { Format = "C2" }
            });

            dataGridView1.Columns.Add(new DataGridViewTextBoxColumn
            {
                DataPropertyName = "StockQuantity",
                HeaderText = "库存",
                Name = "colStock"
            });

            // 双击事件
            dataGridView1.CellDoubleClick += DataGridView1_CellDoubleClick;
        }

        private void ConfigureCategoryComboBox()
        {
            if (_dataSet?.Tables["Categories"] != null)
            {
                cmbCategories.DataSource = _dataSet.Tables["Categories"];
                cmbCategories.DisplayMember = "Name";
                cmbCategories.ValueMember = "CategoryId";
            }
        }

        private void DataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex >= 0)
            {
                var row = ((DataRowView)_productsView[e.RowIndex]).Row;
                ShowProductDetail(row);
            }
        }

        private void ShowProductDetail(DataRow productRow)
        {
            var detailForm = new ProductDetailForm(_dbHelper, productRow);
            if (detailForm.ShowDialog() == DialogResult.OK)
            {
                _productsView.Table.AcceptChanges();
                dataGridView1.Refresh();
            }
        }

        private async void btnAdd_Click(object sender, EventArgs e)
        {
            if (_dataSet == null) return;

            var newRow = _dataSet.Tables["Products"].NewRow();
            newRow["Name"] = txtName.Text;
            newRow["Description"] = txtDescription.Text;
            newRow["Price"] = decimal.Parse(txtPrice.Text);
            newRow["StockQuantity"] = (int)nudStock.Value;
            newRow["CategoryId"] = cmbCategories.SelectedValue;
            newRow["CreatedDate"] = DateTime.Now;

            try
            {
                int result = await _dbHelper.AddProductAsync(newRow);
                if (result > 0)
                {
                    MessageBox.Show("产品添加成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    await LoadDataAsync();
                    ClearInputs();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"添加产品失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ClearInputs()
        {
            txtName.Clear();
            txtDescription.Clear();
            txtPrice.Clear();
            nudStock.Value = 0;
            if (cmbCategories.Items.Count > 0)
                cmbCategories.SelectedIndex = 0;
        }

        private async void btnRefresh_Click(object sender, EventArgs e)
        {
            await LoadDataAsync();
        }

        private async void btnAddCategory_Click(object sender, EventArgs e)
        {
            var categoryForm = new CategoryForm(_dbHelper);
            if (categoryForm.ShowDialog() == DialogResult.OK)
            {
                await LoadDataAsync();
                ConfigureCategoryComboBox();
            }
        }

        private async void btnProcessOrder_Click(object sender, EventArgs e)
        {
            if (dataGridView1.SelectedRows.Count > 0)
            {
                var row = ((DataRowView)dataGridView1.SelectedRows[0].DataBoundItem).Row;
                int productId = (int)row["ProductId"];
                int currentStock = (int)row["StockQuantity"];
                
                using var orderForm = new OrderForm(productId, currentStock);
                if (orderForm.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        bool success = await _dbHelper.ProcessOrderAsync(
                            productId, orderForm.Quantity);
                        
                        if (success)
                        {
                            MessageBox.Show("订单处理成功!", "成功", 
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
                            await LoadDataAsync();
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"订单处理失败: {ex.Message}", "错误", 
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            else
            {
                MessageBox.Show("请先选择一个产品", "提示", 
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

2.3 产品详情窗体 (ProductDetailForm.cs)

using ProductManagementApp.Data;
using System.Data;

namespace ProductManagementApp
{
    public partial class ProductDetailForm : Form
    {
        private readonly DatabaseHelper _dbHelper;
        private readonly DataRow _productRow;

        public ProductDetailForm(DatabaseHelper dbHelper, DataRow productRow)
        {
            InitializeComponent();
            _dbHelper = dbHelper;
            _productRow = productRow;
            InitializeData();
        }

        private void InitializeData()
        {
            txtProductId.Text = _productRow["ProductId"].ToString();
            txtName.Text = _productRow["Name"].ToString();
            txtDescription.Text = _productRow["Description"].ToString();
            txtPrice.Text = Convert.ToDecimal(_productRow["Price"]).ToString("N2");
            nudStock.Value = Convert.ToInt32(_productRow["StockQuantity"]);
            dtpCreatedDate.Value = Convert.ToDateTime(_productRow["CreatedDate"]);
        }

        private async void btnSave_Click(object sender, EventArgs e)
        {
            _productRow["Name"] = txtName.Text;
            _productRow["Description"] = txtDescription.Text;
            _productRow["Price"] = decimal.Parse(txtPrice.Text);
            _productRow["StockQuantity"] = (int)nudStock.Value;

            try
            {
                int result = await _dbHelper.UpdateProductAsync(_productRow);
                if (result > 0)
                {
                    MessageBox.Show("产品更新成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    DialogResult = DialogResult.OK;
                    Close();
                }
                else
                {
                    MessageBox.Show("更新产品失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"更新产品失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private async void btnDelete_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("确定要删除这个产品吗?", "确认", 
                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                try
                {
                    int productId = (int)_productRow["ProductId"];
                    int result = await _dbHelper.DeleteProductAsync(productId);
                    if (result > 0)
                    {
                        MessageBox.Show("产品删除成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        DialogResult = DialogResult.OK;
                        Close();
                    }
                    else
                    {
                        MessageBox.Show("删除产品失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"删除产品失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
    }
}

2.4 分类管理窗体 (CategoryForm.cs)

using ProductManagementApp.Data;

namespace ProductManagementApp
{
    public partial class CategoryForm : Form
    {
        private readonly DatabaseHelper _dbHelper;

        public CategoryForm(DatabaseHelper dbHelper)
        {
            InitializeComponent();
            _dbHelper = dbHelper;
        }

        private async void btnSave_Click(object sender, EventArgs e)
        {
            try
            {
                int result = await _dbHelper.AddCategoryAsync(txtName.Text, txtDescription.Text);
                if (result > 0)
                {
                    MessageBox.Show("分类添加成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    DialogResult = DialogResult.OK;
                    Close();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"添加分类失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

2.5 订单处理窗体 (OrderForm.cs)

namespace ProductManagementApp
{
    public partial class OrderForm : Form
    {
        public int Quantity => (int)nudQuantity.Value;

        public OrderForm(int productId, int currentStock)
        {
            InitializeComponent();
            lblProductId.Text = productId.ToString();
            lblStock.Text = currentStock.ToString();
            nudQuantity.Maximum = currentStock;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            if (nudQuantity.Value > 0)
            {
                DialogResult = DialogResult.OK;
                Close();
            }
            else
            {
                MessageBox.Show("请输入有效的数量", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }
    }
}

3. 主要特点

  1. 纯ADO.NET实现:

    • 完全使用DataSet/DataTable进行数据操作
    • 使用SqlDataAdapter进行数据填充和更新
    • 利用SqlCommandBuilder自动生成CRUD命令
  2. 断开式数据访问:

    • 数据加载到内存后断开连接
    • 修改后批量提交更改
  3. 数据绑定:

    • DataGridView直接绑定到DataView
    • ComboBox绑定到DataTable
  4. 事务处理:

    • 订单处理演示完整的事务操作
    • 使用SqlTransaction确保数据一致性
  5. 异步编程:

    • 所有数据库操作都使用异步方法
    • 避免UI线程阻塞

4. 与传统ADO.NET的区别

  1. 现代异步API:

    • 使用ExecuteNonQueryAsync等异步方法
    • SqlDataAdapter新增异步方法
  2. 连接管理:

    • 使用await using确保资源释放
    • 自动连接池管理
  3. 安全增强:

    • 默认推荐加密连接
    • 参数化查询防止SQL注入

这个示例展示了如何在.NET 8 WinForms应用程序中完全不依赖Dapper等ORM,直接使用ADO.NET DataSet进行数据库操作,同时保持了现代编程实践如异步编程和依赖注入。

总结

本文全面介绍了在C# WinForms应用程序中使用数据库的技术和方法,重点聚焦于ADO.NET和Entity Framework Core两大主流技术栈。首先,文章详细阐述了ADO.NET的核心组件和使用方法,包括连接数据库、执行命令、数据读取、数据适配器与数据集的使用等,并提供了性能优化和安全实践的建议。接着,文章深入探讨了Entity Framework Core的安装、使用以及与ADO.NET的对比,展示了ORM框架在简化开发中的优势。此外,文章还介绍了如何将数据绑定到WinForms控件,如DataGridView,并提供了使用本地数据库SQLite的示例。在.NET 8环境下,文章详细解析了ADO.NET的现代化改进,包括新的连接管理、命令执行增强、数据读取技术、事务处理、安全增强特性以及性能优化技巧,并通过一个完整的WinForms应用程序示例,展示了如何使用ADO.NET进行数据库操作。最后,文章对比了ADO.NET和Entity Framework Core的适用场景,为开发者提供了技术选型的参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2393021.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无人机仿真环境(3维)附项目git链接

项目概述 随着无人机技术在物流、测绘、应急救援等领域的广泛应用&#xff0c;其自主导航、避障算法、路径规划及多机协同等核心技术的研究需求日益迫切。为降低实地测试成本、提高研发效率&#xff0c;本项目旨在构建一个高精度、可扩展的​​无人机三维虚拟仿真环境​​&…

Python 训练营打卡 Day 30-模块和库的导入

模块和库的导入 1.1标准导入 import mathprint("方式1: 使用 import math") print(f"圆周率π的值: {math.pi}") print(f"2的平方根: {math.sqrt(2)}\n") 1.2从库中导入特定项 from math import pi, sqrtprint("方式2&#xff1a;使用 f…

前端实现图片压缩:基于 HTML5 File API 与 Canvas 的完整方案

在 Web 开发中,处理用户上传的图片时,前端压缩可以有效减少服务器压力并提升上传效率。本文将详细讲解如何通过<input type="file">实现图片上传,结合 Canvas 实现图片压缩,并实时展示压缩前后的图片预览和文件大小对比。 一、核心功能架构 我们将实现以…

【Docker管理工具】部署Docker管理面板DweebUI

【Docker管理工具】部署Docker管理面板DweebUI 一、DweebUI介绍1.1 DweebUI 简介1.2 主要特点1.3 使用场景 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载DweebUI镜像五、…

【后端高阶面经:架构篇】50、数据存储架构:如何改善系统的数据存储能力?

一、数据存储架构设计核心原则 (一)分层存储架构:让数据各得其所 根据数据访问频率和价值,将数据划分为热、温、冷三层,匹配不同存储介质,实现性能与成本的平衡。 热数据层:访问频率>100次/秒。采用Redis集群存储高频访问数据(如用户登录态、实时交易数据),配合…

Qt/C++开发监控GB28181系统/sip协议/同时支持udp和tcp模式/底层协议解析

一、前言说明 在gb28181-2011协议中&#xff0c;只有udp要求&#xff0c;从2016版本开始要求支持tcp&#xff0c;估计也是在多年的实际运行过程中&#xff0c;发现有些网络环境差的场景下&#xff0c;一些udp交互指令丢失导致功能异常&#xff0c;所以后面修订的时候增加了tcp…

晨控CK-FR03与汇川H5U系列PLC配置MODBUS TCP通讯连接操作手册

晨控CK-FR03与汇川H5U系列PLC配置MODBUS TCP通讯连接操作手册 CK-FR03-TCP是一款基于射频识别技术的高频RFID标签读卡器&#xff0c;读卡器工作频率为13.56MHZ&#xff0c;支持对I-CODE 2、I-CODE SLI等符合ISO15693国际标准协议格式标签的读取。 读卡器同时支持标准工业通讯协…

t014-项目申报管理系统 【springBoot 含源码】

项目演示视频 摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;项目信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行…

Go语言开发的GMQT物联网MQTT消息服务器(mqtt Broker)支持海量MQTT连接和快速低延时消息传输-提供源码可二次开发定制需求

关于GMQT物联网MQTT消息平台 GoFly社区推出《GMQT物联网MQTT消息平台》&#xff0c;完全使用高性能的Go语言编写&#xff0c;内嵌数据库(不依赖三方库)&#xff0c; 全面支持MQTT的v3.0.0、v3.1.1以及完全兼容 MQTT v5 功能。利用Go语言高并发性、高效利用服务器资源、跨平台支…

leetcode2221. 数组的三角和-medium

1 题目&#xff1a;数组的三角和 官方标定难度&#xff1a;中 给你一个下标从 0 开始的整数数组 nums &#xff0c;其中 nums[i] 是 0 到 9 之间&#xff08;两者都包含&#xff09;的一个数字。 nums 的 三角和 是执行以下操作以后最后剩下元素的值&#xff1a; nums 初始…

Express教程【001】:Express创建基本的Web服务器

文章目录 1、初识express1.1 什么是Express1.2 主要特点1.3 Express的基本使用1.3.1 安装1.3.2 创建基本的Web服务器 1、初识express 目标&#xff1a; 能够使用express.static()快速托管静态资源能够使用express路由精简项目结构能够使用常见的express中间件能够使用express创…

asio之async_result

简介 async_result用来表示异步处理返回类型 async_result 是类模板 type&#xff1a;为类模板中声明的类型&#xff0c;对于不同的类型&#xff0c;可以使用类模板特例化&#xff0c;比如针对use_future

代码随想录算法训练营 Day60 图论Ⅹ Bellmen_ford 系列算法

图论 题目 94. 城市间货物运输 I Bellmen_ford 队列优化算法 SPFA 大家可以发现 Bellman_ford 算法每次松弛 都是对所有边进行松弛。 但真正有效的松弛&#xff0c;是基于已经计算过的节点在做的松弛。 本图中&#xff0c;对所有边进行松弛&#xff0c;真正有效的松弛&#…

独立机构软件第三方检测:流程、需求分析及电商软件检验要点?

独立机构承担着对软件进行第三方检测的任务&#xff0c;这一环节对于提升软件的质量和稳定性起到了极其关键的作用。检测过程拥有一套完善的流程&#xff0c;目的在于确保检测结果的精确性&#xff0c;并保障软件达到高标准。 需求分析 确保软件测试需求清晰十分关键。我们需…

华为FreeArc能和其他华为产品共用充电线吗?

最近刚买的FreeArc终于到手啦&#xff0c;看到网上有朋友说&#xff0c;这次的耳机是不附带充电线&#xff0c;开箱后发现果真如此&#xff0c;那FreeArc到底用什么规格的充电线&#xff0c;能不能和华为的Type-C数据线通用&#xff0c;我来给大家解答一下吧&#xff01; Free…

[网页五子棋][匹配模式]创建房间类、房间管理器、验证匹配功能,匹配模式小结

文章目录 创建房间类创建房间类实现房间管理器 实现匹配器(3)验证匹配功能问题&#xff1a;匹配按钮不改变验证多开 小结 创建房间类 LOL&#xff0c;通过匹配的方式&#xff0c;自动给你加入到一个房间&#xff0c;也可手动创建游戏房间 这一局游戏&#xff0c;进行的“场所…

实验设计与分析(第6版,Montgomery)第3章单因子实验:方差分析3.11思考题3.7 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第3章单因子实验&#xff1a;方差分析3.11思考题3.7 R语言解题。主要涉及单因子方差分析&#xff0c;正态性假设检验&#xff0c;残差与拟合值的关系图&#xff0c;平方根变换。 X<-c(…

【知识点】第2章:Python程序实例解析

文章目录 知识点整理Python程序语法元素分析 练习题判断题填空题选择题 知识点整理 Python程序语法元素分析 Python程序包括格式框架、注释、变量、表达式、分支语句、循环语句、函数等语法元素。 程序的格式框架 Python语言采用严格的 “缩进” 来表明程序的格式框架。缩进…

每日Prompt:指尖做画

提示词 微缩景观&#xff0c;微距摄影&#xff0c;俯瞰角度&#xff0c;特写&#xff0c;硕大食指手指甲&#xff0c;一个小小的人正在做画&#xff0c;小人右手拿画笔&#xff0c;小人左手拿调色盘&#xff0c;在指甲上作画&#xff0c;画的是中国古代山水画&#xff0c;背景…

redis未授权(CVE-2022-0543)

概述 Redis 默认绑定在 0.0.0.0:6379&#xff0c;在未配置防火墙或访问控制的情况下会将服务暴露在公网上。若未设置访问密码&#xff08;默认通常为空&#xff09;&#xff0c;攻击者可直接未授权访问 Redis。利用 Redis 提供的 CONFIG 命令&#xff0c;攻击者可修改配置并将…