搜尋
首頁後端開發C++使用 Linq、Criteria API 和 Query Over 擴充 NHibernate 的 Ardalis.Specification

Extending Ardalis.Specification for NHibernate with Linq, Criteria API, and Query Over

Ardalis.Specification 是一個功能強大的函式庫,支援查詢資料庫的規範模式,主要為Entity Framework Core 設計,但在這裡我將示範如何擴充Ardalis.Specification 來使用NHibernate也是一種ORM。

這篇部落格文章假設您對 Ardalis.Specification 有一定的經驗,並且希望在使用 NHibernate 的專案中使用它。如果您還不熟悉 Ardalis.Specification,請參閱文件以了解更多資訊。

首先,NHibernate 中有三種不同的內建方法來執行查詢

  • Linq 查詢(使用 IQueryable)
  • 標準 API
  • 查詢結束

我將介紹如何擴充 Ardalis.Specification 來處理所有 3 種方式,但由於 Linq to Query 也可以像 Entity Framework Core 一樣與 IQueryable 配合使用,因此我將首先介紹該選項。

Linq 查詢

在建立連線關係時,Entity Framework Core 和 NHIbernate 之間有細微差別。在 Entity Framework Core 中,我們在 IQueryable 上有擴充方法: Include 和 ThenInclude (這些也是 Ardalis.Specification 中使用的方法名稱)。

Fetch、FetchMany、ThenFetch 和 ThenFetchMany 是 IQueryable 上執行連線的 NHibernate 特定方法。 IEvaluator 為我們提供了使用 NHibernate 時呼叫正確擴充方法所需的可擴展性。

新增 IEvaluator 的實現,如下所示:

public class FetchEvaluator : IEvaluator
{
   private static readonly MethodInfo FetchMethodInfo = typeof(EagerFetchingExtensionMethods)
        .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.Fetch))
        .Single();

   private static readonly MethodInfo FetchManyMethodInfo = typeof(EagerFetchingExtensionMethods)
       .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.FetchMany))
       .Single();

   private static readonly MethodInfo ThenFetchMethodInfo
       = typeof(EagerFetchingExtensionMethods)
           .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.ThenFetch))
           .Single();

   private static readonly MethodInfo ThenFetchManyMethodInfo
       = typeof(EagerFetchingExtensionMethods)
           .GetTypeInfo().GetDeclaredMethods(nameof(EagerFetchingExtensionMethods.ThenFetchMany))
           .Single();

    public static FetchEvaluator Instance { get; } = new FetchEvaluator();

    public IQueryable<t> GetQuery<t>(IQueryable<t> query, ISpecification<t> specification) where T : class
    {
        foreach (var includeInfo in specification.IncludeExpressions)
        {
            query = includeInfo.Type switch
            {
                IncludeTypeEnum.Include => BuildInclude<t>(query, includeInfo),
                IncludeTypeEnum.ThenInclude => BuildThenInclude<t>(query, includeInfo),
                _ => query
            };
        }

        return query;
    }

    public bool IsCriteriaEvaluator { get; } = false;

    private IQueryable<t> BuildInclude<t>(IQueryable query, IncludeExpressionInfo includeInfo)
    {
        _ = includeInfo ?? throw new ArgumentNullException(nameof(includeInfo));

        var methodInfo = (IsGenericEnumerable(includeInfo.PropertyType, out var propertyType)
            ? FetchManyMethodInfo 
            : FetchMethodInfo);

       var method = methodInfo.MakeGenericMethod(includeInfo.EntityType, propertyType);

       var result = method.Invoke(null, new object[] { query, includeInfo.LambdaExpression });
        _ = result ?? throw new TargetException();

        return (IQueryable<t>)result;
    }

    private IQueryable<t> BuildThenInclude<t>(IQueryable query, IncludeExpressionInfo includeInfo)
    {
        _ = includeInfo ?? throw new ArgumentNullException(nameof(includeInfo));
        _ = includeInfo.PreviousPropertyType ?? throw new ArgumentNullException(nameof(includeInfo.PreviousPropertyType));

        var method = (IsGenericEnumerable(includeInfo.PreviousPropertyType, out var previousPropertyType)
            ? ThenFetchManyMethodInfo
            : ThenFetchMethodInfo);

        IsGenericEnumerable(includeInfo.PropertyType, out var propertyType);

        var result = method.MakeGenericMethod(includeInfo.EntityType, previousPropertyType, propertyType)
            .Invoke(null, new object[] { query, includeInfo.LambdaExpression });

        _ = result ?? throw new TargetException();

        return (IQueryable<t>)result;
    }

    private static bool IsGenericEnumerable(Type type, out Type propertyType)
    {
        if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IEnumerable)))
        {
            propertyType = type.GenericTypeArguments[0];

            return true;
        }

        propertyType = type;

        return false;
    }
}
</t></t></t></t></t></t></t></t></t></t></t></t>

接下來我們需要設定 ISpecificationEvaluator 以使用我們的 FetchEvaluator(和其他評估器)。我們新增一個實作 ISpecificationEvaluator ,如下所示,並在建構函式中配置評估器。 WhereEvaluator、OrderEvaluator 和 PaginationEvaluator 都在 Ardalis.Specification 中,並且在 NHibernate 中也能很好地工作。

public class LinqToQuerySpecificationEvaluator : ISpecificationEvaluator
{
    private List<ievaluator> Evaluators { get; } = new List<ievaluator>();

    public LinqToQuerySpecificationEvaluator()
    {
        Evaluators.AddRange(new IEvaluator[]
        {
            WhereEvaluator.Instance,
            OrderEvaluator.Instance,
            PaginationEvaluator.Instance,
            FetchEvaluator.Instance
        });
    }


    public IQueryable<tresult> GetQuery<t tresult>(IQueryable<t> query, ISpecification<t tresult> specification) where T : class
    {
        if (specification is null) throw new ArgumentNullException(nameof(specification));
        if (specification.Selector is null && specification.SelectorMany is null) throw new SelectorNotFoundException();
        if (specification.Selector is not null && specification.SelectorMany is not null) throw new ConcurrentSelectorsException();

        query = GetQuery(query, (ISpecification<t>)specification);

        return specification.Selector is not null
            ? query.Select(specification.Selector)
            : query.SelectMany(specification.SelectorMany!);
    }

    public IQueryable<t> GetQuery<t>(IQueryable<t> query, ISpecification<t> specification, bool evaluateCriteriaOnly = false) where T : class
    {
        if (specification is null) throw new ArgumentNullException(nameof(specification));

        var evaluators = evaluateCriteriaOnly ? Evaluators.Where(x => x.IsCriteriaEvaluator) : Evaluators;

        foreach (var evaluator in evaluators)
           query = evaluator.GetQuery(query, specification);

        return query;
    }
}
</t></t></t></t></t></t></t></t></tresult></ievaluator></ievaluator>

現在我們可以在我們的儲存庫中建立對 LinqToQuerySpecificationEvaluator 的引用,可能如下所示:

public class Repository : IRepository
{
    private readonly ISession _session;
    private readonly ISpecificationEvaluator _specificationEvaluator;

    public Repository(ISession session)
    {
        _session = session;
        _specificationEvaluator = new LinqToQuerySpecificationEvaluator();
    } 

    ... other repository methods

    public IEnumerable<t> List<t>(ISpecification<t> specification) where T : class
    {
        return _specificationEvaluator.GetQuery(_session.Query<t>().AsQueryable(), specification).ToList();
    }

    public IEnumerable<tresult> List<t tresult>(ISpecification<t tresult> specification) where T : class
    {    
        return _specificationEvaluator.GetQuery(_session.Query<t>().AsQueryable(), specification).ToList();
    }

    public void Dispose()
    {
        _session.Dispose();
    }
}

</t></t></t></tresult></t></t></t></t>

就是這樣。現在,我們可以在規格中使用 Linq to Query,就像我們通常使用 Ardalis 一樣。規範:

public class TrackByName : Specification<core.entitites.track>
{
    public TrackByName(string trackName)
    {
        Query.Where(x => x.Name == trackName);
    }
}
</core.entitites.track>

現在我們已經介紹了基於 Linq 的查詢,讓我們繼續處理 Criteria API 和 Query Over,這需要不同的方法。

在 NHibernate 中混合 Linq、Criteria 和 Query Over

由於 Criteria API 和 Query Over 有自己的實作來產生 SQL,且不使用 IQueryable,因此它們與 IEvaluator 介面不相容。我的解決方案是在這種情況下避免對這些方法使用 IEvaluator 接口,而是專注於規範模式的好處。但我也希望能夠混搭
我的解決方案中包含 Linq to Query、Criteria 和 Query Over(如果您只需要其中一種實現,您可以根據您的最佳需求進行挑選)。

為了能夠做到這一點,我新增了四個繼承Specification或Specification的新類別

注意: 定義這些類別的組件需要對 NHibernate 的引用,因為我們為 Criteria 和 QueryOver 定義操作,這可以在 NHibernate
中找到

public class CriteriaSpecification<t> : Specification<t>
{
    private Action<icriteria>? _action;
    public Action<icriteria> GetCriteria() => _action ?? throw new NotSupportedException("The criteria has not been specified. Please use UseCriteria() to define the criteria.");
    protected void UseCriteria(Action<icriteria> action) => _action = action;
}

public class CriteriaSpecification<t tresult> : Specification<t tresult>
{
    private Action<icriteria>? _action;
    public Action<icriteria> GetCriteria() => _action ?? throw new NotSupportedException("The criteria has not been specified. Please use UseCriteria() to define the criteria.");
    protected void UseCriteria(Action<icriteria> action) => _action = action;
}

public class QueryOverSpecification<t> : Specification<t>
{
    private Action<iqueryover t>>? _action;
    public Action<iqueryover t>> GetQueryOver() => _action ?? throw new NotSupportedException("The Query over has not been specified. Please use the UseQueryOver() to define the query over.");
    protected void UseQueryOver(Action<iqueryover t>> action) => _action = action;
}

public class QueryOverSpecification<t tresult> : Specification<t tresult>
{
    private Func<iqueryover t>, IQueryOver<t t>>? _action;
    public Func<iqueryover t>, IQueryOver<t t>> GetQueryOver() => _action ??  throw new NotSupportedException("The Query over has not been specified. Please use the UseQueryOver() to define the query over.");
    protected void UseQueryOver(Func<iqueryover t>, IQueryOver<t t>> action) => _action = action;
}
</t></iqueryover></t></iqueryover></t></iqueryover></t></t></iqueryover></iqueryover></iqueryover></t></t></icriteria></icriteria></icriteria></t></t></icriteria></icriteria></icriteria></t></t>

然後我們可以在儲存庫中使用模式匹配來更改使用 NHibernate 進行查詢的方式

public IEnumerable<t> List<t>(ISpecification<t> specification) where T : class
{
    return specification switch
    {
        CriteriaSpecification<t> criteriaSpecification => 
            _session.CreateCriteria<t>()
                .Apply(query => criteriaSpecification.GetCriteria().Invoke(query))
                .List<t>(),

        QueryOverSpecification<t> queryOverSpecification => 
            _session.QueryOver<t>()
                .Apply(queryOver => queryOverSpecification.GetQueryOver().Invoke(queryOver))
                .List<t>(),

        _ => _specificationEvaluator.GetQuery(_session.Query<t>().AsQueryable(), specification).ToList()
    };
}

public IEnumerable<tresult> List<t tresult>(ISpecification<t tresult> specification) where T : class
{

    return specification switch
    {
        CriteriaSpecification<t tresult> criteriaSpecification => 
            _session.CreateCriteria<t>()
                .Apply(query => criteriaSpecification.GetCriteria().Invoke(query))
                .List<tresult>(),

        QueryOverSpecification<t tresult> queryOverSpecification =>
            _session.QueryOver<t>()
                .Apply(queryOver => queryOverSpecification.GetQueryOver().Invoke(queryOver))
                .List<tresult>(),

        _ => _specificationEvaluator.GetQuery(_session.Query<t>().AsQueryable(), specification).ToList()
    };
}
</t></tresult></t></t></tresult></t></t></t></t></tresult></t></t></t></t></t></t></t></t></t></t>

上面的Apply()方法是一個擴充方法,它將查詢簡化為一行:

public static class QueryExtensions
{
    public static T Apply<t>(this T obj, Action<t> action)
    {
        action(obj);
        return obj;
    }

    public static TResult Apply<t tresult>(this T obj, Func<t tresult> func)
    {
        return func(obj);
    }
}
</t></t></t></t>

標準規格範例

注意: 定義這些類別的組件需要對 NHibernate 的引用,因為我們定義 Criteria 的操作,可以在 NHibernate
中找到

public class TrackByNameCriteria : CriteriaSpecification<track>
{
    public TrackByNameCriteria(string trackName)
    {
        this.UseCriteria(criteria => criteria.Add(Restrictions.Eq(nameof(Track.Name), trackName)));
    }
}
</track>

查詢超規格範例

注意: 定義這些類別的組件需要對 NHibernate 的引用,因為我們定義 QueryOver 的操作,可以在 NHibernate
中找到

public class TrackByNameQueryOver : QueryOverSpecification<track>
{
    public TrackByNameQueryOver(string trackName)
    {
        this.UseQueryOver(queryOver => queryOver.Where(x => x.Name == trackName));
    }
}
</track>

透過擴充 NHibernate 的 Ardalis.Specification,我們解鎖了在單一儲存庫模式中使用 Linq to Query、Criteria API 和 Query Over 的能力。這種方法為 NHibernate 用戶提供了高度適應性和強大的解決方案

以上是使用 Linq、Criteria API 和 Query Over 擴充 NHibernate 的 Ardalis.Specification的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
C標準模板庫(STL)如何工作?C標準模板庫(STL)如何工作?Mar 12, 2025 pm 04:50 PM

本文解釋了C標準模板庫(STL),重點關注其核心組件:容器,迭代器,算法和函子。 它詳細介紹了這些如何交互以啟用通用編程,提高代碼效率和可讀性t

如何有效地使用STL(排序,查找,轉換等)的算法?如何有效地使用STL(排序,查找,轉換等)的算法?Mar 12, 2025 pm 04:52 PM

本文詳細介紹了c中有效的STL算法用法。 它強調了數據結構選擇(向量與列表),算法複雜性分析(例如,std :: sort vs. std vs. std :: partial_sort),迭代器用法和並行執行。 常見的陷阱

我如何在C中有效處理異常?我如何在C中有效處理異常?Mar 12, 2025 pm 04:56 PM

本文詳細介紹了C中的有效異常處理,涵蓋了嘗試,捕捉和投擲機制。 它強調了諸如RAII之類的最佳實踐,避免了不必要的捕獲塊,並為強大的代碼登錄例外。 該文章還解決了Perf

在C中如何有效地使用RVALUE參考?在C中如何有效地使用RVALUE參考?Mar 18, 2025 pm 03:29 PM

文章討論了在C中有效使用RVALUE參考,以進行移動語義,完美的轉發和資源管理,重點介紹最佳實踐和性能改進。(159個字符)

如何在C 20中使用範圍進行更有表現的數據操縱?如何在C 20中使用範圍進行更有表現的數據操縱?Mar 17, 2025 pm 12:58 PM

C 20範圍通過表現力,合成性和效率增強數據操作。它們簡化了複雜的轉換並集成到現有代碼庫中,以提高性能和可維護性。

如何使用C中的移動語義來提高性能?如何使用C中的移動語義來提高性能?Mar 18, 2025 pm 03:27 PM

本文討論了使用C中的移動語義來通過避免不必要的複制來提高性能。它涵蓋了使用std :: Move的實施移動構造函數和任務運算符,並確定了關鍵方案和陷阱以有效

動態調度如何在C中起作用,如何影響性能?動態調度如何在C中起作用,如何影響性能?Mar 17, 2025 pm 01:08 PM

本文討論了C中的動態調度,其性能成本和優化策略。它突出了動態調度會影響性能並將其與靜態調度進行比較的場景,強調性能和之間的權衡

C語言數據結構:樹和圖的數據表示與操作C語言數據結構:樹和圖的數據表示與操作Apr 04, 2025 am 11:18 AM

C語言數據結構:樹和圖的數據表示與操作樹是一個層次結構的數據結構由節點組成,每個節點包含一個數據元素和指向其子節點的指針二叉樹是一種特殊類型的樹,其中每個節點最多有兩個子節點數據表示structTreeNode{intdata;structTreeNode*left;structTreeNode*right;};操作創建樹遍歷樹(先序、中序、後序)搜索樹插入節點刪除節點圖是一個集合的數據結構,其中的元素是頂點,它們通過邊連接在一起邊可以是帶權或無權的數據表示鄰

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能