我有一個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的執行情況。)
感謝您的提示!將其標記爲答案,因爲它指示我使用表達式直接調用Any的方向。 – object88 2012-04-04 18:33:03