【问题标题】:Implementing custom providers with an open type使用开放类型实现自定义提供程序
【发布时间】:2012-03-30 01:12:51
【问题描述】:

我有一个 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; }
}

对于配置,Person 有:

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

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

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

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

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

我的问题是,一旦我有了这些,我不知道如何用可以处理我的数据库结构的东西有效地替换表达式树。我试图替换的表达式类似于 ((GetValue(it, "A") == Convert("1")) == True)。我知道“它”是代表我的 Person 实体的表达式。我想不通的是如何创建一个与 Linq-To-Entities 兼容的表达式,它将评估给定 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 的实现。)

【问题讨论】:

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


    【解决方案1】:

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

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

    it.Properties.Any(p => p.Name == "A" && p.Value == "1")

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

    【讨论】:

    • 感谢您的提示!将此标记为答案,因为它为我指明了使用表达式直接调用 Any 的方向。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多