使用继承实体过滤OData请求会导致转换异常

时间:2012-06-16 00:22:57

标签: entity-framework linq-to-entities wcf-data-services odata

我觉得我问了很多问题,但我一直陷入困境。我正在开发一个OData服务,我想要一个可以有多个用户指定的名称 - 值对关联的实体,然后可以对其进行搜索。我使用的是EF4.3,DataServiceVersion 3.0。我正在使用自定义元数据,查询和更新提供程序。

所以,让我说我有一个实体Person:

public class Person : EntityBase
{
    public virtual IList<Property> PropertySet { get; set; }
}

(EntityBase是我所有实体都来自的常见POCO;它只有一个Guid ID属性。)现在让我们定义我们的属性:

public abstract class Property : EntityBase
{
    public string Name { get; set; }
    public Person Person { get; set; }
    public Guid PersonId { get; set; }
}

public class IntProperty : Property
{
    public int? IValue { get; set; }
}

public class StringProperty : Property
{
    public string SValue { get; set; }
}

到目前为止,这么好。在我的配置中,我使用Table Per Hierarchy进行继承。

现在,我可以向我的Person添加一个Property,当我发出这样的请求时:

GET /Service/People(guid'THE_ID')?$expand=PropertySet

有效:

{"d": {
  "__metadata": {...},
  "PropertySet": {
    "results": [{
      "__metadata": {...},
      "Id": "PROP_1_ID",
      "Name": "Number",
      "IValue": 1234
    },{
      "__metadata": {...},
      "Id": "PROP_2_ID",
      "Name": "EmailAddress",
      "SValue": "AAAA"
    }]
  },
  "Id": "THE_ID",
  }
}

如果我查询具有名为“EmailAddress”属性的Person,则可以使用:

GET /Service/People?$expand=PropertySet&$filter=PropertySet/any(x: x/Name eq 'EmailAddress')

但即便如此,我也不得不采取一些措施。我实现了一个表达式访问者,并且进行了一些Linq To Entities似乎不喜欢的比较:

protected override Expression VisitBinary(BinaryExpression node)
{
  if (node.NodeType == ExpressionType.Equal)
  {
    Expression left = Visit(node.Left);
    Expression right = Visit(node.Right);

    ConstantExpression rightConstant = right as ConstantExpression;
    if (null != rightConstant && rightConstant.Value == null)
    {
      if (left.Type == typeof(IList<Property>))
      {
        return Expression.Constant(false, typeof(bool));
      }
    }
  }
  return base.VisitBinary(node);
}

protected override Expression VisitConditional(ConditionalExpression node)
{
  Expression visitedTest = Visit(node.Test);
  Expression visitedIfTrue = Visit(node.IfTrue);
  Expression visitedIfFalse = Visit(node.IfFalse);
  ConstantExpression constantTest = visitedTest as ConstantExpression;
  if (null != constantTest && constantTest.Value is bool)
  {
    return ((bool)constantTest.Value) ? visitedIfTrue : visitedIfFalse;
  }
  return Expression.Condition(visitedTest, visitedIfTrue, visitedIfFalse);
}

要点是第一次覆盖我的查询获取表达式,例如“it.PropertySet == null”,我知道这些表达式总是不真实的。 (在我的spike中,唯一具有PropertySet的是Person,而Person总是有一个PropertySet。)在第二个覆盖中,我正在查看诸如“IIF((it.PropertySet == null),Empty之类的表达式(),it.PropertySet)“,我知道”它“将始终具有PropertySet。这可以防止将IList与null进行比较时出错。

现在,问题。

仅仅寻找物业的存在是不够的。我想检查它的价值:

GET /Service/People?$expand=PropertySet&$filter=PropertySet/any(x: x/Name eq 'EmailAddress' and cast(x, 'InheritedPropertyTest.Entities.StringProperty')/SValue eq 'AAAA')

这是结果查询:

value(System.Data.Objects.ObjectQuery`1[InheritedPropertyTest.Entities.Person])
.MergeAs(AppendOnly)
.Where(it => it.PropertySet.Any(x => ((x.Name == "EmailAddress") AndAlso (IIF((Convert(x) == null), null, Convert(x).SValue) == "AAAA"))))
.OrderBy(p => p.Id)
.Take(100)
.Select(p => new ExpandedWrapper`2() {ExpandedElement = p, Description = "PropertySet", ReferenceDescription = "", ProjectedProperty0 = p.PropertySet.OrderBy(p => p.Id)    .Take(100)})

但是我得到了这个错误:“无法将类型'InheritedPropertyTest.Entities.Property'强制转换为'InheritedPropertyTest.Entities.StringProperty'.LINQ to Entities仅支持转换实体数据模型基元类型。”所以...现在我再次被撞到了墙上。也许我的继承没有正确设置?我是否需要重载其他一些Expression Visitor方法才能使转换工作?如何说服Linq To Entities使用继承的属性?

谢谢!

1 个答案:

答案 0 :(得分:1)

使用类型段而不是强制转换。例如,x / InheritedPropertyTest.Entities.StringProperty / SValue 这应该转换为EF应该能够处理的TypeOf。

请注意,EF上的自定义提供程序无论如何都会遇到很多问题。简化表达式的一种方法是转换空传播(IDataServiceQueryProvider.IsNullPropagationRequired = false)。那应该摆脱IFF(i == null,null,某事)。 但是,当你开始使用投影($ select)时,你仍会遇到问题。