首页 文章

OData $ expand,DTO和Entity Framework

提问于
浏览
16

我有一个基本的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 =名称eq'Product1'和/ odata / supplier?$ select = Id例如所有工作正常 .

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

“LINQ to Entities不支持指定的类型成员'Products' . 仅支持初始化程序,实体成员和实体导航属性 . ”

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

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

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

Update 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实现吗?

4 回答

  • 0

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

  • 1

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

    我假设以下url 's don' t也可以正常工作: /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了解更多信息 . 您的模型结构非常相似 .

  • 0

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

  • 0

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

    [Queryable(MaxExpansionDepth = 1)]
    

相关问题