我有一个基本的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());
}

我已经按照以下步骤设置了两个 Controller :
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,我当然会报错:



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

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

如果我要完全绕开我的DTO并返回EF生成的类,则$ expand可以直接使用。

更新2:
如果将我的产品类别更改为以下内容:
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/product,我会得到我期望的。响应中未返回“供应商”字段的一组产品。生成的sql查询将联接并从Suppliers表中进行选择,这对于下一个查询结果来说对我来说是有意义的。

如果我要致电/odata/product?$ select = Id,我会得到想要的结果。但是$ select转换为不连接到供应商表的sql查询。

/odata/product?$ expand =产品因其他错误而失败:



如果我将产品 Controller 更改为以下内容:
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 = Supplier返回正确的结果,但是显然.ToList()违反了此目的。

我可以尝试将产品 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的实现滚动化吗?

最佳答案

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

关于c# - OData $ expand,DTO和 Entity Framework ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19323545/

10-16 23:47