OData $expand、DTO 和实体框架

本文关键字:实体 框架 DTO expand OData | 更新日期: 2023-09-27 18:34:53

我有一个基本的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 中不受支持。仅支持初始值设定项、实体成员和实体导航属性。

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

这与控制器上缺少的方法无关。如果我要创建一个实现 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; }
}

然后将产品控制器更改为:

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 查询从供应商表中联接和选择,如果不是为了下一个查询结果,这对我来说是有意义的。

如果我调用/odata/product?$select=Id,我会得到我所期望的。但$select转换为不联接到供应商表的 sql 查询。

/

odata/product?$expand=Product 失败并显示不同的错误:

"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=Supplier 返回正确的结果,但显然 .ToList(( 有点违背了目的。

我可以尝试将产品控制器修改为仅调用 .ToList(( 当传递$expand查询时,如下所示:

    [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实现?

OData $expand、DTO 和实体框架

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

您尚未在 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 了解更多信息。您的模型结构非常相似。

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

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

[Queryable(MaxExpansionDepth = 1)]