当前位置: 欣欣网 > 码农

C#中拦截器(AOP)的基础知识及其用法

2024-05-20码农

概述: 在复杂的软件开发世界中,使代码不仅高效,而且可扩展且易于维护至关重要。拦截器提供了一种优雅的方法来满足这些要求。但什么是拦截器?**拦截器的定义:**侦听器是一种机制,可以在调用方法之前或之后拦截和影响操作。它们充当调用方和目标函数之间的一种中间件。这种方法为提高代码质量和灵活性开辟了广泛的可能性。**为什么拦截器很重要?**使用拦截器有几个优点。关注点分离使开发人员能够更好地构建代码并创建可重用的组件。此外,拦截器还可以添加日志记录、安全和错误处理等功能,而无需直接修改主代码。例:**C# 中的简单日志记录拦截器:**让我们创建一个简单的日志记录拦截器。为此,我们使用通过 Nuget 安装的

在复杂的软件开发世界中,使代码不仅高效,而且可扩展且易于维护至关重要。拦截器提供了一种优雅的方法来满足这些要求。但什么是拦截器?

**拦截器的定义:**侦听器是一种机制,可以在调用方法之前或之后拦截和影响操作。它们充当调用方和目标函数之间的一种中间件。这种方法为提高代码质量和灵活性开辟了广泛的可能性。

**为什么拦截器很重要?**使用拦截器有几个优点。关注点分离使开发人员能够更好地构建代码并创建可重用的组件。此外,拦截器还可以添加日志记录、安全和错误处理等功能,而无需直接修改主代码。

例:

**C# 中的简单日志记录拦截器:**让我们创建一个简单的日志记录拦截器。为此,我们使用通过 Nuget 安装的 DynamicProxy:Autofac.Extras.DynamicProxy。
现在,我们创建第一个拦截器,并从以前安装的 Nuget 实现 IIntercaptor 接口:

usingCastle.DynamicProxy;
namespaceConsoleApp1.Interceptor;
public classLogginInterceptor : IInterceptor
{
publicvoidIntercept(IInvocation invocation)
{
Console.WriteLine($"Intercepted: {invocation.Method}");
invocation.Proceed();
}
}

此侦听器允许在调用每个方法之前集成日志记录函数。

在此上下文中,控制台中会显示一条文本消息。

若要截获方法调用,必须将相应的方法声明为虚拟方法。

非虚拟方法无法被覆盖,因此无法被截获。

namespaceConsoleApp1. classes;
public classMethods
{
publicvirtualvoidMethodA()
{
Console.WriteLine("method A and intercepted!!");
}
publicvoidMethodB()
{
Console.WriteLine("method B, not virtual => no interception! :(");
}
}

启动应用程序时,为「Methods」类创建代理对象后,可以截获和影响某些方法调用,而其他方法调用则不会被截获。

看到优势:

改装拦截器将非常容易!

usingCastle.DynamicProxy;
usingConsoleApp1. classes;
usingConsoleApp1.Interceptor;
var generator = newProxyGenerator();
var proxy = generator.Create classProxy\<Methods>(newLogginInterceptor());
// "Intercepting: MethodA"
proxy.MethodA();
// method B is not virtual -> no interception
proxy.MethodB();

基本原则现在应该很清楚了。

现在,让我们在实体框架的上下文中仔细看一下这个例子:

在实体框架中使用拦截器:

在 Entity Framework 中,可以在实际执行之前拦截和操作数据库上的操作。

拦截器可以有效地与实体框架结合使用,以监视、影响和优化数据库访问。

创建 DBContext 时,必须在 EntityFramework 下注册这些拦截器,才能使它们正常工作。
为此,我们必须在 DBContext 的配置中添加拦截器

public classDBContext : DbContext
{
publicDBContext(DbContextOptions<DBContext> options)
: base(options)
{
this.EnsureSeedData(); // new line added
}
//add the interceptors into the configbuilder of the dbcontext
protectedoverridevoidOnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.AddInterceptors(
newEfLoggingInterceptor(), // Loggin interceptor
newEfTransactionInterceptor(), // Transaction interceptor
newEfSaveChangesInterceptor()); // saveChanges interceptor
protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(Employee).Assembly);
}
publicDbSet<Employee> Data { get; set; }
}


下面是实体框架的简单日志记录拦截器示例:

usingMicrosoft.EntityFrameworkCore.Diagnostics;
usingSystem.Data.Common;
public classEfLoggingInterceptor : DbCommandInterceptor
{
publicoverrideInterceptionResult<int>NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
{
Console.WriteLine($"Executing NonQuery: {command.CommandText}");
returnbase.NonQueryExecuting(command, eventData, result);
}
publicoverrideInterceptionResult<DbDataReader>ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
Console.WriteLine($"Executing Reader: {command.CommandText}");
returnbase.ReaderExecuting(command, eventData, result);
}
// Additional methods for various actions such as ScalarExecuting, ScalarExecuted, etc.
}

此类扩展了实体框架的 DbCommandInterceptor 类,并启用对数据库命令的监视。

将拦截器与 EF 配合使用的最佳实践:

  • **目标应用:**确定拦截器提供最大好处的特定方案,以避免不必要的复杂性。

  • **性能注意事项:**在拦截器中进行大规模或计算密集型操作时要小心,以免影响性能。

  • **错误处理:**实施适当的错误管理,以确保拦截器稳健且无错误

  • 示例应用程序:拦截查询

    在此示例中,我们将日志记录侦听器集成到具有实体框架的 C# 应用程序中。这将记录数据库操作,以便深入了解已执行的 SQL 命令。

    若要实现 EfLoggingInterceptor,我们首先需要基类 DbCommandInterceptor 的派生。在我们的方案中,我们想要拦截数据库查询并分析用户查询了哪些数据。 需要注意的是,操作查询结果可能会导致可能的错误。 为了能够访问数据,我们使用了一个「 特殊技巧 」。
    建议注意,理想情况下,实体框架下的拦截器不应执行大量操作,因为这可能会导致潜在的问题,例如性能下降或处理结果时可能发生的错误。

    ReaderExecuted 方法已被重写,用于截获和分析查询结果。

    请注意此示例中的注释!

    publicoverrideDbDataReaderReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
    {
    if (eventData.Context == null)
    {
    returnbase.ReaderExecuted(command, eventData, result);
    }
    **///** In this example, we intercept the result that the user expects from the database and look at what the user has queried.
    **///** Here we have to make sure that we do not materialize the result object, otherwise it will be unusable for the user and trigger an error.
    **///** therefore we create a copy of the result and load it into a DataTable,
    **///** then read it out.
    usingvar dt = newDataTable();
    dt.Load(result);
    if (eventData.CommandSource == CommandSource.LinqQuery)
    {
    foreach (var row in dt.Rows.Cast<DataRow>())
    {
    string Id = row["Id"].ToString();
    string FirstName = row["FirstName"].ToString();
    string LastName = row["LastName"].ToString();
    LogInfo("EFCommandInterceptor.ReaderExecuted", $"{ Id} >> { FirstName}, { LastName}", command.CommandText);
    }
    }
    //since the user is still waiting for his result, we create a new reader here and then return it
    returnbase.ReaderExecuted(command, eventData, dt.CreateDataReader());
    }

    应该注意的是,拦截和分析数据库查询,然后将其转发给用户相对简单。

    操纵拦截

    还可以使用拦截器拦截 Entity Framework 中的插入、删除或更新命令。与查询拦截类似,可以重写 DbCommandInterceptor 中的相应方法来监视这些更改。

    下面是如何为插入、删除和更新命令创建侦听器的示例:

    publicoverrideInterceptionResult<int>SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
    DbContext? dbContext = eventData.Context;
    if (dbContext == null)
    {
    returnbase.SavingChanges(eventData, result);
    }
    **///** We get our entities via the ChangeTracker.
    **///** However, we only want to intercept those that we have previously defined,
    **///** here I have used a simple interface to identify these entities:
    var entities = dbContext!.ChangeTracker.Entries<IWiretap>().ToList();
    foreach (var entry in entities)
    {
    var auditMessage = entry.State switch
    {
    EntityState.Deleted => CreateDeleted(entry),
    EntityState.Modified => CreateModified(entry),
    EntityState.Added => CreateAdded(entry),
    _ => null
    };
    if (auditMessage != null)
    {
    Console.WriteLine(auditMessage);
    }
    }
    returnbase.SavingChanges(eventData, result);
    }

    为每个操作操作生成并显示通知。

    细心的观察者会注意到,在数据保存在数据库中之前,它会被截获以输出消息。

    在这里,您可以影响拦截器应拦截的时间点。

    让我们看一下实体框架下的不同类型的拦截器:

    查询拦截:

  • BeforeQueryExecuted :允许在将数据库查询发送到数据库之前截获数据库查询。这提供了在执行查询之前检查或更改查询的可能性。

  • SaveChanges 拦截:

  • BeforeSave :允许在保存对数据库的更改之前执行自定义代码。这对于在将更改写入数据库之前检查或验证更改非常有用。

  • AfterSave :允许在将更改保存到数据库后执行自定义代码。

  • ThrowingConCurrencyException :允许在引发 ConcurrencyException 之前执行自定义代码。

  • 并发错误是指当多个线程或进程在没有正确同步或协调的情况下同时访问或修改共享资源时发生的错误。这可能会导致难以重现或修复的不一致、意外或不正确的结果。

    并发的一个典型示例是两个用户想要同时更改表中的值。

    交易拦截:

  • AfterCommit :允许在事务成功完成后执行代码。

  • AfterRollback :允许在事务回滚后执行代码。

  • 连接拦截:

  • 打开 :启用对数据库连接打开的拦截。

  • 关闭 :允许关闭要截获的数据库连接。

  • 实体框架中拦截的优点:

    用户自定义逻辑:

  • 优点 :开发人员可以在数据库操作之前或之后集成自定义逻辑。这样可以更精细地控制数据访问行为。

  • 安全检查:

  • 优点 :拦截允许在执行数据库查询或保存更改之前添加安全检查,以防止未经授权的访问。

  • logging:

  • 优点 :开发人员可以使用拦截来记录 SQL 命令,这有助于调试和性能监控。

  • 数据验证:

  • _ 优点 :_在保存更改之前,开发人员可以实施自定义验证,以确保仅将有效数据写入数据库。

  • 交易管理:

  • _ 优点 :_拦截提供了实现自定义事务逻辑的能力,包括在提交或回滚事务后执行的代码。

  • 实体框架中拦截的缺点:

    增加复杂性:

  • 缺点 :使用拦截会增加代码的复杂性,尤其是在没有仔细管理的情况下。过多的拦截会导致代码难以理解。

  • 性能注意事项:

  • 缺点 :拦截方法中的代码优化不足可能会影响性能。开发人员必须确保添加的逻辑是有效的。

  • 对 Entity Framework 版本的依赖关系:

  • 缺点 :某些拦截功能可能取决于 Entity Framework 的版本,这可能会导致应用程序升级到较新版本时的兼容性问题。

  • 某些情况下的限制:

  • 缺点 :在某些复杂场景中,拦截可能无法满足所有要求。开发人员需要确保拦截适合其特定应用。

  • 考虑到这些优点和缺点,在实体框架中谨慎使用拦截至关重要,以免不必要地影响代码的可维护性和性能。

    如果你喜欢我的文章,请给我一个赞!谢谢