EF Core扩展开发:创建自定义功能和插件的完整教程
引言:为什么需要EF Core扩展?
你是否曾经在使用Entity Framework Core时遇到过这样的痛点:
- 需要为所有实体自动添加审计字段(创建时间、修改时间等)
- 想要实现全局的软删除功能
- 需要在执行SQL命令前进行自定义处理
- 希望为特定数据库提供额外的优化功能
EF Core的强大之处在于其高度可扩展的架构设计。通过扩展机制,你可以深度定制ORM行为,创建属于自己的数据库提供程序,或者为现有功能添加增强特性。本文将带你全面掌握EF Core扩展开发的核心技术。
扩展开发的核心架构
EF Core扩展体系概览
核心接口解析
1. 拦截器接口 (IInterceptor)
public interface IInterceptor
{
// 基础拦截器接口,所有具体拦截器都继承自此接口
}
// 具体拦截器示例
public interface IDbCommandInterceptor : IInterceptor
{
ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default);
ValueTask<DbDataReader> ReaderExecutedAsync(
DbCommand command,
CommandExecutedEventData eventData,
DbDataReader result,
CancellationToken cancellationToken = default);
}
2. 选项扩展接口 (IDbContextOptionsExtension)
public interface IDbContextOptionsExtension
{
DbContextOptionsExtensionInfo Info { get; }
void ApplyServices(IServiceCollection services);
void Validate(IDbContextOptions options);
IDbContextOptionsExtension ApplyDefaults(IDbContextOptions options);
}
实战:创建自定义拦截器
示例1:SQL命令日志拦截器
public class SqlCommandLoggerInterceptor : IDbCommandInterceptor
{
private readonly ILogger<SqlCommandLoggerInterceptor> _logger;
public SqlCommandLoggerInterceptor(ILogger<SqlCommandLoggerInterceptor> logger)
{
_logger = logger;
}
public ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
LogCommand(command, eventData);
return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
private void LogCommand(DbCommand command, CommandEventData eventData)
{
var parameters = command.Parameters.Cast<DbParameter>()
.Select(p => $"{p.ParameterName}={p.Value}")
.ToArray();
_logger.LogInformation("执行SQL: {CommandText} 参数: {Parameters}",
command.CommandText, string.Join(", ", parameters));
}
}
示例2:自动审计字段拦截器
public class AuditInterceptor : ISaveChangesInterceptor
{
public ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var context = eventData.Context;
if (context == null) return new ValueTask<InterceptionResult<int>>(result);
var entries = context.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
var now = DateTime.UtcNow;
var currentUser = GetCurrentUser(); // 实现获取当前用户的方法
foreach (var entry in entries)
{
if (entry.Entity is IAuditableEntity auditable)
{
if (entry.State == EntityState.Added)
{
auditable.CreatedAt = now;
auditable.CreatedBy = currentUser;
}
auditable.UpdatedAt = now;
auditable.UpdatedBy = currentUser;
}
}
return new ValueTask<InterceptionResult<int>>(result);
}
}
public interface IAuditableEntity
{
DateTime CreatedAt { get; set; }
string CreatedBy { get; set; }
DateTime? UpdatedAt { get; set; }
string? UpdatedBy { get; set; }
}
创建自定义选项扩展
实现完整的选项扩展
public class CustomOptionsExtension : IDbContextOptionsExtension
{
private DbContextOptionsExtensionInfo? _info;
private readonly bool _enableFeatureX;
private readonly string _customSetting;
public CustomOptionsExtension()
{
_enableFeatureX = false;
_customSetting = string.Empty;
}
protected CustomOptionsExtension(CustomOptionsExtension copyFrom)
{
_enableFeatureX = copyFrom._enableFeatureX;
_customSetting = copyFrom._customSetting;
}
public virtual DbContextOptionsExtensionInfo Info
=> _info ??= new ExtensionInfo(this);
public virtual CustomOptionsExtension WithFeatureX(bool enable)
{
var clone = Clone();
clone._enableFeatureX = enable;
return clone;
}
public virtual CustomOptionsExtension WithCustomSetting(string setting)
{
var clone = Clone();
clone._customSetting = setting;
return clone;
}
public void ApplyServices(IServiceCollection services)
{
services.AddSingleton<ICustomService, CustomService>();
if (_enableFeatureX)
{
services.AddSingleton<IFeatureXService, FeatureXService>();
}
}
public void Validate(IDbContextOptions options)
{
// 验证配置是否有效
if (string.IsNullOrEmpty(_customSetting))
{
throw new InvalidOperationException("Custom setting is required");
}
}
protected virtual CustomOptionsExtension Clone()
=> new CustomOptionsExtension(this);
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
public ExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
{
}
public override bool IsDatabaseProvider => false;
public override string LogFragment => "custom extension";
public override int GetServiceProviderHashCode()
{
var extension = (CustomOptionsExtension)Extension;
var hashCode = new HashCode();
hashCode.Add(extension._enableFeatureX);
hashCode.Add(extension._customSetting);
return hashCode.ToHashCode();
}
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo otherInfo &&
((CustomOptionsExtension)Extension)._enableFeatureX ==
((CustomOptionsExtension)otherInfo.Extension)._enableFeatureX &&
((CustomOptionsExtension)Extension)._customSetting ==
((CustomOptionsExtension)otherInfo.Extension)._customSetting;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
debugInfo["Custom:EnableFeatureX"] =
((CustomOptionsExtension)Extension)._enableFeatureX.ToString();
debugInfo["Custom:CustomSetting"] =
((CustomOptionsExtension)Extension)._customSetting;
}
}
}
创建扩展方法
public static class CustomDbContextOptionsExtensions
{
public static DbContextOptionsBuilder UseCustomFeatures(
this DbContextOptionsBuilder optionsBuilder,
bool enableFeatureX = false,
string customSetting = "")
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
var extension = optionsBuilder.Options.FindExtension<CustomOptionsExtension>()
?? new CustomOptionsExtension();
extension = extension
.WithFeatureX(enableFeatureX)
.WithCustomSetting(customSetting);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder)
.AddOrUpdateExtension(extension);
return optionsBuilder;
}
}
实现自定义约定
模型构建约定示例
public class TableNamingConvention : IModelFinalizingConvention
{
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
var tableName = entityType.ClrType.Name.ToSnakeCase();
entityType.SetTableName(tableName);
// 为所有字符串属性设置默认长度
foreach (var property in entityType.GetProperties()
.Where(p => p.ClrType == typeof(string)))
{
property.SetMaxLength(200);
}
}
}
}
public static class StringExtensions
{
public static string ToSnakeCase(this string input)
{
if (string.IsNullOrEmpty(input)) return input;
return Regex.Replace(input,
@"([a-z0-9])([A-Z])", "$1_$2").ToLower();
}
}
注册约定到模型构建器
public class CustomModelBuilder : ModelBuilder
{
public CustomModelBuilder(ConventionSet conventions)
: base(conventions)
{
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(_ => new TableNamingConvention());
base.ConfigureConventions(configurationBuilder);
}
}
依赖注入和服务扩展
服务集合扩展
public static class CustomServiceCollectionExtensions
{
public static IServiceCollection AddCustomEfCoreServices(
this IServiceCollection services,
Action<CustomOptions>? configureOptions = null)
{
services.Configure(configureOptions ?? (o => { }));
services.AddScoped<ICustomDbContextService, CustomDbContextService>();
services.AddSingleton<ICustomValueGenerator, CustomValueGenerator>();
// 注册拦截器
services.AddScoped<SqlCommandLoggerInterceptor>();
services.AddScoped<AuditInterceptor>();
return services;
}
}
public class CustomOptions
{
public bool EnableAdvancedLogging { get; set; }
public string DefaultSchema { get; set; } = "custom";
}
自定义DbContext配置
public class CustomDbContext : DbContext
{
private readonly ICustomDbContextService _customService;
public CustomDbContext(
DbContextOptions<CustomDbContext> options,
ICustomDbContextService customService)
: base(options)
{
_customService = customService;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder
.UseCustomFeatures(enableFeatureX: true, customSetting: "production")
.AddInterceptors(
new SqlCommandLoggerInterceptor(
LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<SqlCommandLoggerInterceptor>()),
new AuditInterceptor());
}
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
// 应用自定义约定
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
// 自定义模型配置逻辑
_customService.ConfigureEntity(entityType, modelBuilder);
}
base.OnModelCreating(modelBuilder);
}
}
高级主题:性能优化和最佳实践
性能优化技巧
| 优化技术 | 适用场景 | 实现方式 | 性能提升 |
|---|---|---|---|
| 编译查询 | 高频查询 | EF.CompileAsyncQuery() | 减少表达式树编译开销 |
| 批量操作 | 大量数据插入/更新 | 自定义批量处理器 | 减少数据库往返次数 |
| 缓存机制 | 重复查询结果 | 查询结果缓存拦截器 | 避免重复数据库查询 |
| 连接池 | 高并发场景 | 自定义连接管理 | 减少连接建立开销 |
编译查询示例
public static class CompiledQueries
{
public static readonly Func<MyDbContext, int, Task<Customer?>> GetCustomerById =
EF.CompileAsyncQuery((MyDbContext context, int id) =>
context.Customers.FirstOrDefault(c => c.Id == id));
public static readonly Func<MyDbContext, string, IAsyncEnumerable<Customer>> GetCustomersByCity =
EF.CompileAsyncQuery((MyDbContext context, string city) =>
context.Customers.Where(c => c.City == city));
}
// 使用编译查询
var customer = await CompiledQueries.GetCustomerById(dbContext, 123);
await foreach (var customer in CompiledQueries.GetCustomersByCity(dbContext, "Beijing"))
{
// 处理客户数据
}
测试和调试扩展
单元测试策略
[TestFixture]
public class CustomInterceptorTests
{
[Test]
public async Task AuditInterceptor_ShouldSetAuditFields()
{
// 安排
var interceptor = new AuditInterceptor();
var dbContext = CreateTestDbContext();
var customer = new Customer { Name = "Test" };
dbContext.Customers.Add(customer);
var eventData = new DbContextEventData(
dbContext,
dbContext.GetService<IDiagnosticsLogger<DbLoggerCategory.Database>>());
// 执行
await interceptor.SavingChangesAsync(eventData, InterceptionResult<int>.SuppressWithResult(1));
// 断言
customer.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
customer.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
}
private TestDbContext CreateTestDbContext()
{
var options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase("Test")
.Options;
return new TestDbContext(options);
}
}
调试技巧
// 添加调试日志
public class DebugInterceptor : IDbCommandInterceptor
{
public ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
Debug.WriteLine($"Executing: {command.CommandText}");
foreach (DbParameter parameter in command.Parameters)
{
Debug.WriteLine($" {parameter.ParameterName} = {parameter.Value}");
}
return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
}
部署和发布扩展
NuGet包配置
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>MyCompany.EntityFrameworkCore.Extensions</PackageId>
<Version>1.0.0</Version>
<Description>Custom extensions for Entity Framework Core</Description>
<PackageTags>entity-framework-core;efcore;extensions</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
</ItemGroup>
</Project>
版本兼容性矩阵
| EF Core版本 | 扩展版本 | 主要特性 | 注意事项 |
|---|---|---|---|
| 8.0+ | 2.0+ | 编译查询、拦截器 | 需要.NET 8 |
| 7.0 | 1.5+ | 批量操作、JSON支持 | 部分特性受限 |
| 6.0 | 1.0+ | 基础扩展功能 | 维护模式 |
总结
通过本文的深入学习,你已经掌握了EF Core扩展开发的核心技术:
- 拦截器机制 - 实现对数据库操作的全面监控和定制
- 选项扩展 - 创建可配置的扩展功能
- 约定系统 - 统一模型构建规则
- 服务扩展 - 通过依赖注入集成自定义服务
EF Core的扩展架构提供了极大的灵活性,让你能够根据具体业务需求定制ORM行为。无论是简单的审计字段自动填充,还是复杂的自定义数据库提供程序,都可以通过扩展机制优雅实现。
记住扩展开发的最佳实践:
- 保持扩展的单一职责原则
- 提供清晰的配置接口
- 考虑性能影响并进行优化
- 编写充分的单元测试
- 确保版本兼容性
现在,你已经具备了创建专业级EF Core扩展的能力,开始构建属于你自己的ORM增强功能吧!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



