OData $ expand,DTO和Entity Framework

我有一个基本的WebApi服务设置,数据库首先设置了EF DataModel。 我正在运行WebApi,EF6和WebApi OData包的每晚构建。 (WebApi:5.1.0-alpha1,EF:6.1.0-alpha1,WebApi OData:5.1.0-alpha1)

该数据库有两个表:产品和供应商。 产品可以有一个供应商。 供应商可以拥有多种产品。

我还创建了两个DTO类:

public class Supplier { [Key] public int Id { get; set; } public string Name { get; set; } public virtual IQueryable Products { get; set; } } public class Product { [Key] public int Id { get; set; } public string Name { get; set; } } 

我已按如下方式设置了我的WebApiConfig:

 public static void Register(HttpConfiguration config) { ODataConventionModelBuilder oDataModelBuilder = new ODataConventionModelBuilder(); oDataModelBuilder.EntitySet("product"); oDataModelBuilder.EntitySet("supplier"); config.Routes.MapODataRoute(routeName: "oData", routePrefix: "odata", model: oDataModelBuilder.GetEdmModel()); } 

我按如下方式设置了两个控制器:

 public class ProductController : ODataController { [HttpGet] [Queryable] public IQueryable Get() { var context = new ExampleContext(); var results = context.EF_Products .Select(x => new Product() { Id = x.ProductId, Name = x.ProductName}); return results as IQueryable; } } public class SupplierController : ODataController { [HttpGet] [Queryable] public IQueryable Get() { var context = new ExampleContext(); var results = context.EF_Suppliers .Select(x => new Supplier() { Id = x.SupplierId, Name = x.SupplierName }); return results as IQueryable; } } 

这是返回的元数据。 如您所见,导航属性设置正确:

                                     

所以odata查询的正常数组工作正常:/ odata / product?$ filter = Name + eq +’Product1’和/ odata / supplier?$ select = Id例如所有工作正常。

问题是当我尝试使用$ expand时。 如果我要做/ odata / supplier?$ expand = Products,我当然会收到错误:

“LINQ to Entities不支持指定的类型成员’Products’。仅支持初始值设定项,实体成员和实体导航属性。”

更新:我不断收到相同的问题,所以我要添加更多信息。 是的,导航属性设置正确,如上面发布的元数据信息中所示。

这与控制器上缺少的方法无关。 如果我要创建一个实现IODataRoutingConvention的类,/ odata / supplier(1)/ product将被解析为“〜/ entityset / key / navigation”就好了。

如果我完全绕过我的DTO并返回EF生成的类,$ expand开箱即用。

更新2:如果我将Product类更改为以下内容:

 public class Product { [Key] public int Id { get; set; } public string Name { get; set; } public virtual Supplier Supplier { get; set; } } 

然后将ProductController更改为:

 public class ProductController : ODataController { [HttpGet] [Queryable] public IQueryable Get() { var context = new ExampleContext(); return context.EF_Products .Select(x => new Product() { Id = x.ProductId, Name = x.ProductName, Supplier = new Supplier() { Id = x.EF_Supplier.SupplierId, Name = x.EF_Supplier.SupplierName } }); } } 

如果我打电话/ odata /产品,我会回到我的预期。 在响应中未返回“供应商”字段的“产品”数组。 sql查询生成了来自Suppliers表的连接和选择,如果不是下一个查询结果,这对我来说是有意义的。

如果我打电话给/ odata / product?$ select = Id,我会回复我的期望。 但$ select转换为不加入供应商表的SQL查询。

/ odata / product?$ expand =产品失败并出现不同的错误:

“DbIsNullExpression的参数必须引用基元,枚举或引用类型。”

如果我将产品控制器更改为以下内容:

 public class ProductController : ODataController { [HttpGet] [Queryable] public IQueryable Get() { var context = new ExampleContext(); return context.EF_Products .Select(x => new Product() { Id = x.ProductId, Name = x.ProductName, Supplier = new Supplier() { Id = x.EF_Supplier.SupplierId, Name = x.EF_Supplier.SupplierName } }) .ToList() .AsQueryable(); } } 

/ odata / product,/ odata / product?$ select = Id,和/ odata / product?$ expand =供应商返回正确的结果,但很显然.ToList()有点失败了。

我可以尝试修改Product Controller,只在传递$ expand查询时调用.ToList(),如下所示:

  [HttpGet] public IQueryable Get(ODataQueryOptions queryOptions) { var context = new ExampleContext(); if (queryOptions.SelectExpand == null) { var results = context.EF_Products .Select(x => new Product() { Id = x.ProductId, Name = x.ProductName, Supplier = new Supplier() { Id = x.EF_Supplier.SupplierId, Name = x.EF_Supplier.SupplierName } }); IQueryable returnValue = queryOptions.ApplyTo(results); return returnValue as IQueryable; } else { var results = context.EF_Products .Select(x => new Product() { Id = x.ProductId, Name = x.ProductName, Supplier = new Supplier() { Id = x.EF_Supplier.SupplierId, Name = x.EF_Supplier.SupplierName } }) .ToList() .AsQueryable(); IQueryable returnValue = queryOptions.ApplyTo(results); return returnValue as IQueryable; } } } 

不幸的是,当我调用/ odata / product?$ select = Id或/ odata / product?$ expand = Supplier时会抛出序列化错误,因为returnValue无法转换为IQueryable。 如果我打电话给/ odata / product,我可以演员。

这里的工作是什么? 我是否只是不得不跳过尝试使用我自己的DTO或可以/我应该推出自己的$ expand和$ select实现?

基本问题已在EF 6.1.0中修复。 请参阅https://entityframework.codeplex.com/workitem/826 。

您尚未在web-api中设置实体关系。 您需要向控制器添加更多方法。

我假设以下url不起作用: /odata/product(1)/Supplier这是因为没有设置关系。

将以下方法添加到您的控制器,我认为它应该解决问题:

 // GET /Products(1)/Supplier public Supplier GetSupplier([FromODataUri] int key) { var context = new ExampleContext(); Product product = context.EF_Products.FirstOrDefault(p => p.ID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product.Supplier; } 

我认为这符合您的命名。 根据需要修复它们。 有关详细信息, 请参阅http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/working-with-entity-relations 。 您的模型结构非常相似。

您应该使用ICollection导航属性而不是IQueryable 。 这些类型非常不同。 不确定这是你的问题,但值得修复。

$ expand命令仅在控制器操作将MaxExpansionDepth参数添加到Queryable属性(大于0)时才有效。

 [Queryable(MaxExpansionDepth = 1)]