我有一个基本的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<Product> 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>("product");
oDataModelBuilder.EntitySet<Supplier>("supplier");
config.Routes.MapODataRoute(routeName: "oData",
routePrefix: "odata",
model: oDataModelBuilder.GetEdmModel());
}
我按如下方式设置了两个控制器:
public class ProductController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Product> Get()
{
var context = new ExampleContext();
var results = context.EF_Products
.Select(x => new Product() { Id = x.ProductId, Name = x.ProductName});
return results as IQueryable<Product>;
}
}
public class SupplierController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Supplier> Get()
{
var context = new ExampleContext();
var results = context.EF_Suppliers
.Select(x => new Supplier() { Id = x.SupplierId, Name = x.SupplierName });
return results as IQueryable<Supplier>;
}
}
以下是返回的元数据。如您所见,导航属性设置正确:
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<Schema Namespace="StackOverflowExample.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="Product">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
</EntityType>
<EntityType Name="Supplier">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
<NavigationProperty Name="Products" Relationship="StackOverflowExample.Models.StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner" ToRole="Products" FromRole="ProductsPartner" />
</EntityType>
<Association Name="StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner">
<End Type="StackOverflowExample.Models.Product" Role="Products" Multiplicity="*" />
<End Type="StackOverflowExample.Models.Supplier" Role="ProductsPartner" Multiplicity="0..1" />
</Association>
</Schema>
<Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
<EntitySet Name="product" EntityType="StackOverflowExample.Models.Product" />
<EntitySet Name="supplier" EntityType="StackOverflowExample.Models.Supplier" />
<AssociationSet Name="StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartnerSet" Association="StackOverflowExample.Models.StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner">
<End Role="ProductsPartner" EntitySet="supplier" />
<End Role="Products" EntitySet="product" />
</AssociationSet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
所以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<Product> 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<Product> 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<Product> 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<Product>;
}
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<Product>;
}
}
}
不幸的是,当我调用/ odata / product?$ select = Id或/ odata / product?$ expand = Supplier时会抛出序列化错误,因为returnValue无法转换为IQueryable。如果我打电话给/ odata / product,我可以演员。
这里的工作是什么?我是否只是不得不跳过尝试使用我自己的DTO,或者我应该/我应该推出自己的$ expand和$ select实现吗?
答案 0 :(得分:1)
基本问题已在EF 6.1.0中修复。见https://entityframework.codeplex.com/workitem/826。
答案 1 :(得分:0)
您尚未在web-api中设置实体关系。您需要向控制器添加更多方法。
我认为以下网址不起作用:/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了解更多信息。您的模型结构非常相似。
答案 2 :(得分:0)
您应该使用ICollection
导航属性而不是IQueryable
。这些类型非常不同。不确定这是你的问题,但值得修复。
答案 3 :(得分:0)
$ expand命令仅在控制器操作将MaxExpansionDepth参数添加到Queryable属性大于0时才有效。
[Queryable(MaxExpansionDepth = 1)]