當前位置: 妍妍網 > 碼農

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 的版本,這可能會導致應用程式升級到較新版本時的相容性問題。

  • 某些情況下的限制:

  • 缺點 :在某些復雜場景中,攔截可能無法滿足所有要求。開發人員需要確保攔截適合其特定套用。

  • 考慮到這些優點和缺點,在實體框架中謹慎使用攔截至關重要,以免不必要地影響程式碼的可維護性和效能。

    如果你喜歡我的文章,請給我一個贊!謝謝