2012-03-30 77 views
1

我有一個IDataServiceMetadataProvider/IDataServiceQueryProvider/IDataServiceUpdateProvider的自定義實現,放在一起從網上找到的各種示例。到現在爲止,我所有的實體都已經被很好地定義,並且所有的功能都是按照需要運行的我正在使用EF 4.3。但是現在,我想允許一個實體包含專門的屬性。使用開放類型實現自定義提供程序

對於這個問題,假設我有兩個實體:Person和Property(在People和Properties中收集)。這些都是簡單的對象:

public class Person 
{ 
    public Guid Id { get; set; } 
    public virtual IList<Property> Properties { get; set; } 
} 

public class Property 
{ 
    public Guid Id { get; set; } 
    public string Name { get; set; } 
    public string Value { get; set; } 
    public Person Person { get; set; } 
} 

對於配置,人員有:

// specify person and property association 
HasMany(p => p.Properties).WithRequired(a => a.Person).Map(x => x.MapKey("PERSONID")); 

我的數據庫結構相匹配,所以沒有什麼貓膩存在。 Person的元數據不公開Property列表。

實現IDataServiceQueryProvider.GetOpenPropertyValues很簡單;我可以看到如何返回我的實體與他們的特定屬性。然而,當我做線沿線的一個請求:

GET /服務/人$過濾器= A EQ '1'

...我對嚴重的麻煩運行。我正在使用自定義IQueryProvider,以便可以插入我自己的ExpressionVisitor。我對這段代碼有信心,因爲我正在使用它截取並處理一些ResourceReperty的CanReflectOnInstanceTypeProperty設置爲false。所以我重寫了ExpressionVisitor.VisitMethodCall,並在OpenTypeMethod.Equal和OpenTypeMethod.GetValue被調用時檢測到。

我的問題是,一旦我有這些,我不知道如何有效地替換表達式樹的東西,將處理我的數據庫結構。我想要替換的表達式看起來像((GetValue(it,「A」)== Convert(「1」))== True)。我知道'它'是表示我的Person實體的表達式。我無法弄清楚的是如何創建一個與Linq-To-Entities兼容的表達式,它將針對給定的Person來評估它是否具有指定名稱和匹配值的屬性。任何意見,將不勝感激。也許最讓人困惑的是如何減少一個導致IQueryable的一般查詢,最終導致可以比較的單個元素。

感謝您的任何建議!

答案

OK,我花了一段時間更得到這個整理出來,但由於巴里·凱利的回答這個post,我得到了它的工作。

我們從Expression Visitor實現開始。我們需要重寫VisitMethodCall並接收對OpenTypeMethods.GetValue,VisitBinary的調用以處理比較操作(此代碼中的Equal和GreaterThanOrEqual,但更多功能需要完整功能),以及VisitUnary處理Convert(我不確定需要,但它爲我工作)。

public class LinqToObjectExpressionVisitor : ExpressionVisitor 
{ 
    internal static readonly MethodInfo GetValueOpenPropertyMethodInfo = 
     typeof(OpenTypeMethods).GetMethod("GetValue", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(string) }, null); 

    internal static readonly MethodInfo GreaterThanOrEqualMethodInfo = 
     typeof(OpenTypeMethods).GetMethod("GreaterThanOrEqual", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(object) }, null); 

    internal static readonly MethodInfo EqualMethodInfo = 
     typeof(OpenTypeMethods).GetMethod("Equal", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(object) }, null); 

    protected override Expression VisitMethodCall(MethodCallExpression node) 
    { 
     if (node.Method == GetValueOpenPropertyMethodInfo) 
     { 
      return Visit(LinqResolver.ResolveOpenPropertyValue(node.Arguments[0], node.Arguments[1])); 
     } 

     return base.VisitMethodCall(node); 
    } 

    protected override Expression VisitBinary(BinaryExpression node) 
    { 
     MethodInfo mi = node.Method; 
     if (null != mi && mi.DeclaringType == typeof(OpenTypeMethods)) 
     { 
      Expression right = Visit(node.Right); 
      ConstantExpression constantRight = right as ConstantExpression; 
      if (constantRight != null && constantRight.Value is bool) 
      { 
       right = Expression.Constant(constantRight.Value, typeof(bool)); 
      } 

      Expression left = Visit(node.Left); 

      if (node.Method == EqualMethodInfo) 
      { 
       return Expression.Equal(left, right); 
      } 
      if (node.Method == GreaterThanOrEqualMethodInfo) 
      { 
       return Expression.GreaterThanOrEqual(left, right); 
      } 
     } 

     return base.VisitBinary(node); 
    } 

    protected override Expression VisitUnary(UnaryExpression node) 
    { 
     if (node.NodeType == ExpressionType.Convert) 
     { 
      return Visit(node.Operand); 
     } 
     return base.VisitUnary(node); 
    } 
} 

那麼什麼是LinqResolver?這是我自己的類來處理表達式樹重寫。

public class LinqResolver 
{ 
    public static Expression ResolveOpenPropertyValue(Expression entityExpression, Expression propertyExpression) 
    { 
     ConstantExpression propertyNameExpression = propertyExpression as ConstantExpression; 
     string propertyName = propertyNameExpression.Value as string; 

     // {it}.Properties 
     Expression propertiesExpression = Expression.Property(entityExpression, typeof(Person), "Properties"); 

     // (pp => pp.Name == {name}) 
     ParameterExpression propertyParameter = Expression.Parameter(typeof(Property), "pp"); 
     LambdaExpression exp = Expression.Lambda(
      Expression.Equal(Expression.Property(propertyParameter, "Name"), propertyNameExpression), 
      propertyParameter); 

     // {it}.Properties.FirstOrDefault(pp => pp.Name == {name}) 
     Expression resultProperty = CallFirstOrDefault(propertiesExpression, exp); 

     // {it}.Properties.FirstOrDefault(pp => pp.Name == {name}).Value 
     Expression result = Expression.Property(resultProperty, "Value"); 

     return result; 
    } 

    private static Expression CallFirstOrDefault(Expression collection, Expression predicate) 
    { 
     Type cType = GetIEnumerableImpl(collection.Type); 
     collection = Expression.Convert(collection, cType); 

     Type elemType = cType.GetGenericArguments()[0]; 
     Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool)); 

     // Enumerable.FirstOrDefault<T>(IEnumerable<T>, Func<T,bool>) 
     MethodInfo anyMethod = (MethodInfo) 
      GetGenericMethod(typeof(Enumerable), "FirstOrDefault", new[] { elemType }, 
       new[] { cType, predType }, BindingFlags.Static); 

     return Expression.Call(anyMethod, collection, predicate); 
    } 
} 

對我來說,關鍵是認識到,我可以用IEnumberable方法在我的樹,如果我留在我的樹呼叫的表情,那是OK的,只要我再次訪問該節點,因爲Linq To Entities然後將替換爲我。我一直在想,我需要將表達式轉化爲直接由Linq-To-Entities支持的表達式。但實際上,你可以在更復雜的表達式離開,只要它們可以被翻譯

CallFirstOrDefault的實施就像巴里·凱利的CallAny(同樣,他的post,其中包括GetIEnumerableImpl的執行情況。)

回答

0

解決這個問題的最好方法(我使用了很多)是嘗試直接寫EF的示例代碼,它會給你想要的結果。一旦你得到了這個工作,創建匹配表達式通常是相當簡單的(如果沒有其他的話,你可以使用示例查詢並在調試器中查看它以查看節點等)。

在這種特殊情況下,您必須將其翻譯爲某種連接。也許像

it.Properties.Any(P => p.Name == 「A」 & & p.Value == 「1」)

但我並沒有嘗試看看是否能EF處理這種情況。

+0

感謝您的提示!將其標記爲答案,因爲它指示我使用表達式直接調用Any的方向。 – object88 2012-04-04 18:33:03

相關問題