我們在希望阻止用戶即使接收對象從數據庫回來,如果他不應該訪問它的方式來實現我們的周圍NHibernate的持久層安全層。該安全層看起來是這樣的: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()安全()。
所以我們有許多類型的返回Expression<Func<T, bool>>
,這樣我們就可以把它傳遞到哪裏():
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
一樣,通過後再使用表達式API與MemberAccessExpressions和這樣打造出來,但我預計的混亂,會變成什麼樣子,我不知道什麼是兩害取其輕。
任何幫助,非常感謝。
工程就像一個魅力!非常感謝。順便說一句:對於其中一個表達式,MasterDocument的屬性嵌套了多個屬性(例如doc.PropA..PropB.MasterDocument),在這種情況下,存在PropA爲null的可能性。通常情況下,如果PropA爲空,我們將返回true,並且我認爲我必須以某種方式將它表達到表達式中。但事實上,事實證明並非必要。它沒有檢查,即使我可能期望NullReferenceException。對此有何解釋? – 2015-02-12 16:28:25
@MarcChu SQL以不同的方式處理空值。它只是在您嘗試對null值執行操作而不是崩潰時傳播null。當你試圖獲得'null'值的'PropB'值時,它只返回'null',而不是拋出。如果代碼是作爲C#代碼執行的,而不是被轉換成SQL,那麼你不得不明確地處理'null'。 – Servy 2015-02-12 17:22:53