You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NHibernate自定义Linq扩展问题:权限查询逻辑转换

问题描述

现有NH Linq查询如下:

var query = _session
    .Query<Revision>()
    .Join(
        _session.Query<Document>(),
        rev => rev.Document,
        doc => doc,
        (rev, doc) => new FolderDocumentRevision { Revision = rev, Document = doc }
    ).Where(
        item => item.Document.OrganisationalUnitRights.Any(cr => (cr.OrganisationalUnit!=null && parentOus.Contains(cr.OrganisationalUnit.Id) ||
                cr.RoleId!=null && (_securityInfo.SessionData.Profile.SystemRoleId==cr.RoleId || _securityInfo.SessionData.Profile.ApplicationRoleId==cr.RoleId )) 
            && cr.CanView)
    );

希望通过扩展方法HasAccess简化Where条件,最终调用方式如下:

query = query.Where(
    item => item.Document.OrganisationalUnitRights.HasAccess(SessionFactory,_securityInfo,cr => cr.CanView)
);

目前已实现部分BaseHqlGeneratorForMethod代码,但卡在Any()和parentOus.Contains逻辑的HQL转换上,初始代码如下:

public class WithRightLinqGenerator : BaseHqlGeneratorForMethod
{
    public WithRightLinqGenerator()
    {
        SupportedMethods = new[]
        {
            NHibernate.Util.ReflectHelper.GetMethodDefinition<ICollection<OrganisationalUnitRight>>(rights => rights.HasAccess(null, null, null))
        };
    }

    public override HqlTreeNode BuildHql(
        MethodInfo method,
        Expression targetObject,
        ReadOnlyCollection<Expression> arguments,
        HqlTreeBuilder treeBuilder,
        IHqlExpressionVisitor visitor)
    {
        var rightsExpression = visitor.Visit(arguments[0]).AsExpression();//visitor.Visit(targetObject).AsExpression();

        // The second argument is SessionContainer
        var sessionContainerExpression = arguments[1];
        var sessionContainerValue = GetConstantValue(sessionContainerExpression) as SessionContainer;

        // The third argument is SecurityInfo
        var securityInfoExpression = arguments[2];
        var securityInfoValue = GetConstantValue(securityInfoExpression) as SecurityInfo;

        var parentOus = sessionContainerValue.OUHierarchy.GetAllParents(securityInfoValue.SessionData.Profile.Id).ToList();
        var systemRoleId = securityInfoValue.SessionData.Profile.SystemRoleId;
        var applicationRoleId = securityInfoValue.SessionData.Profile.ApplicationRoleId;

        
    }
    private object GetConstantValue(Expression expression)
    {
        if (expression is ConstantExpression constantExpression)
        {
            return constantExpression.Value;
        }

        // You can extend this method to handle other types of expressions if needed
        throw new InvalidOperationException("Expected a constant expression.");
    }
}

求完成该查询转换的具体方案。

解决方案

1. 定义正确的扩展方法

先确保HasAccess扩展方法的签名与HqlGenerator匹配,同时支持传入的谓词:

public static class OrganisationalUnitRightExtensions
{
    public static bool HasAccess(this ICollection<OrganisationalUnitRight> rights, 
                                SessionContainer sessionContainer, 
                                SecurityInfo securityInfo,
                                Func<OrganisationalUnitRight, bool> accessPredicate)
    {
        // 内存执行逻辑,用于非查询场景
        var parentOus = sessionContainer.OUHierarchy.GetAllParents(securityInfo.SessionData.Profile.Id).ToList();
        var systemRoleId = securityInfo.SessionData.Profile.SystemRoleId;
        var applicationRoleId = securityInfo.SessionData.Profile.ApplicationRoleId;

        return rights.Any(cr => 
            (cr.OrganisationalUnit != null && parentOus.Contains(cr.OrganisationalUnit.Id) ||
             cr.RoleId != null && (systemRoleId == cr.RoleId || applicationRoleId == cr.RoleId)) 
            && accessPredicate(cr));
    }
}

2. 完成HqlGenerator的HQL构建逻辑

核心是把原有的Any条件转换成HQL节点,处理Contains和多条件组合:

public class WithRightLinqGenerator : BaseHqlGeneratorForMethod
{
    public WithRightLinqGenerator()
    {
        SupportedMethods = new[]
        {
            ReflectHelper.GetMethodDefinition<ICollection<OrganisationalUnitRight>>(
                rights => rights.HasAccess(null, null, cr => cr.CanView))
        };
    }

    public override HqlTreeNode BuildHql(
        MethodInfo method,
        Expression targetObject,
        ReadOnlyCollection<Expression> arguments,
        HqlTreeBuilder treeBuilder,
        IHqlExpressionVisitor visitor)
    {
        // 获取目标集合(OrganisationalUnitRights)的HQL表达式
        var collectionExpr = visitor.Visit(targetObject).AsExpression();

        // 获取常量参数值(SessionContainer和SecurityInfo必须是常量)
        var sessionContainer = GetConstantValue(arguments[0]) as SessionContainer;
        var securityInfo = GetConstantValue(arguments[1]) as SecurityInfo;
        var accessPredicateExpr = arguments[2] as LambdaExpression;

        // 计算权限相关常量值
        var parentOuIds = sessionContainer.OUHierarchy.GetAllParents(securityInfo.SessionData.Profile.Id).ToList();
        var systemRoleId = securityInfo.SessionData.Profile.SystemRoleId;
        var applicationRoleId = securityInfo.SessionData.Profile.ApplicationRoleId;

        // 构建HQL的Any子查询条件
        return treeBuilder.Any(collectionExpr, cr => 
        {
            // 组织单元权限条件:cr.OrganisationalUnit不为空且ID在父OU列表中
            var ouNotNull = treeBuilder.IsNotNull(treeBuilder.Dot(cr, "OrganisationalUnit"));
            var ouIdIn = treeBuilder.In(treeBuilder.Dot(treeBuilder.Dot(cr, "OrganisationalUnit"), "Id"), 
                                       treeBuilder.Constant(parentOuIds));
            var ouCondition = treeBuilder.And(ouNotNull, ouIdIn);

            // 角色权限条件:cr.RoleId不为空且匹配系统/应用角色ID
            var roleNotNull = treeBuilder.IsNotNull(treeBuilder.Dot(cr, "RoleId"));
            var roleMatchSystem = treeBuilder.Eq(treeBuilder.Dot(cr, "RoleId"), treeBuilder.Constant(systemRoleId));
            var roleMatchApp = treeBuilder.Eq(treeBuilder.Dot(cr, "RoleId"), treeBuilder.Constant(applicationRoleId));
            var roleCondition = treeBuilder.And(roleNotNull, treeBuilder.Or(roleMatchSystem, roleMatchApp));

            // 组合组织单元或角色的权限条件
            var ouOrRoleCondition = treeBuilder.Or(ouCondition, roleCondition);

            // 替换传入谓词的参数,适配HQL上下文
            var predicateExpr = visitor.Visit(accessPredicateExpr.Body.ReplaceParameter(accessPredicateExpr.Parameters[0], cr)).AsExpression();

            // 最终组合条件:(OU/角色权限) AND 自定义谓词
            return treeBuilder.And(ouOrRoleCondition, predicateExpr);
        });
    }

    private object GetConstantValue(Expression expression)
    {
        // 支持常量表达式和成员访问表达式(如静态属性)
        if (expression is ConstantExpression constantExpr)
            return constantExpr.Value;
        if (expression is MemberExpression memberExpr)
        {
            var obj = GetConstantValue(memberExpr.Expression);
            return memberExpr.Member.GetValue(obj);
        }
        throw new InvalidOperationException("SessionContainer和SecurityInfo必须是可解析的常量表达式");
    }
}

// 辅助类:替换Lambda表达式中的参数
public static class ExpressionExtensions
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression oldParam, Expression newParam)
    {
        return new ParameterReplacer(oldParam, newParam).Visit(expression);
    }

    private class ParameterReplacer : ExpressionVisitor
    {
        private readonly ParameterExpression _oldParam;
        private readonly Expression _newParam;

        public ParameterReplacer(ParameterExpression oldParam, Expression newParam)
        {
            _oldParam = oldParam;
            _newParam = newParam;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == _oldParam ? _newParam : base.VisitParameter(node);
        }
    }
}

3. 注册HqlGenerator到NHibernate配置

在NHibernate配置中注册自定义Linq生成器:

var configuration = new Configuration();
// 其他配置项...
configuration.LinqToHqlGeneratorsRegistry<CustomLinqToHqlGeneratorsRegistry>();

public class CustomLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public CustomLinqToHqlGeneratorsRegistry()
    {
        RegisterGenerator(typeof(OrganisationalUnitRightExtensions).GetMethod(nameof(OrganisationalUnitRightExtensions.HasAccess)), 
                          new WithRightLinqGenerator());
    }
}

关键说明

  • SessionContainer和SecurityInfo必须是常量表达式(如静态属性、局部常量),否则无法被NHibernate解析为HQL参数。
  • ReplaceParameter辅助类用于将传入的Lambda谓词参数替换为HQL构建时的集合元素参数,保证表达式上下文一致。
  • treeBuilder.In自动将parentOus.Contains转换为SQL的IN子句,符合NHibernate的查询转换规则。

内容的提问来源于stack exchange,提问作者robocik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:04:57