NHibernate,表达树,并消除重复

时间:2015-02-11 20:47:30

标签: c# linq nhibernate linq-to-sql expression-trees

我们已经在我们的NHibernate持久层周围实现了一个安全层,希望防止用户甚至无法从数据库接收对象,如果他不应该访问它。该安全层如下所示:

public static IQueryable<T> Secure<T>(this Queryable<T> query){
    //if T does not implement ISecurable, then return query
    //else
        return query.Where(expressionFactory.GetExpression(securityKey));
}

我们基本上通过使用调用ISession.Query()的装饰器包装来限制对ISession的访问.Secure()。

因此我们有许多返回Expression<Func<T, bool>>的类型,以便我们可以将其传递给Where():

public class DocumentSecurityExpressionFactory : ISecurityExpressionFactory<Document> {
    public Expression<Func<Document, bool>> GetExpression(SecurityKey key) {
        return doc => doc.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup) //Look at non-access group compartments for access
                        .All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
                    && (
            //person has to be either NTK 
                        doc.MasterDocument.NeedToKnowAccessList.Count() == 0
                        || doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
                        || doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
                        );
    }
}

public class DocumentSummarySecurityExpressionFactory : ISecurityExpressionFactory<DocumentSummary> {
    public Expression<Func<DocumentSummary, bool>> GetExpression(SecurityKey key) {
        return doc => doc.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
                        .All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
                        && (
                          doc.MasterDocument.NeedToKnowAccessList.Count() == 0
                        || doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
                        || doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
                           );
    }
}

public class LatestDocumentVersionSecurityExpressionFactory : ISecurityExpressionFactory<LatestDocumentVersion> {
    public Expression<Func<LatestDocumentVersion, bool>> GetExpression(SecurityKey key) {
        return version => version.BaseDocument.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
                            .All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
                            && (
                                version.BaseDocument.MasterDocument.NeedToKnowAccessList.Count() == 0
                                || version.BaseDocument.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
                                || version.BaseDocument.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
                            );
    }
}

对于不同类型,实际上还有几个看起来像这样。

这里的问题应该是明确的:我们这样做的每个实体都基本相同。它们每个都有一个对MasterDocument对象的引用,在该对象上完成所有逻辑。重复这段代码非常糟糕(而且它们都位于一个文件中,因此如果他们这样做,他们都可以一起改变)。

我觉得我应该能够告诉一个方法如何从类型T获取MasterDocument,然后有一个构建表达式的通用方法。像这样:

public static class ExpressionFactory {
    public static Expression<Func<T, bool>> Get<T>(Expression<Func<T, MasterDocument>> mdSource, SecurityKey key) {
        return t => {
             var md = mdSource.Compile()(t);
             return md.Compartments.Where(c => c.AssociatedCompartment)...
        };
    }
}

并称之为:

public class DocumentSecurityExpressionFactory : ISecurityExpressionFactory<Document> {
    public Expression<Func<Document, bool>> GetExpression(SecurityKey key) {
        return ExpressionFactory.Get<Document>(doc => doc.MasterDocument, key);
    }
}

现在,我理解为什么这段代码不起作用。我无法弄清楚的是如何正确构建这个表达式树,以便大大简化我们的代码。我想我可以像Expression<Func<T, MasterDocument>> mdSource那样传递,然后使用Expression API来构建它与MemberAccessExpressions等等,但我期待看起来像这样的混乱,我不知道会是什么较小的邪恶。

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:1)

你可以做的是使用Compose方法,可以将一个表达式与另一个表达式组合在一起:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

使用以下方法将一个表达式的所有实例替换为另一个:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

现在你可以写:

public static class ExpressionFactory 
{
    public static Expression<Func<T, bool>> Get<T>(
         Expression<Func<T, MasterDocument>> mdSource, SecurityKey key) 
    {
        return mdSource.Compose(document => 
            document.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
                    .All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
                    && (
                      doc.MasterDocument.NeedToKnowAccessList.Count() == 0
                    || doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
                    || doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
                       );

    }
}