Реализация пользовательских провайдеров с открытым типом - PullRequest
1 голос
/ 30 марта 2012

У меня есть пользовательская реализация IDataServiceMetadataProvider / IDataServiceQueryProvider / IDataServiceUpdateProvider, собранная из различных примеров, найденных в Интернете.До сих пор все мои сущности были четко определены, и все функционировало как хотелось.Я использую EF 4.3.Однако теперь я хотел бы разрешить сущности включать специальные свойства.

Для этой проблемы, скажем, у меня есть две сущности: Person и Property (собранные в People and 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).Я знаю, что «это» - это выражение, представляющее мою сущность «Персона».Что я не могу понять, так это как создать выражение, совместимое с Linq-To-Entities, которое будет оценивать для данного Person, имеет ли оно свойство с указанным именем и соответствующим значением.Любые советы по этому вопросу будут оценены.Возможно, больше всего озадачило то, как уменьшить общий запрос, который приводит к IQueryable, до одного элемента, который можно сравнить с.

Спасибо за любой совет!

THEОТВЕТ

Хорошо, мне потребовалось еще немного времени, чтобы разобраться с этим, но благодаря ответу Барри Келли на этот пост , я получил его на работу.

Мы начнем с реализации 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 аналогична CallAny Барри Келли (опять же, его post , который включает в себяреализация GetIEnumerableImpl.)

1 Ответ

0 голосов
/ 31 марта 2012

Лучший способ выяснить это (который я часто использую) - это попытаться написать пример кода непосредственно для EF, который даст вам желаемые результаты. После того, как вы работаете, обычно достаточно просто создать соответствующее выражение (если ничего другого, вы можете использовать пример запроса и просмотреть его в отладчике, чтобы увидеть узлы и тому подобное).

В этом конкретном случае вам придется перевести его в какое-то соединение. Может быть, что-то вроде

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

Но я не пытался понять, справится ли EF с таким условием.

...