У меня есть пользовательская реализация 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.)