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
相关产品推荐
相关产品推荐

