使用开放类型实现自定义提供程序

时间:2012-03-30 01:12:51

标签: entity-framework linq-to-entities odata

我有一个IDataServiceMetadataProvider / IDataServiceQueryProvider / IDataServiceUpdateProvider的自定义实现,它是通过Web上的各种示例组合而成的。到目前为止,我所有的实体都已经很好地定义了,一切都按照需要运行。我使用的是EF 4.3。但是现在,我想允许实体包含ad-hoc属性。

对于这个问题,假设我有两个实体: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; }
}

对于配置,Person有:

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

我的数据库结构匹配,所以没有什么棘手的。 Person的元数据不会公开Property列表。

实现IDataServiceQueryProvider.GetOpenPropertyValues非常简单;我可以看到如何使用其特定属性返回我的实体。但是,当我按照以下方式提出请求时:

GET / Service / People?$ filter = A eq'1'

......我遇到了严重的麻烦。我正在使用自定义IQueryProvider,以便我可以插入自己的ExpressionVisitor。我对这段代码很有信心,因为我用它来拦截并处理一些将CanReflectOnInstanceTypeProperty设置为false的ResourceProperty。所以我重写了ExpressionVisitor.VisitMethodCall,并在调用OpenTypeMethod.Equal和OpenTypeMethod.GetValue时检测到。

我的问题是,一旦有了这些,我就不知道如何用能处理我的数据库结构的东西有效地替换表达式树。我试图替换的表达式看起来像((GetValue(it,“A”)== Convert(“1”))== True)。我知道'它'是表示我的Person实体的表达式。我无法弄清楚的是如何创建一个兼容Linq-To-Entities的Expression,它将为给定的Person评估它是否具有指定名称和匹配值的属性。对此有任何建议将不胜感激。也许最让人困惑的是如何减少一般查询,从而将IQueryable降低到可以与之比较的单个元素。

感谢您的任何建议!

答案

好的,我花了一些时间来解决这个问题,但多亏了Barry Kelly对此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方法,如果我在树中留下一个Call表达式,那就没关系,只要我再次访问该节点,因为Linq To Entities会然后替换它。我一直在想我需要将表达式简化为Linq-To-Entities直接支持的表达式。但事实上,只要可以翻译,你就可以留下更复杂的表达方式

CallFirstOrDefault的实现就像Barry Kelly的CallAny(再次,他的post,其中包括GetIEnumerableImpl的实现。)

1 个答案:

答案 0 :(得分:0)

解决这个问题的最佳方法(我经常使用)是尝试直接针对EF编写一些示例代码,这样可以获得所需的结果。一旦你开始工作,创建匹配表达式通常相当简单(如果没有别的,你可以使用示例查询并在调试器中查看它以查看节点等)。

在这种特殊情况下,您必须将其转换为某种联接。也许像是

it.Properties.Any(p =&gt; p.Name ==“A”&amp;&amp; p.Value ==“1”)

但我没有试着看EF是否可以处理这种情况。