1. 项目概述与开发环境配置
在VS2015环境下使用C#进行数据库操作和Excel报表处理,是企业级应用开发中的常见需求。这个技术组合特别适合需要快速生成业务报表的中小型管理系统。我最近在一个库存管理系统中实际应用了这套技术栈,发现虽然现在有更新的工具可用,但VS2015+Office2013的组合依然稳定可靠,特别适合需要长期维护的遗留系统升级。
1.1 开发环境准备
首先需要确保开发环境正确配置:
- 安装Visual Studio 2015(建议使用Professional或以上版本)
- 安装Office 2013完整版(必须包含Excel组件)
- 对于SQL Server开发,建议安装SQL Server 2014 Express版
- 对于Access开发,需要安装Access Database Engine
注意:Office 2013的版本号对应的是15.0,这在后续添加COM引用时需要特别注意。如果系统安装了多个Office版本,可能会出现组件冲突问题。
1.2 项目引用配置
在VS2015中创建C#项目后,需要添加以下关键引用:
- 对于SQL Server操作:System.Data.SqlClient(通常已默认包含)
- 对于Access操作:System.Data.OleDb(通常已默认包含)
- 对于Excel操作:需要通过COM选项卡添加"Microsoft Excel 15.0 Object Library"
添加Excel引用时常见的坑是版本不匹配。我曾经遇到过因为安装了Office更新导致版本号变化的情况,解决方案是:
- 在解决方案资源管理器中右键项目
- 选择"添加"→"引用"
- 切换到"COM"选项卡
- 查找并选中"Microsoft Excel 15.0 Object Library"
- 如果找不到,可以尝试浏览到Office安装目录下的EXCEL.EXE文件
2. 数据库连接与操作实战
2.1 SQL Server数据库连接最佳实践
连接SQL Server时,除了基本的连接字符串配置,还有一些安全性考虑和性能优化技巧:
csharp复制using System.Data.SqlClient;
string connectionString = "Server=YOUR_SERVER;Database=YOUR_DB;User ID=YOUR_USER;Password=YOUR_PWD;";
// 更安全的做法是使用集成安全认证:
// string connectionString = "Server=YOUR_SERVER;Database=YOUR_DB;Integrated Security=True;";
using (SqlConnection conn = new SqlConnection(connectionString))
{
try
{
conn.Open();
// 使用参数化查询防止SQL注入
string sql = "SELECT * FROM Products WHERE CategoryID = @CategoryID";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CategoryID", 5);
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"Product: {reader["ProductName"]}, Price: {reader["UnitPrice"]}");
}
}
}
}
catch (SqlException ex)
{
// 更详细的错误处理
Console.WriteLine($"SQL Error {ex.Number}: {ex.Message}");
}
finally
{
// using语句会自动关闭连接,这里只是演示
if (conn.State != ConnectionState.Closed)
conn.Close();
}
}
实际项目中我发现几个关键点:
- 连接字符串应该存储在配置文件中,而不是硬编码
- 使用参数化查询是防止SQL注入的基本要求
- 不同的SQL Server版本可能需要不同的连接字符串格式
- 连接池默认是开启的,但可以配置Max Pool Size等参数优化性能
2.2 Access数据库连接的特殊处理
Access数据库连接虽然简单,但在实际部署时经常会遇到权限问题:
csharp复制using System.Data.OleDb;
string dbPath = @"C:\Data\Inventory.accdb";
string connectionString = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath};";
// 对于旧版.mdb文件使用:
// string connectionString = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={dbPath};";
using (OleDbConnection conn = new OleDbConnection(connectionString))
{
try
{
conn.Open();
// Access的SQL语法有些特殊
string sql = "SELECT * FROM [Products] WHERE [Discontinued] = False";
using (OleDbCommand cmd = new OleDbCommand(sql, conn))
{
using (OleDbDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"{reader["ProductCode"]}: {reader["Description"]}");
}
}
}
}
catch (OleDbException ex)
{
Console.WriteLine($"Access数据库错误: {ex.Message}");
}
}
Access开发中常见的问题包括:
- 64位系统需要安装正确的Access Database Engine
- 文件路径权限问题(特别是部署到IIS时)
- 表名或字段名含空格时需要加方括号
- 并发访问性能较差,不适合高负载场景
3. 高级数据库操作技巧
3.1 事务处理实战
在需要保证数据一致性的操作中,事务处理是关键:
csharp复制using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlTransaction transaction = conn.BeginTransaction();
try
{
// 更新库存
string updateStock = "UPDATE Products SET Stock = Stock - @Quantity WHERE ProductID = @ProductID";
using (SqlCommand cmd = new SqlCommand(updateStock, conn, transaction))
{
cmd.Parameters.AddWithValue("@ProductID", 101);
cmd.Parameters.AddWithValue("@Quantity", 5);
int affected = cmd.ExecuteNonQuery();
if (affected == 0)
throw new Exception("产品不存在或库存不足");
}
// 记录交易
string insertLog = "INSERT INTO Transactions (ProductID, Quantity, Date) VALUES (@ProductID, @Quantity, @Date)";
using (SqlCommand cmd = new SqlCommand(insertLog, conn, transaction))
{
cmd.Parameters.AddWithValue("@ProductID", 101);
cmd.Parameters.AddWithValue("@Quantity", 5);
cmd.Parameters.AddWithValue("@Date", DateTime.Now);
cmd.ExecuteNonQuery();
}
transaction.Commit();
Console.WriteLine("事务完成");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"事务回滚: {ex.Message}");
}
}
3.2 批量数据操作
对于大量数据操作,批量处理可以显著提高性能:
csharp复制// 使用SqlBulkCopy快速导入数据
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString))
{
bulkCopy.DestinationTableName = "Products";
// 列映射确保数据正确导入
bulkCopy.ColumnMappings.Add("Code", "ProductCode");
bulkCopy.ColumnMappings.Add("Name", "ProductName");
bulkCopy.ColumnMappings.Add("Price", "UnitPrice");
// 假设dataTable是从其他数据源获取的数据
bulkCopy.WriteToServer(dataTable);
}
4. Excel报表导出高级技巧
4.1 基本数据导出
csharp复制using Excel = Microsoft.Office.Interop.Excel;
var excelApp = new Excel.Application();
excelApp.Visible = true; // 调试时可见
try
{
Excel.Workbook workbook = excelApp.Workbooks.Add();
Excel.Worksheet worksheet = (Excel.Worksheet)workbook.Sheets[1];
// 设置标题行
worksheet.Cells[1, 1] = "产品编号";
worksheet.Cells[1, 2] = "产品名称";
worksheet.Cells[1, 3] = "单价";
// 从数据库获取数据
var products = GetProductsFromDatabase();
// 填充数据
for (int i = 0; i < products.Count; i++)
{
worksheet.Cells[i + 2, 1] = products[i].Code;
worksheet.Cells[i + 2, 2] = products[i].Name;
worksheet.Cells[i + 2, 3] = products[i].Price;
}
// 自动调整列宽
worksheet.Columns.AutoFit();
// 保存文件
string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "产品列表.xlsx");
workbook.SaveAs(filePath);
}
finally
{
excelApp.Quit();
ReleaseObject(excelApp);
}
4.2 高级格式设置
csharp复制// 添加边框
Excel.Range range = worksheet.Range["A1:C" + (products.Count + 1)];
range.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
// 设置标题样式
Excel.Range header = worksheet.Range["A1:C1"];
header.Font.Bold = true;
header.Interior.Color = Excel.XlRgbColor.rgbLightGray;
// 设置数字格式
Excel.Range priceColumn = worksheet.Range["C2:C" + (products.Count + 1)];
priceColumn.NumberFormat = "¥#,##0.00";
// 添加条件格式
Excel.FormatCondition condition = (Excel.FormatCondition)range.FormatConditions.Add(
Excel.XlFormatConditionType.xlCellValue,
Excel.XlFormatConditionOperator.xlGreater,
100);
condition.Interior.Color = Excel.XlRgbColor.rgbLightYellow;
4.3 性能优化技巧
处理大量数据时,直接操作单元格会很慢。可以采用以下优化方法:
csharp复制// 方法1:使用数组一次性写入
object[,] data = new object[products.Count, 3];
for (int i = 0; i < products.Count; i++)
{
data[i, 0] = products[i].Code;
data[i, 1] = products[i].Name;
data[i, 2] = products[i].Price;
}
Excel.Range startCell = (Excel.Range)worksheet.Cells[2, 1];
Excel.Range endCell = (Excel.Range)worksheet.Cells[products.Count + 1, 3];
worksheet.Range[startCell, endCell].Value2 = data;
// 方法2:关闭屏幕更新
excelApp.ScreenUpdating = false;
// 执行导出操作...
excelApp.ScreenUpdating = true;
5. Excel打印功能实现
5.1 基本打印设置
csharp复制Excel.Worksheet worksheet = ... // 获取工作表引用
// 设置打印区域
worksheet.PageSetup.PrintArea = "A1:C20";
// 设置页面方向
worksheet.PageSetup.Orientation = Excel.XlPageOrientation.xlLandscape;
// 设置页边距(单位:英寸)
worksheet.PageSetup.LeftMargin = 0.5;
worksheet.PageSetup.RightMargin = 0.5;
worksheet.PageSetup.TopMargin = 0.75;
worksheet.PageSetup.BottomMargin = 0.75;
// 设置居中方式
worksheet.PageSetup.CenterHorizontally = true;
// 直接打印
worksheet.PrintOut(
Copies: 1,
Preview: false, // true为打印预览
ActivePrinter: "默认打印机",
PrintToFile: false);
5.2 高级打印功能
csharp复制// 设置页眉页脚
worksheet.PageSetup.CenterHeader = "&\"Arial,Bold\"&12销售报表";
worksheet.PageSetup.RightFooter = "第&P页/共&N页";
// 设置打印标题行
worksheet.PageSetup.PrintTitleRows = "$1:$1"; // 第一行作为标题行每页重复
// 设置缩放比例
worksheet.PageSetup.Zoom = 90; // 90%缩放
// 设置每页打印的行数
worksheet.PageSetup.FitToPagesTall = 10; // 每页最多10行
// 打印网格线
worksheet.PageSetup.PrintGridlines = true;
6. 常见问题与解决方案
6.1 数据库连接问题
问题1:SQL Server连接超时
- 现象:连接时抛出Timeout异常
- 解决方案:
- 增加连接字符串中的Connect Timeout参数(默认15秒)
csharp复制string connStr = "Server=...;Connect Timeout=30;";- 检查网络连接和SQL Server服务状态
- 检查防火墙设置
问题2:Access数据库锁定
- 现象:多用户访问时出现锁定文件
- 解决方案:
- 将数据库文件放在有写权限的共享文件夹
- 考虑升级到SQL Server Express
- 使用拆分数据库架构(前端/后端)
6.2 Excel操作问题
问题1:Excel进程无法退出
- 现象:程序结束后Excel进程仍在运行
- 解决方案:
csharp复制// 确保正确释放COM对象
private static void ReleaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch
{
obj = null;
}
finally
{
GC.Collect();
}
}
// 使用方式
Excel.Application excelApp = new Excel.Application();
try
{
// 操作代码...
}
finally
{
excelApp.Quit();
ReleaseObject(excelApp);
}
问题2:权限不足
- 现象:保存文件时抛出权限异常
- 解决方案:
- 检查目标文件夹的写入权限
- 使用临时文件夹作为中间存储
- 处理文件被占用的情况
6.3 性能优化建议
- 对于频繁的数据库操作,考虑使用连接池
- 大量数据导出到Excel时,使用数组批量写入
- 避免在循环中频繁访问Excel单元格
- 考虑使用EPPlus等第三方库替代Interop(不需要安装Office)
- 对于只读操作,使用SqlDataReader而不是DataSet
7. 项目部署注意事项
7.1 环境依赖
- 目标机器必须安装相应版本的Office(2013)
- 对于Access数据库操作,需要安装Access Database Engine
- 32位与64位兼容性问题:
- 如果使用Any CPU编译,在64位系统上可能出错
- 建议明确指定平台目标(x86通常兼容性更好)
7.2 配置文件管理
推荐将连接字符串等配置信息存储在App.config中:
xml复制<configuration>
<connectionStrings>
<add name="InventoryDB"
connectionString="Server=.;Database=Inventory;Integrated Security=True;"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
代码中读取方式:
csharp复制string connStr = ConfigurationManager.ConnectionStrings["InventoryDB"].ConnectionString;
7.3 错误处理与日志记录
实现完善的错误处理和日志记录:
csharp复制try
{
// 数据库操作...
}
catch (SqlException ex)
{
Logger.Error($"数据库错误: {ex.Message}", ex);
MessageBox.Show("数据库操作出错,请查看日志");
}
catch (Exception ex)
{
Logger.Error($"系统错误: {ex.Message}", ex);
MessageBox.Show("系统发生错误,请联系管理员");
}
8. 替代方案与扩展思路
8.1 不使用Office Interop的方案
如果目标机器没有安装Office,可以考虑:
- EPPlus库(开源,支持.xlsx格式)
csharp复制using (ExcelPackage pck = new ExcelPackage())
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Sheet1");
ws.Cells["A1"].Value = "产品列表";
// ...填充数据
pck.SaveAs(new FileInfo("Report.xlsx"));
}
-
NPOI库(支持.xls和.xlsx格式)
-
导出为CSV(最简单,但功能有限)
8.2 数据库访问的现代方案
- Entity Framework:ORM框架,简化数据访问
- Dapper:轻量级ORM,性能优异
- Repository模式:更好的架构设计
8.3 报表生成的进阶方向
- 使用报表工具:如Microsoft RDLC、DevExpress等
- Web报表:通过ASP.NET生成HTML报表
- PDF导出:使用iTextSharp等库生成PDF报表
在实际项目中,我通常会根据客户环境和需求选择合适的技术组合。对于需要长期维护的系统,保持技术栈的简洁和稳定往往比追求最新技术更重要。