【发布时间】:2015-02-11 20:47:29
【问题描述】:
我们在 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().Secure() 的装饰器包装 ISession 来限制对 ISession 的访问。
所以我们有许多类型返回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 等来构建它,但我预计会出现这样的混乱情况,我不确定哪个会更小邪恶的。
非常感谢任何帮助。
【问题讨论】:
标签: c# linq nhibernate linq-to-sql expression-trees