A:Access接口增加可加密选项

This commit is contained in:
jimmy committed 2025-11-19 18:12:11 +08:00
1 parent dd993e5b45
commit 2739a71134
12 files changed
+469 -252

No files matched your search

+125 -79
View File
@@ -1,8 +1,12 @@
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Sharp.Data.Access.Connections;
using Sharp.Data.Access.Initialization;
using Sharp.Data.Access.Internal;
using Sharp.Data.Access.Options;
using Sharp.Data.Access.Security;
namespace Sharp.Data.Access;
@@ -14,26 +18,27 @@ internal class AccessTransaction : IAccessTransaction
private readonly OleDbConnection _connection;
private readonly OleDbTransaction _transaction;
private readonly ILogger? _logger;
private readonly bool _ownsConnection;
private bool _disposed;
public AccessTransaction(OleDbConnection connection, ILogger? logger)
public AccessTransaction(OleDbConnection connection, bool ownsConnection, ILogger? logger)
{
_connection = connection;
_ownsConnection = ownsConnection;
_transaction = connection.BeginTransaction();
_logger = logger;
}
public Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters)
public int Execute(string sql, params OleDbParameter[] parameters)
{
using var command = new OleDbCommand(sql, _connection, _transaction);
if (parameters.Length > 0)
command.Parameters.AddRange(parameters);
var result = command.ExecuteNonQuery();
return Task.FromResult(result);
return command.ExecuteNonQuery();
}
public Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new()
public List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new()
{
using var command = new OleDbCommand(sql, _connection, _transaction);
if (parameters.Length > 0)
@@ -48,17 +53,16 @@ internal class AccessTransaction : IAccessTransaction
results.Add(item);
}
return Task.FromResult(results);
return results;
}
public Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters)
public object? ExecuteScalar(string sql, params OleDbParameter[] parameters)
{
using var command = new OleDbCommand(sql, _connection, _transaction);
if (parameters.Length > 0)
command.Parameters.AddRange(parameters);
var result = command.ExecuteScalar();
return Task.FromResult(result);
return command.ExecuteScalar();
}
public void Commit()
@@ -77,8 +81,18 @@ internal class AccessTransaction : IAccessTransaction
{
if (_disposed) return;
_transaction.Dispose();
_connection.Dispose();
try
{
_transaction.Dispose();
}
finally
{
if (_ownsConnection)
{
_connection.Dispose();
}
}
_disposed = true;
}
@@ -144,34 +158,65 @@ internal class AccessTransaction : IAccessTransaction
}
/// <summary>
/// Access 数据服务实现
/// Access 数据服务实现(基于 System.Data.OleDb)
/// </summary>
public class AccessDataService : IAccessDataService
{
private readonly AccessDataServiceOptions _options;
private readonly AccessDatabaseOptions _options;
private readonly IAccessConnectionStringFactory _connectionFactory;
private readonly IAccessDatabaseInitializer _initializer;
private readonly IAccessPasswordDecryptor _decryptor;
private readonly ILogger<AccessDataService>? _logger;
private readonly string _connectionString;
public AccessDataService(AccessDataServiceOptions options, ILogger<AccessDataService>? logger = null)
public AccessDataService(
AccessDatabaseOptions options,
IAccessConnectionStringFactory? connectionFactory = null,
IAccessDatabaseInitializer? initializer = null,
IAccessPasswordDecryptor? decryptor = null,
ILogger<AccessDataService>? logger = null)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_options.Validate();
_connectionFactory = connectionFactory ?? new AccessConnectionStringFactory(decryptor ?? new PassthroughAccessPasswordDecryptor());
_initializer = initializer ?? new AccessDatabaseInitializer();
_decryptor = decryptor ?? new PassthroughAccessPasswordDecryptor();
_logger = logger;
if (string.IsNullOrEmpty(_options.DatabasePath))
try
{
throw new ArgumentException("数据库路径不能为空", nameof(options.DatabasePath));
}
// 确保数据库存在(如果配置了模板复制)
_initializer.EnsureDatabase(_options, NullServiceProvider.Instance);
if (!File.Exists(_options.DatabasePath))
// 构建连接字符串
_connectionString = _connectionFactory.Create(_options, NullServiceProvider.Instance);
_logger?.LogInformation("AccessDataService 已初始化,数据库: {DatabaseFile}, Provider: {Provider}",
_options.DatabaseFile, _options.ProviderName);
_logger?.LogDebug("实际连接字符串: {ConnectionString}", _connectionString);
}
catch (Exception ex)
{
throw new FileNotFoundException($"找不到 Access 数据库文件: {_options.DatabasePath}");
_logger?.LogError(ex, "AccessDataService 初始化失败");
// 提供详细的错误诊断信息
if (ex.Message.Contains("找不到") || ex is FileNotFoundException)
{
throw new InvalidOperationException(
$"找不到 Access 数据库文件: {_options.DatabaseFile}。" +
$"请确保文件存在或配置 TemplateFile 以自动创建。", ex);
}
throw;
}
}
public async Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new()
public List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new()
{
_logger?.LogDebug("执行查询: {Sql}", sql);
return await Task.Run(() =>
return ExecuteWithRetry(() =>
{
using var connection = CreateConnection();
connection.Open();
@@ -183,29 +228,8 @@ public class AccessDataService : IAccessDataService
var results = new List<T>();
using var reader = command.ExecuteReader();
LogFieldMap(reader);
if (_logger?.IsEnabled(LogLevel.Information) == true)
{
var columns = Enumerable.Range(0, reader.FieldCount)
.Select(i => $"{reader.GetName(i)}(idx:{i})")
.ToArray();
_logger.LogInformation("查询列: {Columns}", string.Join(", ", columns));
}
var rowIndex = 0;
while (reader.Read())
{
rowIndex++;
if (_logger?.IsEnabled(LogLevel.Information) == true)
{
var fields = Enumerable.Range(0, reader.FieldCount)
.Select(i => $"{reader.GetName(i)}='{SafeGetValue(reader, i)}'")
.ToArray();
_logger.LogInformation("Row {RowIndex}: {Fields}", rowIndex, string.Join(", ", fields));
}
var item = MapReaderToObject<T>(reader);
results.Add(item);
}
@@ -215,11 +239,11 @@ public class AccessDataService : IAccessDataService
});
}
public async Task<T?> QueryFirstOrDefaultAsync<T>(string sql, params OleDbParameter[] parameters) where T : new()
public T? QueryFirstOrDefault<T>(string sql, params OleDbParameter[] parameters) where T : new()
{
_logger?.LogDebug("执行单条查询: {Sql}", sql);
return await Task.Run(() =>
return ExecuteWithRetry(() =>
{
using var connection = CreateConnection();
connection.Open();
@@ -230,14 +254,6 @@ public class AccessDataService : IAccessDataService
using var reader = command.ExecuteReader();
if (_logger?.IsEnabled(LogLevel.Debug) == true)
{
var columns = Enumerable.Range(0, reader.FieldCount)
.Select(reader.GetName)
.ToArray();
_logger.LogDebug("查询列: {Columns}", string.Join(", ", columns));
}
if (reader.Read())
{
return MapReaderToObject<T>(reader);
@@ -247,11 +263,11 @@ public class AccessDataService : IAccessDataService
});
}
public async Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters)
public object? ExecuteScalar(string sql, params OleDbParameter[] parameters)
{
_logger?.LogDebug("执行标量查询: {Sql}", sql);
return await Task.Run(() =>
return ExecuteWithRetry(() =>
{
using var connection = CreateConnection();
connection.Open();
@@ -266,11 +282,11 @@ public class AccessDataService : IAccessDataService
});
}
public async Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters)
public int Execute(string sql, params OleDbParameter[] parameters)
{
_logger?.LogDebug("执行命令: {Sql}", sql);
return await Task.Run(() =>
return ExecuteWithRetry(() =>
{
using var connection = CreateConnection();
connection.Open();
@@ -290,17 +306,17 @@ public class AccessDataService : IAccessDataService
_logger?.LogDebug("开始事务");
var connection = CreateConnection();
connection.Open();
return new AccessTransaction(connection, _logger);
return new AccessTransaction(connection, ownsConnection: true, _logger);
}
public async Task ExecuteInTransactionAsync(Func<IAccessTransaction, Task> action)
public void ExecuteInTransaction(Action<IAccessTransaction> action)
{
_logger?.LogDebug("在事务中执行操作");
using var transaction = BeginTransaction();
try
{
await action(transaction);
action(transaction);
transaction.Commit();
_logger?.LogDebug("事务执行成功");
}
@@ -312,13 +328,13 @@ public class AccessDataService : IAccessDataService
}
}
public async Task<bool> TestConnectionAsync()
public bool TestConnection()
{
try
{
_logger?.LogDebug("测试数据库连接");
using var connection = CreateConnection();
await connection.OpenAsync();
connection.Open();
_logger?.LogDebug("数据库连接成功");
return true;
}
@@ -331,30 +347,60 @@ public class AccessDataService : IAccessDataService
private OleDbConnection CreateConnection()
{
return new OleDbConnection(_options.ConnectionString);
return new OleDbConnection(_connectionString);
}
private void LogFieldMap(IDataReader reader)
private T ExecuteWithRetry<T>(Func<T> operation)
{
if (!(_logger?.IsEnabled(LogLevel.Information) ?? false)) return;
var maxRetries = _options.MaxRetryCount;
var retryDelay = _options.RetryDelayMilliseconds;
var columns = Enumerable.Range(0, reader.FieldCount)
.Select(i => $"'{reader.GetName(i)}'->idx:{i}")
.ToArray();
_logger!.LogInformation("字段映射: {Columns}", string.Join(", ", columns));
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
return operation();
}
catch (OleDbException ex) when (attempt < maxRetries && IsRetriableError(ex))
{
_logger?.LogWarning(ex, "数据库操作失败,正在重试 ({Attempt}/{MaxRetries})...", attempt + 1, maxRetries);
Thread.Sleep(retryDelay);
}
catch (OleDbException ex)
{
// 提供详细的错误诊断信息
var errorMessage = $"Access 数据库操作失败: {ex.Message}";
// 检查是否是 Provider 相关的错误
if (ex.Message.Contains("找不到") || ex.Message.Contains("未注册") ||
ex.Message.Contains("OLE DB") || ex.Message.Contains("多步"))
{
errorMessage += $"\n\n可能的原因:" +
$"\n1. 未安装 Access Database Engine:当前使用 Provider = {_options.ProviderName}" +
$"\n - 如果使用 16.0,需要安装 Access Database Engine 2016 或 Office 2016+" +
$"\n - 如果使用 12.0,需要安装 Access Database Engine 2010 或 Office 2010+" +
$"\n - 建议:优先使用 12.0 版本,兼容性最好" +
$"\n2. 32位/64位不匹配:进程架构必须与 Access Driver 架构一致" +
$"\n3. 数据库文件损坏或被其他程序锁定" +
$"\n4. 数据库文件路径不正确:{_options.DatabaseFile}";
_logger?.LogError(ex, "数据库连接失败,Provider: {Provider}", _options.ProviderName);
}
throw new InvalidOperationException(errorMessage, ex);
}
}
// 不会到这里,因为最后一次尝试会抛出异常
throw new InvalidOperationException("重试逻辑错误");
}
private static object? SafeGetValue(IDataRecord reader, int ordinal)
private static bool IsRetriableError(OleDbException ex)
{
try
{
return reader.GetValue(ordinal);
}
catch
{
// 某些 OleDb 列类型在获取值时可能抛 COM 异常,这里做容错日志使用
return "<unavailable>";
}
// Access 数据库常见的可重试错误
// -2147467259: 数据库被锁定
// -2147217865: 无法打开数据库(可能正在被其他进程使用)
return ex.ErrorCode == -2147467259 || ex.ErrorCode == -2147217865;
}
private static T MapReaderToObject<T>(IDataReader reader) where T : new()
@@ -1,4 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Sharp.Data.Access.Options;
using Sharp.Data.Access.Security;
using Sharp.Data.Access.Connections;
using Sharp.Data.Access.Initialization;
namespace Sharp.Data.Access;
@@ -8,25 +13,63 @@ namespace Sharp.Data.Access;
public static class AccessDataServiceExtensions
{
/// <summary>
/// 添加 Access 数据服务
/// 添加 Access 数据服务(最简单方式 - 仅指定数据库路径)
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="databasePath">数据库文件路径</param>
/// <param name="provider">OLE DB 提供程序(默认:Microsoft.ACE.OLEDB.12.0)</param>
/// <param name="databasePath">数据库文件路径(绝对或相对路径)</param>
/// <param name="password">数据库密码(可选)</param>
/// <param name="provider">OLE DB 提供程序,默认:Microsoft.ACE.OLEDB.16.0</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddAccessDataService(
this IServiceCollection services,
string databasePath,
string provider = "Microsoft.ACE.OLEDB.12.0")
string? password = null,
string? provider = null)
{
var options = new AccessDataServiceOptions
return services.AddAccessDataService(options =>
{
DatabasePath = databasePath,
Provider = provider
};
options.DatabaseFile = databasePath;
if (!string.IsNullOrEmpty(password))
options.Password = password;
if (!string.IsNullOrEmpty(provider))
options.ProviderName = provider;
});
}
/// <summary>
/// 添加 Access 数据服务(使用配置委托)
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configureOptions">配置委托</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddAccessDataService(
this IServiceCollection services,
Action<AccessDatabaseOptions> configureOptions)
{
if (services == null) throw new ArgumentNullException(nameof(services));
if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));
var options = new AccessDatabaseOptions();
configureOptions(options);
options.Validate();
// 注册核心服务
services.TryAddSingleton<IAccessPasswordDecryptor, PassthroughAccessPasswordDecryptor>();
services.TryAddSingleton<IAccessConnectionStringFactory>(sp =>
new AccessConnectionStringFactory(sp.GetRequiredService<IAccessPasswordDecryptor>()));
services.TryAddSingleton<IAccessDatabaseInitializer, AccessDatabaseInitializer>();
// 注册配置
services.AddSingleton(options);
services.AddSingleton<IAccessDataService, AccessDataService>();
// 注册 AccessDataService
services.AddSingleton<IAccessDataService>(sp =>
new AccessDataService(
options,
sp.GetRequiredService<IAccessConnectionStringFactory>(),
sp.GetRequiredService<IAccessDatabaseInitializer>(),
sp.GetRequiredService<IAccessPasswordDecryptor>(),
sp.GetService<Microsoft.Extensions.Logging.ILogger<AccessDataService>>()));
return services;
}
@@ -39,30 +82,37 @@ public static class AccessDataServiceExtensions
/// <returns>服务集合</returns>
public static IServiceCollection AddAccessDataService(
this IServiceCollection services,
AccessDataServiceOptions options)
AccessDatabaseOptions options)
{
services.AddSingleton(options);
services.AddSingleton<IAccessDataService, AccessDataService>();
if (options == null) throw new ArgumentNullException(nameof(options));
return services.AddAccessDataService(_ => { });
}
/// <summary>
/// 注册自定义密码解密器
/// </summary>
/// <typeparam name="TDecryptor">解密器类型</typeparam>
/// <param name="services">服务集合</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddAccessPasswordDecryptor<TDecryptor>(this IServiceCollection services)
where TDecryptor : class, IAccessPasswordDecryptor
{
services.AddSingleton<IAccessPasswordDecryptor, TDecryptor>();
return services;
}
/// <summary>
/// 添加 Access 数据服务(使用配置委托)
/// 注册自定义密码解密器(使用工厂方法)
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configureOptions">配置委托</param>
/// <param name="implementationFactory">解密器工厂</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddAccessDataService(
public static IServiceCollection AddAccessPasswordDecryptor(
this IServiceCollection services,
Action<AccessDataServiceOptions> configureOptions)
Func<IServiceProvider, IAccessPasswordDecryptor> implementationFactory)
{
var options = new AccessDataServiceOptions();
configureOptions(options);
services.AddSingleton(options);
services.AddSingleton<IAccessDataService, AccessDataService>();
services.AddSingleton(implementationFactory);
return services;
}
}
@@ -1,22 +0,0 @@
namespace Sharp.Data.Access;
/// <summary>
/// Access 数据服务配置选项
/// </summary>
public class AccessDataServiceOptions
{
/// <summary>
/// 数据库文件路径
/// </summary>
public string DatabasePath { get; set; } = string.Empty;
/// <summary>
/// OLE DB 提供程序(默认:Microsoft.ACE.OLEDB.12.0)
/// </summary>
public string Provider { get; set; } = "Microsoft.ACE.OLEDB.12.0";
/// <summary>
/// 完整的连接字符串
/// </summary>
public string ConnectionString => $"Provider={Provider};Data Source={DatabasePath}";
}
@@ -29,6 +29,8 @@ internal sealed class AccessConnectionStringFactory : IAccessConnectionStringFac
throw new InvalidOperationException("Access 数据库需要提供 DatabaseFile 或 ConnectionString。");
}
// 基础连接字符串
// 注意:Connect Timeout 参数在某些 ACE 版本中不稳定,已移除
var builder = new OleDbConnectionStringBuilder
{
["Provider"] = configuration.ProviderName,
+10 -10
View File
@@ -10,17 +10,17 @@ public interface IAccessTransaction : IDisposable
/// <summary>
/// 执行非查询命令(INSERT, UPDATE, DELETE)
/// </summary>
Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters);
int Execute(string sql, params OleDbParameter[] parameters);
/// <summary>
/// 查询多条记录
/// </summary>
Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new();
List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new();
/// <summary>
/// 执行标量查询(COUNT, SUM等)
/// </summary>
Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters);
object? ExecuteScalar(string sql, params OleDbParameter[] parameters);
/// <summary>
/// 提交事务
@@ -34,29 +34,29 @@ public interface IAccessTransaction : IDisposable
}
/// <summary>
/// Access 数据服务接口
/// Access 数据服务接口(基于 System.Data.OleDb)
/// </summary>
public interface IAccessDataService
{
/// <summary>
/// 查询多条记录
/// </summary>
Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new();
List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new();
/// <summary>
/// 查询单条记录
/// </summary>
Task<T?> QueryFirstOrDefaultAsync<T>(string sql, params OleDbParameter[] parameters) where T : new();
T? QueryFirstOrDefault<T>(string sql, params OleDbParameter[] parameters) where T : new();
/// <summary>
/// 执行标量查询(COUNT, SUM等)
/// </summary>
Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters);
object? ExecuteScalar(string sql, params OleDbParameter[] parameters);
/// <summary>
/// 执行非查询命令(INSERT, UPDATE, DELETE)
/// </summary>
Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters);
int Execute(string sql, params OleDbParameter[] parameters);
/// <summary>
/// 开始事务
@@ -66,10 +66,10 @@ public interface IAccessDataService
/// <summary>
/// 在事务中执行操作(自动提交/回滚)
/// </summary>
Task ExecuteInTransactionAsync(Func<IAccessTransaction, Task> action);
void ExecuteInTransaction(Action<IAccessTransaction> action);
/// <summary>
/// 测试数据库连接
/// </summary>
Task<bool> TestConnectionAsync();
bool TestConnection();
}
@@ -12,6 +12,9 @@ internal sealed record AccessDatabaseConfiguration(
string? EncryptedPassword,
string? TemplateFile,
string? DataDirectory,
int ConnectionTimeout,
int MaxRetryCount,
int RetryDelayMilliseconds,
IReadOnlyDictionary<string, string> AdditionalParameters,
bool CopyTemplateWhenMissing,
bool ThrowIfMissing)
@@ -38,6 +41,9 @@ internal sealed record AccessDatabaseConfiguration(
encryptedPassword,
templateFile,
dataDirectory,
options.ConnectionTimeout,
options.MaxRetryCount,
options.RetryDelayMilliseconds,
new Dictionary<string, string>(options.AdditionalParameters, StringComparer.OrdinalIgnoreCase),
options.CopyTemplateWhenMissing,
options.ThrowIfMissing);
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Sharp.Data.Access.Options;
/// <summary>
/// Access 数据库配置项,调用方负责提供所有需要的参数。
/// Access 数据库统一配置项,同时支持 OleDb 和 EF Core 两种方式。
/// </summary>
public class AccessDatabaseOptions
{
@@ -40,7 +40,7 @@ public class AccessDatabaseOptions
public Func<IServiceProvider, string?>? DataDirectoryFactory { get; set; }
/// <summary>
/// 明文密码,可选。
/// 明文密码,可选。用于访问加密的 Access 数据库。
/// </summary>
public string? Password { get; set; }
@@ -77,13 +77,32 @@ public class AccessDatabaseOptions
/// <summary>
/// 当数据库文件缺失(且无法复制)时是否抛出异常。
/// </summary>
public bool ThrowIfMissing { get; set; }
public bool ThrowIfMissing { get; set; } = true;
/// <summary>
/// Provider 名称,默认为 ACE OLE DB 16。
/// Provider 名称,默认为 ACE OLE DB 16.0(功能最完整)。
/// 可选值:
/// - AccessProviderNames.AceOleDb16 (Microsoft.ACE.OLEDB.16.0) - 推荐,需要 Office 2016+
/// - AccessProviderNames.AceOleDb12 (Microsoft.ACE.OLEDB.12.0) - 需要 Office 2010+,不支持 Connect Timeout
/// - AccessProviderNames.JetOleDb4 (Microsoft.Jet.OLEDB.4.0) - 仅支持 32 位和旧版 Access
/// </summary>
public string ProviderName { get; set; } = AccessProviderNames.AceOleDb16;
/// <summary>
/// 连接超时时间(秒),默认 15 秒。
/// </summary>
public int ConnectionTimeout { get; set; } = 15;
/// <summary>
/// 最大重试次数,默认 3 次。用于增强稳健性。
/// </summary>
public int MaxRetryCount { get; set; } = 3;
/// <summary>
/// 重试延迟(毫秒),默认 100 毫秒。
/// </summary>
public int RetryDelayMilliseconds { get; set; } = 100;
/// <summary>
/// 额外的 OLE DB 参数,按需添加。
/// </summary>
@@ -105,5 +124,20 @@ public class AccessDatabaseOptions
{
throw new InvalidOperationException("必须指定 Access Provider。");
}
if (ConnectionTimeout <= 0)
{
throw new InvalidOperationException("ConnectionTimeout 必须大于 0。");
}
if (MaxRetryCount < 0)
{
throw new InvalidOperationException("MaxRetryCount 不能小于 0。");
}
if (RetryDelayMilliseconds < 0)
{
throw new InvalidOperationException("RetryDelayMilliseconds 不能小于 0。");
}
}
}
+32 -5
View File
@@ -6,6 +6,7 @@ using Sharp.Log;
using Sharp.Data;
using Sharp.Data.Context;
using Sharp.Data.Access;
using Sharp.Data.Access.Options;
using Server.Config;
using Server.Services;
using Server.Data;
@@ -110,7 +111,7 @@ namespace Server
throw;
}
}
private void InitializeAccessDataService()
{
if (LoggerFactory == null)
@@ -123,13 +124,39 @@ namespace Server
try
{
var databasePath = Path.GetFullPath("dbDwDmsSetup.accdb");
var options = new AccessDataServiceOptions
// 诊断:输出当前目录信息
logger.LogInformation("AppContext.BaseDirectory: {BaseDirectory}", AppContext.BaseDirectory);
logger.LogInformation("Environment.CurrentDirectory: {CurrentDirectory}", Environment.CurrentDirectory);
logger.LogInformation("数据库文件在输出目录: {DbPath}",
Path.Combine(AppContext.BaseDirectory, "dbDwDmsSetup.accdb"));
logger.LogInformation("数据库文件是否存在: {Exists}",
File.Exists(Path.Combine(AppContext.BaseDirectory, "dbDwDmsSetup.accdb")));
// 配置 Access 数据库选项
var options = new AccessDatabaseOptions
{
DatabasePath = databasePath
DatabaseFile = "dbDwDmsSetup.accdb",
// 使用 ACE OLE DB 16.0(功能最完整,支持所有连接字符串参数)
// 注意:12.0 不支持 Connect Timeout 参数,会导致 0x80040E21 错误
ProviderName = AccessProviderNames.AceOleDb16,
// 如果数据库有密码,可以在这里设置:
// Password = "your_password",
// 或者使用加密密码(需要先注册自定义解密器):
// EncryptedPassword = "encrypted_password",
// 可选:调整稳健性配置
// MaxRetryCount = 3, // 最大重试次数
// RetryDelayMilliseconds = 100, // 重试延迟
// ConnectionTimeout = 15, // 连接超时(秒)
};
AccessDataService = new AccessDataService(options, LoggerFactory.CreateLogger<AccessDataService>());
// 创建 Access 数据服务
AccessDataService = new AccessDataService(
options,
logger: LoggerFactory.CreateLogger<AccessDataService>());
logger.LogInformation("Access 数据服务初始化成功");
}
+3
View File
@@ -7,6 +7,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<!-- 强制使用 x64 架构,与已安装的 64 位 ACE 驱动匹配 -->
<PlatformTarget>x64</PlatformTarget>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
@@ -1,10 +1,12 @@
using System;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using System.Windows;
using EntityFrameworkCore.Jet;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Sharp.Data.Access.Options;
using Server.Data.Access;
namespace Server.Views;
@@ -110,7 +112,7 @@ public partial class AccessDatabaseWindow : Window
}
/// <summary>
/// 创建 Access 数据库上下文
/// 创建 Access 数据库上下文(使用新的统一配置体系)
/// </summary>
private AccessDbContext CreateDbContext()
{
@@ -122,8 +124,18 @@ public partial class AccessDatabaseWindow : Window
throw new FileNotFoundException($"找不到 Access 数据库文件: {dbPath}");
}
// 构建连接字符串
var connectionString = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath}";
// 使用统一的配置方式构建连接字符串
var builder = new OleDbConnectionStringBuilder
{
["Provider"] = AccessProviderNames.AceOleDb16, // 使用 16.0
["Data Source"] = dbPath
// Connect Timeout 在某些情况下不稳定,已移除
};
// 如果数据库有密码,可以在这里设置:
// builder["Jet OLEDB:Database Password"] = "your_password";
var connectionString = builder.ConnectionString;
// 创建 DbContext 选项
var optionsBuilder = new DbContextOptionsBuilder<AccessDbContext>();
@@ -46,14 +46,14 @@ public partial class AccessDatabaseWindowOleDb : Window
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await LoadDataAsync();
await Task.Run(() => LoadData());
}
#region 基础操作
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
{
await LoadDataAsync();
await Task.Run(() => LoadData());
}
private async void TestConnectionButton_Click(object sender, RoutedEventArgs e)
@@ -68,7 +68,7 @@ public partial class AccessDatabaseWindowOleDb : Window
try
{
_logger?.LogInformation("测试 Access 数据库连接...");
var success = await _accessDataService.TestConnectionAsync();
var success = await Task.Run(() => _accessDataService.TestConnection());
if (success)
{
@@ -120,31 +120,42 @@ public partial class AccessDatabaseWindowOleDb : Window
try
{
// 检查用户是否已存在
var count = await _accessDataService.ExecuteScalarAsync(
"SELECT COUNT(*) FROM UserInfo WHERE UserName = ?",
new OleDbParameter("@UserName", userName));
if (Convert.ToInt32(count) > 0)
await Task.Run(() =>
{
MessageBox.Show($"用户 '{userName}' 已存在!", "警告",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 检查用户是否已存在
var count = _accessDataService.ExecuteScalar(
"SELECT COUNT(*) FROM UserInfo WHERE UserName = ?",
new OleDbParameter("@UserName", userName));
// 插入新用户
await _accessDataService.ExecuteAsync(
"INSERT INTO UserInfo (UserName, [Password]) VALUES (?, ?)",
new OleDbParameter("@UserName", userName),
new OleDbParameter("@Password", password));
if (Convert.ToInt32(count) > 0)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show($"用户 '{userName}' 已存在!", "警告",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
return;
}
_logger?.LogInformation($"成功添加用户: {userName}");
MessageBox.Show($"成功添加用户 '{userName}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
// 插入新用户
_accessDataService.Execute(
"INSERT INTO UserInfo (UserName, [Password]) VALUES (?, ?)",
new OleDbParameter("@UserName", userName),
new OleDbParameter("@Password", password));
UserNameTextBox.Clear();
PasswordTextBox.Clear();
await LoadDataAsync();
_logger?.LogInformation($"成功添加用户: {userName}");
Dispatcher.Invoke(() =>
{
MessageBox.Show($"成功添加用户 '{userName}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
UserNameTextBox.Clear();
PasswordTextBox.Clear();
});
LoadData();
});
}
catch (Exception ex)
{
@@ -189,25 +200,36 @@ public partial class AccessDatabaseWindowOleDb : Window
try
{
// 更新密码
var rowsAffected = await _accessDataService.ExecuteAsync(
"UPDATE UserInfo SET [Password] = ? WHERE UserName = ?",
new OleDbParameter("@Password", newPassword),
new OleDbParameter("@UserName", userName));
if (rowsAffected == 0)
await Task.Run(() =>
{
MessageBox.Show($"用户 '{userName}' 不存在!", "警告",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 更新密码
var rowsAffected = _accessDataService.Execute(
"UPDATE UserInfo SET [Password] = ? WHERE UserName = ?",
new OleDbParameter("@Password", newPassword),
new OleDbParameter("@UserName", userName));
_logger?.LogInformation($"成功修改用户 {userName} 的密码");
MessageBox.Show($"成功修改用户 '{userName}' 的密码!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
if (rowsAffected == 0)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show($"用户 '{userName}' 不存在!", "警告",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
return;
}
PasswordTextBox.Clear();
await LoadDataAsync();
_logger?.LogInformation($"成功修改用户 {userName} 的密码");
Dispatcher.Invoke(() =>
{
MessageBox.Show($"成功修改用户 '{userName}' 的密码!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
PasswordTextBox.Clear();
});
LoadData();
});
}
catch (Exception ex)
{
@@ -245,31 +267,39 @@ public partial class AccessDatabaseWindowOleDb : Window
try
{
// 使用事务删除用户及其权限
await _accessDataService.ExecuteInTransactionAsync(async transaction =>
await Task.Run(() =>
{
// 先删除用户的所有权限
await transaction.ExecuteAsync(
"DELETE FROM UserPermission WHERE UserInfo_UserName = ?",
new OleDbParameter("@UserName", userName));
// 再删除用户
var rowsAffected = await transaction.ExecuteAsync(
"DELETE FROM UserInfo WHERE UserName = ?",
new OleDbParameter("@UserName", userName));
if (rowsAffected == 0)
_accessDataService.ExecuteInTransaction(transaction =>
{
throw new InvalidOperationException($"用户 '{userName}' 不存在!");
}
// 先删除用户的所有权限
transaction.Execute(
"DELETE FROM UserPermission WHERE UserInfo_UserName = ?",
new OleDbParameter("@UserName", userName));
// 再删除用户
var rowsAffected = transaction.Execute(
"DELETE FROM UserInfo WHERE UserName = ?",
new OleDbParameter("@UserName", userName));
if (rowsAffected == 0)
{
throw new InvalidOperationException($"用户 '{userName}' 不存在!");
}
});
_logger?.LogInformation($"成功删除用户: {userName}");
Dispatcher.Invoke(() =>
{
MessageBox.Show($"成功删除用户 '{userName}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
UserNameTextBox.Clear();
PasswordTextBox.Clear();
});
LoadData();
});
_logger?.LogInformation($"成功删除用户: {userName}");
MessageBox.Show($"成功删除用户 '{userName}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
UserNameTextBox.Clear();
PasswordTextBox.Clear();
await LoadDataAsync();
}
catch (InvalidOperationException ex)
{
@@ -312,31 +342,41 @@ public partial class AccessDatabaseWindowOleDb : Window
try
{
// 检查权限是否已存在
var count = await _accessDataService.ExecuteScalarAsync(
"SELECT COUNT(*) FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
new OleDbParameter("@UserName", _selectedUserName),
new OleDbParameter("@PermissionCode", permissionCode));
if (Convert.ToInt32(count) > 0)
await Task.Run(() =>
{
MessageBox.Show($"用户 '{_selectedUserName}' 已拥有权限 '{permissionCode}'!", "警告",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 检查权限是否已存在
var count = _accessDataService.ExecuteScalar(
"SELECT COUNT(*) FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
new OleDbParameter("@UserName", _selectedUserName),
new OleDbParameter("@PermissionCode", permissionCode));
// 插入权限
await _accessDataService.ExecuteAsync(
"INSERT INTO UserPermission (UserInfo_UserName, Permission_Code) VALUES (?, ?)",
new OleDbParameter("@UserName", _selectedUserName),
new OleDbParameter("@PermissionCode", permissionCode));
if (Convert.ToInt32(count) > 0)
{
Dispatcher.Invoke(() =>
{
MessageBox.Show($"用户 '{_selectedUserName}' 已拥有权限 '{permissionCode}'!", "警告",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
return;
}
_logger?.LogInformation($"成功为用户 {_selectedUserName} 添加权限: {permissionCode}");
MessageBox.Show($"成功添加权限 '{permissionCode}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
// 插入权限
_accessDataService.Execute(
"INSERT INTO UserPermission (UserInfo_UserName, Permission_Code) VALUES (?, ?)",
new OleDbParameter("@UserName", _selectedUserName),
new OleDbParameter("@PermissionCode", permissionCode));
PermissionCodeTextBox.Clear();
LoadUserPermissions(_selectedUserName);
_logger?.LogInformation($"成功为用户 {_selectedUserName} 添加权限: {permissionCode}");
Dispatcher.Invoke(() =>
{
MessageBox.Show($"成功添加权限 '{permissionCode}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
PermissionCodeTextBox.Clear();
LoadUserPermissions(_selectedUserName);
});
});
}
catch (Exception ex)
{
@@ -378,16 +418,23 @@ public partial class AccessDatabaseWindowOleDb : Window
try
{
await _accessDataService.ExecuteAsync(
"DELETE FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
new OleDbParameter("@UserName", _selectedUserName),
new OleDbParameter("@PermissionCode", selectedPermission.Permission_Code));
await Task.Run(() =>
{
_accessDataService.Execute(
"DELETE FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
new OleDbParameter("@UserName", _selectedUserName),
new OleDbParameter("@PermissionCode", selectedPermission.Permission_Code));
_logger?.LogInformation($"成功删除用户 {_selectedUserName} 的权限: {selectedPermission.Permission_Code}");
MessageBox.Show($"成功删除权限 '{selectedPermission.Permission_Code}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
_logger?.LogInformation($"成功删除用户 {_selectedUserName} 的权限: {selectedPermission.Permission_Code}");
LoadUserPermissions(_selectedUserName);
Dispatcher.Invoke(() =>
{
MessageBox.Show($"成功删除权限 '{selectedPermission.Permission_Code}'!", "成功",
MessageBoxButton.OK, MessageBoxImage.Information);
LoadUserPermissions(_selectedUserName);
});
});
}
catch (Exception ex)
{
@@ -400,8 +447,7 @@ public partial class AccessDatabaseWindowOleDb : Window
#endregion
#region 数据加载
private async System.Threading.Tasks.Task LoadDataAsync()
private void LoadData()
{
if (_accessDataService == null) return;
@@ -410,7 +456,7 @@ public partial class AccessDatabaseWindowOleDb : Window
_logger?.LogInformation("开始加载 Access 数据库数据...");
// 查询所有用户(使用泛型映射)
var users = await _accessDataService.QueryAsync<UserInfoDto>(
var users = _accessDataService.Query<UserInfoDto>(
"SELECT [UserName], [Password] AS [Password] FROM [UserInfo]");
var userDetails = string.Join(" | ",
@@ -420,7 +466,7 @@ public partial class AccessDatabaseWindowOleDb : Window
// 为每个用户查询权限数量
foreach (var user in users)
{
var count = await _accessDataService.ExecuteScalarAsync(
var count = _accessDataService.ExecuteScalar(
"SELECT COUNT(*) FROM UserPermission WHERE UserInfo_UserName = ?",
new OleDbParameter("@UserName", user.UserName));
user.PermissionCount = Convert.ToInt32(count);
@@ -428,16 +474,22 @@ public partial class AccessDatabaseWindowOleDb : Window
_logger?.LogInformation($"加载了 {users.Count} 个用户");
// 更新 UI
UsersDataGrid.ItemsSource = users;
PermissionsDataGrid.ItemsSource = null;
_selectedUserName = null;
// 更新 UI(需要在 UI 线程上执行)
Dispatcher.Invoke(() =>
{
UsersDataGrid.ItemsSource = users;
PermissionsDataGrid.ItemsSource = null;
_selectedUserName = null;
});
}
catch (Exception ex)
{
_logger?.LogError(ex, "加载数据失败");
MessageBox.Show($"加载数据失败: {ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
Dispatcher.Invoke(() =>
{
MessageBox.Show($"加载数据失败: {ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
});
}
}
@@ -457,25 +509,32 @@ public partial class AccessDatabaseWindowOleDb : Window
}
}
private async void LoadUserPermissions(string userName)
private void LoadUserPermissions(string userName)
{
if (_accessDataService == null) return;
try
{
// 查询用户权限(使用泛型映射)
var permissions = await _accessDataService.QueryAsync<UserPermissionDto>(
var permissions = _accessDataService.Query<UserPermissionDto>(
"SELECT Permission_Code FROM UserPermission WHERE UserInfo_UserName = ?",
new OleDbParameter("@UserName", userName));
PermissionsDataGrid.ItemsSource = permissions;
Dispatcher.Invoke(() =>
{
PermissionsDataGrid.ItemsSource = permissions;
});
_logger?.LogInformation($"用户 {userName} 有 {permissions.Count} 个权限");
}
catch (Exception ex)
{
_logger?.LogError(ex, "加载权限失败");
MessageBox.Show($"加载权限失败: {ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
Dispatcher.Invoke(() =>
{
MessageBox.Show($"加载权限失败: {ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
});
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
},
"SharpLog": {
"UseConsole": true,
"MinLevel": "Information",
"MinLevel": "Debug",
"File": {
"Enabled": true,
"Path": "logs/server-.log",