2015-02-11 77 views
0

我們在希望阻止用戶即使接收對象從數據庫回來,如果他不應該訪問它的方式來實現我們的周圍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和這樣打造出來,但我預計的混亂,會變成什麼樣子,我不知道什麼是兩害取其輕。

任何幫助,非常感謝。

回答

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)) 
         ); 

    } 
} 
+0

工程就像一個魅力!非常感謝。順便說一句:對於其中一個表達式,MasterDocument的屬性嵌套了多個屬性(例如doc.PropA..PropB.MasterDocument),在這種情況下,存在PropA爲null的可能性。通常情況下,如果PropA爲空,我們將返回true,並且我認爲我必須以某種方式將它表達到表達式中。但事實上,事實證明並非必要。它沒有檢查,即使我可能期望NullReferenceException。對此有何解釋? – 2015-02-12 16:28:25

+0

@MarcChu SQL以不同的方式處理空值。它只是在您嘗試對null值執行操作而不是崩潰時傳播null。當你試圖獲得'null'值的'PropB'值時,它只返回'null',而不是拋出。如果代碼是作爲C#代碼執行的,而不是被轉換成SQL,那麼你不得不明確地處理'null'。 – Servy 2015-02-12 17:22:53